@geekmidas/cli 10.0.0-alpha.64 → 10.0.0-alpha.65

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 (175) hide show
  1. package/README.md +1 -1
  2. package/dist/{HostingerProvider-Bx2lHHvu.cjs → HostingerProvider-Dlmbfass.cjs} +3 -3
  3. package/dist/{HostingerProvider-Bx2lHHvu.cjs.map → HostingerProvider-Dlmbfass.cjs.map} +1 -1
  4. package/dist/{HostingerProvider-Cd4aS5Kj.mjs → HostingerProvider-x9gQTAGZ.mjs} +2 -2
  5. package/dist/{HostingerProvider-Cd4aS5Kj.mjs.map → HostingerProvider-x9gQTAGZ.mjs.map} +1 -1
  6. package/dist/{LegacyStateStore-C2abV49N.cjs → LegacyStateStore-BGoo__sO.cjs} +2 -2
  7. package/dist/{LegacyStateStore-C2abV49N.cjs.map → LegacyStateStore-BGoo__sO.cjs.map} +1 -1
  8. package/dist/{LegacyStateStore-DIgkGaUP.mjs → LegacyStateStore-BUu1nQ5u.mjs} +2 -2
  9. package/dist/{LegacyStateStore-DIgkGaUP.mjs.map → LegacyStateStore-BUu1nQ5u.mjs.map} +1 -1
  10. package/dist/{LocalStateStore-DiJqKyPy.cjs → LocalStateStore-93urXyQe.cjs} +2 -2
  11. package/dist/{LocalStateStore-DiJqKyPy.cjs.map → LocalStateStore-93urXyQe.cjs.map} +1 -1
  12. package/dist/{LocalStateStore-Cy1ZBW7U.mjs → LocalStateStore-BRhMQRuy.mjs} +2 -2
  13. package/dist/{LocalStateStore-Cy1ZBW7U.mjs.map → LocalStateStore-BRhMQRuy.mjs.map} +1 -1
  14. package/dist/{S3StateStore-Ds0V1Lgm.cjs → S3StateStore-F4dxw6kS.cjs} +2 -2
  15. package/dist/{S3StateStore-Ds0V1Lgm.cjs.map → S3StateStore-F4dxw6kS.cjs.map} +1 -1
  16. package/dist/{S3StateStore-D3x6ivBL.mjs → S3StateStore-pw1GhWwt.mjs} +2 -2
  17. package/dist/{S3StateStore-D3x6ivBL.mjs.map → S3StateStore-pw1GhWwt.mjs.map} +1 -1
  18. package/dist/{SSMStateStore-CLipvx47.cjs → SSMStateStore-Bt9acuAF.cjs} +2 -2
  19. package/dist/{SSMStateStore-CLipvx47.cjs.map → SSMStateStore-Bt9acuAF.cjs.map} +1 -1
  20. package/dist/{SSMStateStore-DfF174xQ.mjs → SSMStateStore-Dmj7zzFO.mjs} +2 -2
  21. package/dist/{SSMStateStore-DfF174xQ.mjs.map → SSMStateStore-Dmj7zzFO.mjs.map} +1 -1
  22. package/dist/{bundler-C8uJf324.cjs → bundler-D6MlIfBT.cjs} +3 -3
  23. package/dist/{bundler-C8uJf324.cjs.map → bundler-D6MlIfBT.cjs.map} +1 -1
  24. package/dist/{bundler-DYgm9xsd.mjs → bundler-D_14JLZF.mjs} +3 -3
  25. package/dist/{bundler-DYgm9xsd.mjs.map → bundler-D_14JLZF.mjs.map} +1 -1
  26. package/dist/{cli-D3jofJ6j.cjs → cli-BfH1deAT.cjs} +6 -6
  27. package/dist/{cli-D3jofJ6j.cjs.map → cli-BfH1deAT.cjs.map} +1 -1
  28. package/dist/{cli-Bogz-zEj.mjs → cli-P56vvrDQ.mjs} +5 -5
  29. package/dist/{cli-Bogz-zEj.mjs.map → cli-P56vvrDQ.mjs.map} +1 -1
  30. package/dist/compose-391oEKj1.cjs +1982 -0
  31. package/dist/compose-391oEKj1.cjs.map +1 -0
  32. package/dist/compose-DprE3ASm.mjs +1862 -0
  33. package/dist/compose-DprE3ASm.mjs.map +1 -0
  34. package/dist/{config-DqNRiQOp.d.mts → config-BonvTW8p.d.mts} +3 -3
  35. package/dist/{config-Be-4_I6H.d.cts.map → config-BonvTW8p.d.mts.map} +1 -1
  36. package/dist/{config-Be-4_I6H.d.cts → config-V_X_BMpm.d.cts} +3 -3
  37. package/dist/{config-DqNRiQOp.d.mts.map → config-V_X_BMpm.d.cts.map} +1 -1
  38. package/dist/config.cjs +1 -1
  39. package/dist/config.d.cts +3 -3
  40. package/dist/config.d.mts +3 -3
  41. package/dist/config.mjs +1 -1
  42. package/dist/{credentials-B70vmLcm.cjs → credentials-Bzbg23n8.cjs} +176 -3
  43. package/dist/credentials-Bzbg23n8.cjs.map +1 -0
  44. package/dist/{credentials-s0wMeZBU.mjs → credentials-Dq1MnWQf.mjs} +128 -3
  45. package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
  46. package/dist/{define-CYs0PEfZ.mjs → define-DR8xGYfL.mjs} +5 -1
  47. package/dist/define-DR8xGYfL.mjs.map +1 -0
  48. package/dist/{define-zUsP72ym.cjs → define-DzRz8FgH.cjs} +5 -1
  49. package/dist/define-DzRz8FgH.cjs.map +1 -0
  50. package/dist/deploy/public.cjs +18 -18
  51. package/dist/deploy/public.cjs.map +1 -1
  52. package/dist/deploy/public.d.cts +7 -4
  53. package/dist/deploy/public.d.cts.map +1 -1
  54. package/dist/deploy/public.d.mts +7 -4
  55. package/dist/deploy/public.d.mts.map +1 -1
  56. package/dist/deploy/public.mjs +11 -11
  57. package/dist/{deploy-Dc2LBJaK.mjs → deploy-9MBG_QLd.mjs} +8 -7
  58. package/dist/deploy-9MBG_QLd.mjs.map +1 -0
  59. package/dist/{deploy-yUcR2OlW.cjs → deploy-B80idPUZ.cjs} +11 -10
  60. package/dist/deploy-B80idPUZ.cjs.map +1 -0
  61. package/dist/{deploy-ZRXIPp1c.mjs → deploy-CZ5nlpsu.mjs} +13 -119
  62. package/dist/deploy-CZ5nlpsu.mjs.map +1 -0
  63. package/dist/{deploy-55rJkdKz.cjs → deploy-Cy8igpQF.cjs} +39 -145
  64. package/dist/deploy-Cy8igpQF.cjs.map +1 -0
  65. package/dist/{dokploy-CepdXuMl.mjs → dokploy-BdtBePjp.mjs} +3 -3
  66. package/dist/{dokploy-CepdXuMl.mjs.map → dokploy-BdtBePjp.mjs.map} +1 -1
  67. package/dist/{dokploy-D1d37rvM.cjs → dokploy-iFY8MY-L.cjs} +3 -3
  68. package/dist/{dokploy-D1d37rvM.cjs.map → dokploy-iFY8MY-L.cjs.map} +1 -1
  69. package/dist/{encryption-C9X98u2F.cjs → encryption-B1wJwgae.cjs} +5 -5
  70. package/dist/{encryption-C9X98u2F.cjs.map → encryption-B1wJwgae.cjs.map} +1 -1
  71. package/dist/{encryption-CKHjMpCr.mjs → encryption-DF_hgIpl.mjs} +4 -4
  72. package/dist/{encryption-CKHjMpCr.mjs.map → encryption-DF_hgIpl.mjs.map} +1 -1
  73. package/dist/{github-D-byNI7T.mjs → github-B0YYE98x.mjs} +3 -3
  74. package/dist/{github-D-byNI7T.mjs.map → github-B0YYE98x.mjs.map} +1 -1
  75. package/dist/{github-Dd3tMa1u.cjs → github-CYcmTdFs.cjs} +3 -3
  76. package/dist/{github-Dd3tMa1u.cjs.map → github-CYcmTdFs.cjs.map} +1 -1
  77. package/dist/{index-CfE9Y885.d.cts → index-DPUAGJaK.d.cts} +16 -3
  78. package/dist/index-DPUAGJaK.d.cts.map +1 -0
  79. package/dist/{index-CJ2qsSpp.d.mts → index-DwKD_8lt.d.mts} +16 -3
  80. package/dist/index-DwKD_8lt.d.mts.map +1 -0
  81. package/dist/index.cjs +233 -1667
  82. package/dist/index.cjs.map +1 -1
  83. package/dist/index.mjs +196 -1614
  84. package/dist/index.mjs.map +1 -1
  85. package/dist/{local-C7wnrcYK.cjs → local-BdH4XN0E.cjs} +1 -178
  86. package/dist/local-BdH4XN0E.cjs.map +1 -0
  87. package/dist/{local-Cd8ZYQG3.mjs → local-CWR4Y_kW.mjs} +3 -126
  88. package/dist/local-CWR4Y_kW.mjs.map +1 -0
  89. package/dist/{migrate-B4gngTLe.mjs → migrate-B2qL5wj4.mjs} +6 -5
  90. package/dist/{migrate-B4gngTLe.mjs.map → migrate-B2qL5wj4.mjs.map} +1 -1
  91. package/dist/{migrate-tx9-P9uz.cjs → migrate-q4pPAXtO.cjs} +19 -18
  92. package/dist/{migrate-tx9-P9uz.cjs.map → migrate-q4pPAXtO.cjs.map} +1 -1
  93. package/dist/openapi.d.cts +1 -1
  94. package/dist/openapi.d.mts +1 -1
  95. package/dist/provider-C9_-45d5.cjs +56 -0
  96. package/dist/provider-C9_-45d5.cjs.map +1 -0
  97. package/dist/provider-q4lsYzYG.mjs +45 -0
  98. package/dist/provider-q4lsYzYG.mjs.map +1 -0
  99. package/dist/reconcile/public.d.cts +1 -1
  100. package/dist/reconcile/public.d.mts +1 -1
  101. package/dist/resolve-DLkIXTct.mjs +262 -0
  102. package/dist/resolve-DLkIXTct.mjs.map +1 -0
  103. package/dist/resolve-N_i4XNd-.cjs +315 -0
  104. package/dist/resolve-N_i4XNd-.cjs.map +1 -0
  105. package/dist/{runtime-CJEYK4Gz.d.mts → runtime-BnD7Nn96.d.cts} +2 -2
  106. package/dist/{runtime-CJEYK4Gz.d.mts.map → runtime-BnD7Nn96.d.cts.map} +1 -1
  107. package/dist/{runtime-Cjq1jlVX.d.cts → runtime-j-tmFL65.d.mts} +2 -2
  108. package/dist/{runtime-Cjq1jlVX.d.cts.map → runtime-j-tmFL65.d.mts.map} +1 -1
  109. package/dist/sniffer-BSwaL-dX.cjs +2236 -0
  110. package/dist/sniffer-BSwaL-dX.cjs.map +1 -0
  111. package/dist/sniffer-BZ-OJO4Y.mjs +2068 -0
  112. package/dist/sniffer-BZ-OJO4Y.mjs.map +1 -0
  113. package/dist/{sst-Cper040I.mjs → sst-B2HbrDR3.mjs} +4 -4
  114. package/dist/{sst-Cper040I.mjs.map → sst-B2HbrDR3.mjs.map} +1 -1
  115. package/dist/{sst-BdALKEas.cjs → sst-DcfsVH6x.cjs} +4 -4
  116. package/dist/{sst-BdALKEas.cjs.map → sst-DcfsVH6x.cjs.map} +1 -1
  117. package/dist/{sniffer-49KPbDgq.mjs → state-B8YwmUZU.mjs} +878 -2465
  118. package/dist/state-B8YwmUZU.mjs.map +1 -0
  119. package/dist/{sniffer-ByhbEW5A.cjs → state-DF26e_lo.cjs} +811 -2525
  120. package/dist/state-DF26e_lo.cjs.map +1 -0
  121. package/dist/{store-D_2ByRtW.cjs → store-CJF1Tqkq.cjs} +2 -2
  122. package/dist/{store-D_2ByRtW.cjs.map → store-CJF1Tqkq.cjs.map} +1 -1
  123. package/dist/{store-BaaonhkJ.mjs → store-CLiHnqOY.mjs} +2 -2
  124. package/dist/{store-BaaonhkJ.mjs.map → store-CLiHnqOY.mjs.map} +1 -1
  125. package/dist/target/public.cjs +6 -6
  126. package/dist/target/public.d.cts +2 -2
  127. package/dist/target/public.d.mts +2 -2
  128. package/dist/target/public.mjs +3 -3
  129. package/dist/terminal-Bs5E1oxm.cjs +88 -0
  130. package/dist/terminal-Bs5E1oxm.cjs.map +1 -0
  131. package/dist/terminal-CfbiYUbZ.mjs +84 -0
  132. package/dist/terminal-CfbiYUbZ.mjs.map +1 -0
  133. package/dist/{types-B_bEQvb9.d.mts → types-BCzMWBb6.d.mts} +43 -11
  134. package/dist/types-BCzMWBb6.d.mts.map +1 -0
  135. package/dist/{types-DkrDOMxw.d.cts → types-ByMWkXKP.d.cts} +43 -11
  136. package/dist/types-ByMWkXKP.d.cts.map +1 -0
  137. package/dist/workspace/index.cjs +3 -1
  138. package/dist/workspace/index.d.cts +3 -3
  139. package/dist/workspace/index.d.mts +3 -3
  140. package/dist/workspace/index.mjs +2 -2
  141. package/dist/{workspace-BwgWJK7h.cjs → workspace-BewAHZIo.cjs} +38 -3
  142. package/dist/workspace-BewAHZIo.cjs.map +1 -0
  143. package/dist/{workspace-Bwit4q9O.mjs → workspace-C06_pW4g.mjs} +27 -4
  144. package/dist/workspace-C06_pW4g.mjs.map +1 -0
  145. package/package.json +13 -13
  146. package/dist/credentials-B70vmLcm.cjs.map +0 -1
  147. package/dist/credentials-s0wMeZBU.mjs.map +0 -1
  148. package/dist/define-CYs0PEfZ.mjs.map +0 -1
  149. package/dist/define-zUsP72ym.cjs.map +0 -1
  150. package/dist/deploy-55rJkdKz.cjs.map +0 -1
  151. package/dist/deploy-Dc2LBJaK.mjs.map +0 -1
  152. package/dist/deploy-ZRXIPp1c.mjs.map +0 -1
  153. package/dist/deploy-yUcR2OlW.cjs.map +0 -1
  154. package/dist/index-CJ2qsSpp.d.mts.map +0 -1
  155. package/dist/index-CfE9Y885.d.cts.map +0 -1
  156. package/dist/local-C7wnrcYK.cjs.map +0 -1
  157. package/dist/local-Cd8ZYQG3.mjs.map +0 -1
  158. package/dist/resolve-BXsNQjMR.cjs +0 -220
  159. package/dist/resolve-BXsNQjMR.cjs.map +0 -1
  160. package/dist/resolve-DZ2IJ0qY.mjs +0 -173
  161. package/dist/resolve-DZ2IJ0qY.mjs.map +0 -1
  162. package/dist/runtime-BGNDrHzh.cjs +0 -163
  163. package/dist/runtime-BGNDrHzh.cjs.map +0 -1
  164. package/dist/runtime-Cmn-FeYO.mjs +0 -134
  165. package/dist/runtime-Cmn-FeYO.mjs.map +0 -1
  166. package/dist/sniffer-49KPbDgq.mjs.map +0 -1
  167. package/dist/sniffer-ByhbEW5A.cjs.map +0 -1
  168. package/dist/terminal-Bdv--jhY.cjs +0 -481
  169. package/dist/terminal-Bdv--jhY.cjs.map +0 -1
  170. package/dist/terminal-D7Ntz6WE.mjs +0 -440
  171. package/dist/terminal-D7Ntz6WE.mjs.map +0 -1
  172. package/dist/types-B_bEQvb9.d.mts.map +0 -1
  173. package/dist/types-DkrDOMxw.d.cts.map +0 -1
  174. package/dist/workspace-BwgWJK7h.cjs.map +0 -1
  175. package/dist/workspace-Bwit4q9O.mjs.map +0 -1
@@ -1,24 +1,19 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_local = require("./local-C7wnrcYK.cjs");
2
+ const require_local = require("./local-BdH4XN0E.cjs");
3
3
  const require_discover = require("./discover-DHdECueV.cjs");
4
- const require_sandbox = require("./sandbox-CL--ZDui.cjs");
5
4
  const require_derive = require("./derive-DAbo49sm.cjs");
6
- const require_workspace = require("./workspace-BwgWJK7h.cjs");
5
+ const require_workspace = require("./workspace-BewAHZIo.cjs");
7
6
  const require_config = require("./config.cjs");
8
7
  const require_storage = require("./storage-CSGoC3bK.cjs");
9
- const require_store = require("./store-D_2ByRtW.cjs");
10
- const require_runtime = require("./runtime-BGNDrHzh.cjs");
8
+ const require_store = require("./store-CJF1Tqkq.cjs");
9
+ const require_resolve = require("./resolve-N_i4XNd-.cjs");
11
10
  const require_fakes = require("./fakes-zW-jNAbQ.cjs");
12
11
  let node_path = require("node:path");
13
- let node_process = require("node:process");
14
- let node_readline_promises = require("node:readline/promises");
15
- node_readline_promises = require_rolldown_runtime.__toESM(node_readline_promises, 1);
16
12
  let node_fs = require("node:fs");
17
13
  let node_fs_promises = require("node:fs/promises");
18
14
  let node_os = require("node:os");
19
15
  let node_util = require("node:util");
20
16
  let _geekmidas_manifest = require("@geekmidas/manifest");
21
- let node_url = require("node:url");
22
17
  let node_child_process = require("node:child_process");
23
18
  let node_net = require("node:net");
24
19
  let dotenv = require("dotenv");
@@ -35,191 +30,6 @@ let _aws_sdk_client_sqs = require("@aws-sdk/client-sqs");
35
30
  let pg = require("pg");
36
31
  let yaml = require("yaml");
37
32
  let _geekmidas_cache_postgres = require("@geekmidas/cache/postgres");
38
- //#region src/prompt.ts
39
- /**
40
- * Asking the person at the terminal.
41
- *
42
- * CLI layer only: a command's action may prompt, nothing it calls may. A
43
- * library call (`deploy()`) that needs something it was not given raises a
44
- * named error instead, and the command decides whether to ask.
45
- */
46
- /** A prompt was needed with no terminal to ask at. */
47
- var PromptNeedsTerminal = class extends Error {
48
- question;
49
- instead;
50
- constructor(question, instead) {
51
- super(`Interactive input required. ${instead}`);
52
- this.question = question;
53
- this.instead = instead;
54
- this.name = "PromptNeedsTerminal";
55
- }
56
- };
57
- /** Whether there is a person at a terminal to ask. */
58
- function canPrompt() {
59
- return process.stdin.isTTY === true;
60
- }
61
- /**
62
- * Ask `message` and resolve with the line typed — without echoing it when
63
- * `hidden`. Ctrl+C at a hidden prompt exits, as it would anywhere else in a
64
- * terminal program: raw mode has taken the signal away from the shell.
65
- */
66
- async function prompt(message, options) {
67
- if (!canPrompt()) throw new PromptNeedsTerminal(message, options.instead);
68
- if (options.hidden) {
69
- process.stdout.write(message);
70
- return new Promise((resolve, reject) => {
71
- let value = "";
72
- const cleanup = () => {
73
- process.stdin.setRawMode(false);
74
- process.stdin.pause();
75
- process.stdin.removeListener("data", onData);
76
- process.stdin.removeListener("error", onError);
77
- };
78
- const onError = (err) => {
79
- cleanup();
80
- reject(err);
81
- };
82
- const onData = (char) => {
83
- const c = char.toString();
84
- if (c === "\n" || c === "\r") {
85
- cleanup();
86
- process.stdout.write("\n");
87
- resolve(value);
88
- } else if (c === "") {
89
- cleanup();
90
- process.stdout.write("\n");
91
- process.exit(1);
92
- } else if (c === "" || c === "\b") {
93
- if (value.length > 0) value = value.slice(0, -1);
94
- } else value += c;
95
- };
96
- process.stdin.setRawMode(true);
97
- process.stdin.resume();
98
- process.stdin.on("data", onData);
99
- process.stdin.on("error", onError);
100
- });
101
- }
102
- const rl = node_readline_promises.createInterface({
103
- input: node_process.stdin,
104
- output: node_process.stdout
105
- });
106
- try {
107
- return await rl.question(message);
108
- } finally {
109
- rl.close();
110
- }
111
- }
112
- //#endregion
113
- //#region src/auth/index.ts
114
- const logger$2 = console;
115
- /**
116
- * Validate Dokploy token by making a test API call
117
- */
118
- async function validateDokployToken(endpoint, token) {
119
- const { DokployApi } = await Promise.resolve().then(() => dokploy_api_exports);
120
- return new DokployApi({
121
- baseUrl: endpoint,
122
- token
123
- }).validateToken();
124
- }
125
- /** What answers a login prompt without a terminal. */
126
- const LOGIN_INSTEAD = "Please provide --token option.";
127
- /**
128
- * Login to a service
129
- */
130
- async function loginCommand(options) {
131
- const { provider, token: providedToken, endpoint: providedEndpoint } = options;
132
- if (provider === "dokploy") {
133
- logger$2.log("\n🔐 Logging in to Dokploy...\n");
134
- let endpoint = providedEndpoint;
135
- if (!endpoint) endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ", { instead: LOGIN_INSTEAD });
136
- endpoint = endpoint.replace(/\/$/, "");
137
- try {
138
- new URL(endpoint);
139
- } catch {
140
- logger$2.error("Invalid URL format");
141
- process.exit(1);
142
- }
143
- let token = providedToken;
144
- if (!token) {
145
- logger$2.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
146
- token = await prompt("API Token: ", {
147
- hidden: true,
148
- instead: LOGIN_INSTEAD
149
- });
150
- }
151
- if (!token) {
152
- logger$2.error("Token is required");
153
- process.exit(1);
154
- }
155
- logger$2.log("\nValidating credentials...");
156
- if (!await validateDokployToken(endpoint, token)) {
157
- logger$2.error("\n✗ Invalid credentials. Please check your token and try again.");
158
- process.exit(1);
159
- }
160
- await require_local.storeDokployCredentials(token, endpoint);
161
- logger$2.log("\n✓ Successfully logged in to Dokploy!");
162
- logger$2.log(` Endpoint: ${endpoint}`);
163
- logger$2.log(` Credentials stored in: ${require_local.getCredentialsPath()}`);
164
- logger$2.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
165
- }
166
- if (provider === "hostinger") {
167
- logger$2.log("\n🔐 Logging in to Hostinger...\n");
168
- let token = providedToken;
169
- if (!token) {
170
- logger$2.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
171
- token = await prompt("API Token: ", {
172
- hidden: true,
173
- instead: LOGIN_INSTEAD
174
- });
175
- }
176
- if (!token) {
177
- logger$2.error("Token is required");
178
- process.exit(1);
179
- }
180
- await require_local.storeHostingerToken(token);
181
- logger$2.log("\n✓ Hostinger token stored.");
182
- logger$2.log(` Credentials stored in: ${require_local.getCredentialsPath()}`);
183
- logger$2.log(" Not validated here: every DNS endpoint is scoped to a domain, so the\n first deploy that touches DNS is where a bad token reports itself.");
184
- logger$2.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
185
- }
186
- }
187
- /**
188
- * Logout from a service
189
- */
190
- async function logoutCommand(options) {
191
- const { provider = "dokploy" } = options;
192
- if (provider === "all") {
193
- if (await require_local.removeDokployCredentials()) logger$2.log("\n✓ Logged out from all services");
194
- else logger$2.log("\nNo stored credentials found");
195
- return;
196
- }
197
- if (provider === "dokploy") {
198
- if (await require_local.removeDokployCredentials()) logger$2.log("\n✓ Logged out from Dokploy");
199
- else logger$2.log("\nNo Dokploy credentials found");
200
- }
201
- }
202
- /**
203
- * Show current login status
204
- */
205
- async function whoamiCommand() {
206
- logger$2.log("\n📋 Current credentials:\n");
207
- const dokploy = await require_local.getDokployCredentials();
208
- if (dokploy) {
209
- logger$2.log(" Dokploy:");
210
- logger$2.log(` Endpoint: ${dokploy.endpoint}`);
211
- logger$2.log(` Token: ${maskToken(dokploy.token)}`);
212
- } else logger$2.log(" Dokploy: Not logged in");
213
- logger$2.log(`\n Credentials file: ${require_local.getCredentialsPath()}`);
214
- }
215
- /**
216
- * Mask a token for display
217
- */
218
- function maskToken(token) {
219
- if (token.length <= 8) return "****";
220
- return `${token.slice(0, 4)}...${token.slice(-4)}`;
221
- }
222
- //#endregion
223
33
  //#region src/credentials/index.ts
224
34
  const logger$1 = console;
225
35
  /**
@@ -456,17 +266,6 @@ async function prepareEntryCredentials(options) {
456
266
  };
457
267
  }
458
268
  //#endregion
459
- //#region src/workspace/client-generator.ts
460
- /**
461
- * Normalize routes to an array of patterns.
462
- * Handles a glob or a list of them.
463
- * @internal Exported for use in dev command
464
- */
465
- function normalizeRoutes(routes) {
466
- if (!routes) return [];
467
- return Array.isArray(routes) ? routes : [routes];
468
- }
469
- //#endregion
470
269
  //#region src/reconcile/containers.ts
471
270
  /**
472
271
  * What gkm knows about each container it can derive.
@@ -674,7 +473,7 @@ function volumeOf(container) {
674
473
  */
675
474
  function providerOf(workspace) {
676
475
  const target = workspace.deploy?.default;
677
- return target ? require_runtime.runtimeOf(target, workspace) : "aws";
476
+ return target ? require_resolve.runtimeOf(target, workspace) : "aws";
678
477
  }
679
478
  /**
680
479
  * The cache backend a target implies.
@@ -3361,883 +3160,637 @@ function localAddress(port) {
3361
3160
  return `http://localhost:${port}`;
3362
3161
  }
3363
3162
  //#endregion
3364
- //#region src/secrets/encryption.ts
3365
- var encryption_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
3366
- encryptSecrets: () => encryptSecrets,
3367
- generateDefineOptions: () => generateDefineOptions,
3368
- keyFingerprint: () => keyFingerprint
3369
- });
3370
- /** AES-256-GCM configuration */
3371
- const ALGORITHM = "aes-256-gcm";
3372
- const KEY_LENGTH = 32;
3373
- const IV_LENGTH = 12;
3374
- /**
3375
- * Encrypt secrets for embedding in a bundle.
3376
- * Uses AES-256-GCM with a randomly generated ephemeral key.
3377
- *
3378
- * @param secrets - Key-value pairs to encrypt
3379
- * @returns Encrypted payload with ephemeral master key
3380
- */
3381
- function encryptSecrets(secrets) {
3382
- const masterKey = (0, node_crypto.randomBytes)(KEY_LENGTH);
3383
- const iv = (0, node_crypto.randomBytes)(IV_LENGTH);
3384
- const plaintext = JSON.stringify(secrets);
3385
- const cipher = (0, node_crypto.createCipheriv)(ALGORITHM, masterKey, iv);
3386
- const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
3387
- const authTag = cipher.getAuthTag();
3388
- return {
3389
- encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
3390
- iv: iv.toString("hex"),
3391
- masterKey: masterKey.toString("hex")
3392
- };
3393
- }
3163
+ //#region src/deploy/domain.ts
3164
+ /** A stage was deployed that `deploy.domains` gives no domain. */
3165
+ var NoDomainForStage = class extends Error {
3166
+ stage;
3167
+ constructor(stage) {
3168
+ super(`No domain for stage "${stage}". Add it to gkm.config.ts: deploy: { domains: { ${stage}: 'example.com' } }.`);
3169
+ this.stage = stage;
3170
+ this.name = "NoDomainForStage";
3171
+ }
3172
+ };
3394
3173
  /**
3395
- * Generate the define options for tsdown/esbuild.
3396
- * These will be injected at build time.
3174
+ * The hostname an app answers on for a stage.
3175
+ *
3176
+ * 1. An explicit `app.domain` — a string, or one per stage — wins.
3177
+ * 2. Otherwise from the stage's base domain in `deploy.domains`: the root site
3178
+ * answers on the domain itself, and every other app on
3179
+ * `{subdomain}.{domain}` — its declaration's `subdomain`, or its own name.
3180
+ *
3181
+ * @throws {NoDomainForStage} when `deploy.domains` names no domain for it
3397
3182
  */
3398
- function generateDefineOptions(payload) {
3399
- return {
3400
- __GKM_ENCRYPTED_CREDENTIALS__: JSON.stringify(payload.encrypted),
3401
- __GKM_CREDENTIALS_IV__: JSON.stringify(payload.iv)
3402
- };
3183
+ function resolveHost(appName, app, stage, domains, isMainFrontend) {
3184
+ if (app.domain) {
3185
+ if (typeof app.domain === "string") return app.domain;
3186
+ if (app.domain[stage]) return app.domain[stage];
3187
+ }
3188
+ const baseDomain = domains?.[stage];
3189
+ if (!baseDomain) throw new NoDomainForStage(stage);
3190
+ if (isMainFrontend) return baseDomain;
3191
+ return `${app.subdomain ?? appName}.${baseDomain}`;
3403
3192
  }
3404
3193
  /**
3405
- * A short, one-way name for a master key: the first 8 hex characters of its
3406
- * SHA-256.
3194
+ * Whether this app is the site the base domain points at — see `rootSite`,
3195
+ * which the local edge asks too.
3407
3196
  *
3408
- * Printed where the key itself used to be, so a person can tell which key a
3409
- * build used — and match it against the one they hold — without the output
3410
- * carrying a value that decrypts every secret of the stage.
3197
+ * @throws {AmbiguousRootSite} when several sites could be the root and none says it is
3411
3198
  */
3412
- function keyFingerprint(masterKey) {
3413
- return (0, node_crypto.createHash)("sha256").update(masterKey).digest("hex").slice(0, 8);
3199
+ function isMainFrontendApp(appName, app, allApps) {
3200
+ if (app.type !== "web") return false;
3201
+ return rootSite(Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
3202
+ name,
3203
+ root: a.root
3204
+ }))) === appName;
3414
3205
  }
3415
- //#endregion
3416
- //#region src/deploy/generated.ts
3417
3206
  /**
3418
- * The values a deployed stage generates once and keeps: what nothing can
3419
- * provision and nobody else issues.
3420
- *
3421
- * - **A seed**, which salts every password a deploy derives — a database's
3422
- * master and roles, a bucket's root user. Derived from repo facts alone
3423
- * (project, stage, construct id), those passwords were computable by anyone
3424
- * who could read the repo.
3425
- * - **Each `secret` construct's value** — an auth server's signing secret —
3426
- * random, rather than derived.
3427
- * - **Each `encryption` construct's keyring** — one random key and an index
3428
- * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.
3207
+ * Get public URL arg names from app dependencies.
3429
3208
  *
3430
- * Both are generated on the first deploy that needs them and written to the
3431
- * stage's store, so every later deploy reads the same values: sessions survive
3432
- * a redeploy, and a role's password does not move. A value set by hand with
3433
- * `gkm secrets:set` is kept, never replaced.
3434
- */
3435
- /**
3436
- * The stage's secrets with its seed and every declared `secret`'s value
3437
- * present, generating what is missing. Pure: the caller writes the result
3438
- * back to the stage's store when `generated` is not empty.
3209
+ * @param app - The web/mobile app configuration
3210
+ * @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks
3211
+ * without a public prefix (e.g. Remix).
3439
3212
  */
3440
- function withGeneratedSecrets(secrets, manifest) {
3441
- const generated = [];
3442
- const custom = { ...secrets.custom };
3443
- for (const declaration of Object.values(manifest)) {
3444
- if (declaration?.kind !== "secret" && declaration?.kind !== "encryption") continue;
3445
- const key = declaration.provides?.[0];
3446
- if (!key || custom[key]) continue;
3447
- custom[key] = declaration.kind === "encryption" ? (0, _geekmidas_constructs_encryption.generateKeyring)() : random();
3448
- generated.push(key);
3449
- }
3450
- const seed = secrets.seed ?? random();
3451
- if (!secrets.seed) generated.push("seed");
3452
- return {
3453
- secrets: generated.length ? {
3454
- ...secrets,
3455
- seed,
3456
- custom,
3457
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3458
- } : secrets,
3459
- generated
3460
- };
3461
- }
3462
- /** 256 bits, URL-safe. */
3463
- function random() {
3464
- return (0, node_crypto.randomBytes)(32).toString("base64url");
3213
+ function getPublicUrlArgNames(app) {
3214
+ const prefix = require_workspace.getPublicEnvPrefix(app.framework);
3215
+ if (!prefix) return [];
3216
+ return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
3465
3217
  }
3466
3218
  //#endregion
3467
- //#region src/deploy/StateProvider.ts
3219
+ //#region src/docker/imageRef.ts
3468
3220
  /**
3469
- * Check if value is a StateProvider implementation.
3221
+ * An image reference, checked before it reaches `docker`.
3222
+ *
3223
+ * The parts come from the workspace — a registry in `gkm.config.ts`, an image
3224
+ * name from the project, a tag from `--tag` — and an argument array already
3225
+ * keeps them out of a shell. What it cannot do is stop `docker` reading a value
3226
+ * that starts with `-` as one of its own flags, so a ref is held to Docker's
3227
+ * own grammar (distribution/reference) and anything else is refused by name
3228
+ * before a build starts.
3470
3229
  */
3471
- function isStateProvider(value) {
3472
- return typeof value === "object" && value !== null && typeof value.read === "function" && typeof value.write === "function";
3230
+ const DOMAIN_COMPONENT = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
3231
+ const DOMAIN = `${DOMAIN_COMPONENT}(?:\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;
3232
+ const PATH_COMPONENT = "[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*";
3233
+ const REFERENCE = new RegExp(`^(?:${DOMAIN}/)?${PATH_COMPONENT}(?:/${PATH_COMPONENT})*(?::[\\w][\\w.-]{0,127})?(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,})?$`);
3234
+ /** Docker refuses a repository name longer than this. */
3235
+ const MAX_NAME_LENGTH = 255;
3236
+ /** A value that is not an image reference was about to be built or pushed. */
3237
+ var ImageRefInvalid = class extends Error {
3238
+ ref;
3239
+ constructor(ref) {
3240
+ super(`'${ref}' is not a valid image reference. An image is \`[registry/]name[:tag]\`: the name lowercase letters, digits and . _ - separators, the tag up to 128 of letters, digits, _ . and -, not starting with . or -. Check docker.registry in gkm.config.ts and the --tag passed.`);
3241
+ this.ref = ref;
3242
+ this.name = "ImageRefInvalid";
3243
+ }
3244
+ };
3245
+ /** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */
3246
+ function validateImageRef(ref) {
3247
+ const name = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
3248
+ if (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) throw new ImageRefInvalid(ref);
3249
+ return ref;
3473
3250
  }
3474
3251
  //#endregion
3475
- //#region src/deploy/StateStore.ts
3252
+ //#region src/docker/templates.ts
3253
+ const LOCKFILES = [
3254
+ ["pnpm-lock.yaml", "pnpm"],
3255
+ ["bun.lockb", "bun"],
3256
+ ["yarn.lock", "yarn"],
3257
+ ["package-lock.json", "npm"]
3258
+ ];
3476
3259
  /**
3477
- * Deploy state store.
3478
- *
3479
- * `StateProvider` reads and writes a whole stage at once, with nothing to stop
3480
- * two runs of the same stage from overwriting each other and nowhere to record
3481
- * a resource the moment it exists. A `StateStore` adds the three things a
3482
- * deploy needs to be safe against crashes and concurrent runs:
3483
- *
3484
- * - `lock(stage)`: one run per stage. A second gets `StateLocked`, naming the
3485
- * holder, instead of racing the first.
3486
- * - Versioned `read` and conditional `write`: a write names the version it was
3487
- * based on, and raises `StateVersionConflict` when the stored state moved on
3488
- * underneath it, so nothing is lost silently even without the lock.
3489
- * - Per-resource records: `putResource` journals one resource at a time, so a
3490
- * run that dies half way leaves behind the ids of everything it created.
3491
- *
3492
- * Built-in stores: `LocalStateStore`, `SSMStateStore`, `S3StateStore`. A
3493
- * custom `StateProvider` is wrapped by `LegacyStateStore`, which keeps it
3494
- * working but cannot lock.
3260
+ * Detect package manager from lockfiles
3261
+ * Walks up the directory tree to find lockfile (for monorepos)
3495
3262
  */
3496
- var StateLocked = class extends Error {
3497
- stage;
3498
- holder;
3499
- location;
3500
- constructor(stage, holder, location) {
3501
- const by = holder ? `${holder.owner}@${holder.host} (pid ${holder.pid}) since ${holder.acquiredAt}${holder.operation ? ` for '${holder.operation}'` : ""}` : "a run that did not record who it was";
3502
- super(`Deploy state for stage '${stage}' is locked by ${by} (${location}). Wait for that run to finish; if it crashed, release the lock with \`gkm state:unlock --stage ${stage}\`.`);
3503
- this.stage = stage;
3504
- this.holder = holder;
3505
- this.location = location;
3506
- this.name = "StateLocked";
3507
- }
3508
- };
3509
- var StateVersionConflict = class extends Error {
3510
- stage;
3511
- expectedVersion;
3512
- actualVersion;
3513
- constructor(stage, expectedVersion, actualVersion) {
3514
- super(`Deploy state for stage '${stage}' changed since it was read (expected ${expectedVersion ? `version ${expectedVersion}` : "no state"}, found ${actualVersion ? `version ${actualVersion}` : "no state"}). Another run wrote it: read the state again and re-apply the change, and hold the stage's lock so runs take turns.`);
3515
- this.stage = stage;
3516
- this.expectedVersion = expectedVersion;
3517
- this.actualVersion = actualVersion;
3518
- this.name = "StateVersionConflict";
3519
- }
3520
- };
3521
- var StageStateMissing = class extends Error {
3522
- stage;
3523
- constructor(stage) {
3524
- super(`There is no deploy state for stage '${stage}' to record a resource in. Write the stage's state first (write with expectedVersion: null).`);
3525
- this.stage = stage;
3526
- this.name = "StageStateMissing";
3527
- }
3528
- };
3529
- var StateUnreadable = class extends Error {
3530
- stage;
3531
- location;
3532
- constructor(stage, location, cause) {
3533
- super(`Deploy state for stage '${stage}' at ${location} is not valid JSON. Restore it from a backup (or the provider's history) rather than deploying without it — a deploy without state recreates every resource.`, { cause });
3534
- this.stage = stage;
3535
- this.location = location;
3536
- this.name = "StateUnreadable";
3537
- }
3538
- };
3539
- var StateSchemaTooNew = class extends Error {
3540
- stage;
3541
- schemaVersion;
3542
- constructor(stage, schemaVersion) {
3543
- super(`Deploy state for stage '${stage}' has schema version ${String(schemaVersion)}, newer than this @geekmidas/cli understands (2). Upgrade @geekmidas/cli to the version that wrote it.`);
3544
- this.stage = stage;
3545
- this.schemaVersion = schemaVersion;
3546
- this.name = "StateSchemaTooNew";
3263
+ function detectPackageManager(cwd = process.cwd()) {
3264
+ let dir = cwd;
3265
+ const root = (0, node_path.parse)(dir).root;
3266
+ while (dir !== root) {
3267
+ for (const [lockfile, pm] of LOCKFILES) if ((0, node_fs.existsSync)((0, node_path.join)(dir, lockfile))) return pm;
3268
+ dir = (0, node_path.dirname)(dir);
3547
3269
  }
3548
- };
3270
+ for (const [lockfile, pm] of LOCKFILES) if ((0, node_fs.existsSync)((0, node_path.join)(root, lockfile))) return pm;
3271
+ return "pnpm";
3272
+ }
3549
3273
  /**
3550
- * Not thrown: emitted (once per store) when a custom `StateProvider` stands
3551
- * in for a store, so a deploy that cannot be protected from a concurrent one
3552
- * says so.
3274
+ * Find the lockfile path by walking up the directory tree
3275
+ * Returns the full path to the lockfile, or null if not found
3553
3276
  */
3554
- var StateStoreWithoutLocking = class extends Error {
3555
- provider;
3556
- constructor(provider) {
3557
- super(`The custom state provider '${provider}' has no lock and no versions, so two deploys of the same stage can still overwrite each other. Implement StateStore (lock, versioned read, conditional write) or use the 'local', 'ssm' or 's3' state provider.`);
3558
- this.provider = provider;
3559
- this.name = "StateStoreWithoutLocking";
3560
- }
3561
- };
3562
- var StateStoreNeedsWorkspaceName = class extends Error {
3563
- provider;
3564
- constructor(provider) {
3565
- super(`The '${provider}' state provider keys state by workspace name. Set "name" in gkm.config.ts.`);
3566
- this.provider = provider;
3567
- this.name = "StateStoreNeedsWorkspaceName";
3568
- }
3569
- };
3570
- var UnknownStateProvider = class extends Error {
3571
- config;
3572
- constructor(config) {
3573
- super(`Unknown state provider ${JSON.stringify(config)}. Use 'local', 'ssm', 's3', or an object implementing StateStore or StateProvider.`);
3574
- this.config = config;
3575
- this.name = "UnknownStateProvider";
3576
- }
3577
- };
3578
- /** A lock or write that could not get at the state backend in time. */
3579
- var StateStoreBusy = class extends Error {
3580
- stage;
3581
- location;
3582
- constructor(stage, location) {
3583
- super(`Timed out waiting to write deploy state for stage '${stage}' (${location}). If no other gkm process is running, remove ${location}.`);
3584
- this.stage = stage;
3585
- this.location = location;
3586
- this.name = "StateStoreBusy";
3277
+ function findLockfilePath(cwd = process.cwd()) {
3278
+ let dir = cwd;
3279
+ const root = (0, node_path.parse)(dir).root;
3280
+ while (dir !== root) {
3281
+ for (const [lockfile] of LOCKFILES) {
3282
+ const lockfilePath = (0, node_path.join)(dir, lockfile);
3283
+ if ((0, node_fs.existsSync)(lockfilePath)) return lockfilePath;
3284
+ }
3285
+ dir = (0, node_path.dirname)(dir);
3587
3286
  }
3588
- };
3589
- function decodeDocument(stage, body, location) {
3590
- let parsed;
3591
- try {
3592
- parsed = JSON.parse(body);
3593
- } catch (error) {
3594
- throw new StateUnreadable(stage, location, error);
3287
+ for (const [lockfile] of LOCKFILES) {
3288
+ const lockfilePath = (0, node_path.join)(root, lockfile);
3289
+ if ((0, node_fs.existsSync)(lockfilePath)) return lockfilePath;
3595
3290
  }
3596
- if (typeof parsed !== "object" || parsed === null) throw new StateUnreadable(stage, location, parsed);
3597
- const schemaVersion = parsed.schemaVersion;
3598
- if (schemaVersion === void 0) return {
3599
- schemaVersion: 1,
3600
- state: parsed
3601
- };
3602
- if (schemaVersion === 2) return {
3603
- schemaVersion: 2,
3604
- document: parsed
3605
- };
3606
- throw new StateSchemaTooNew(stage, schemaVersion);
3607
- }
3608
- function encodeDocument(document) {
3609
- return JSON.stringify(document, null, 2);
3291
+ return null;
3610
3292
  }
3611
3293
  /**
3612
- * v1 state as a v2 document. The ids v1 kept are seeded as `ready` records,
3613
- * so the first journaled deploy after an upgrade adopts them rather than
3614
- * treating them as unknown.
3294
+ * Check if we're in a monorepo (lockfile is in a parent directory)
3615
3295
  */
3616
- function migrateV1(stage, state) {
3617
- const now = (/* @__PURE__ */ new Date()).toISOString();
3618
- const resources = {};
3619
- const seed = (type, name, id) => {
3620
- if (!id) return;
3621
- const key = name ? `${type}:${name}` : type;
3622
- resources[key] = {
3623
- key,
3624
- type,
3625
- id,
3626
- status: "ready",
3627
- updatedAt: now
3628
- };
3629
- };
3630
- seed("project", null, state.projectId);
3631
- seed("environment", null, state.environmentId);
3632
- for (const [app, id] of Object.entries(state.applications ?? {})) seed("application", app, id);
3633
- seed("postgres", null, state.services?.postgresId);
3634
- seed("redis", null, state.services?.redisId);
3635
- seed("backup-destination", null, state.backups?.destinationId);
3636
- return {
3637
- schemaVersion: 2,
3638
- stage,
3639
- serial: 1,
3640
- state,
3641
- resources,
3642
- updatedAt: now
3643
- };
3644
- }
3645
- function newLockHolder(options) {
3646
- let owner = "unknown";
3647
- try {
3648
- owner = (0, node_os.userInfo)().username;
3649
- } catch {}
3650
- return {
3651
- id: (0, node_crypto.randomUUID)(),
3652
- owner,
3653
- host: (0, node_os.hostname)(),
3654
- pid: process.pid,
3655
- acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
3656
- operation: options?.operation
3657
- };
3658
- }
3659
- function contentVersion(body) {
3660
- return (0, node_crypto.createHash)("sha256").update(body).digest("hex").slice(0, 16);
3296
+ function isMonorepo(cwd = process.cwd()) {
3297
+ const lockfilePath = findLockfilePath(cwd);
3298
+ if (!lockfilePath) return false;
3299
+ return (0, node_path.dirname)(lockfilePath) !== cwd;
3661
3300
  }
3662
3301
  /**
3663
- * A store over a backend that can read and conditionally write one blob per
3664
- * stage, and create a lock object only if it does not exist. Migration and
3665
- * resource records are the same for every such backend, so they live here.
3302
+ * Check if turbo.json exists (walks up directory tree)
3666
3303
  */
3667
- var DocumentStateStore = class {
3668
- async lock(stage, options) {
3669
- const holder = newLockHolder(options);
3670
- await this.createLock(stage, holder);
3671
- let released = false;
3672
- return {
3673
- stage,
3674
- holder,
3675
- release: async () => {
3676
- if (released) return;
3677
- released = true;
3678
- await this.removeLock(stage, holder);
3679
- }
3680
- };
3681
- }
3682
- async forceUnlock(stage) {
3683
- const holder = await this.readLock(stage);
3684
- await this.removeLock(stage, null);
3685
- return holder;
3686
- }
3687
- async read(stage) {
3688
- const current = await this.readDocument(stage);
3689
- if (!current) return null;
3690
- return {
3691
- state: current.document.state,
3692
- resources: current.document.resources,
3693
- version: current.version
3694
- };
3695
- }
3696
- async write(stage, state, options) {
3697
- if (options.expectedVersion === null) {
3698
- const document = {
3699
- schemaVersion: 2,
3700
- stage,
3701
- serial: 1,
3702
- state,
3703
- resources: {},
3704
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3705
- };
3706
- return this.writeRaw(stage, encodeDocument(document), null);
3707
- }
3708
- return this.mutate(stage, options.expectedVersion, (document) => ({
3709
- ...document,
3710
- state
3711
- }));
3712
- }
3713
- async putResource(stage, record, options) {
3714
- return this.mutate(stage, options?.expectedVersion, (document) => ({
3715
- ...document,
3716
- resources: {
3717
- ...document.resources,
3718
- [record.key]: {
3719
- ...record,
3720
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3721
- }
3722
- }
3723
- }));
3724
- }
3725
- async deleteResource(stage, key, options) {
3726
- return this.mutate(stage, options?.expectedVersion, (document) => {
3727
- const { [key]: _removed, ...resources } = document.resources;
3728
- return {
3729
- ...document,
3730
- resources
3731
- };
3732
- });
3733
- }
3734
- async mutate(stage, expectedVersion, change) {
3735
- const current = await this.readDocument(stage);
3736
- if (!current) {
3737
- if (expectedVersion !== void 0) throw new StateVersionConflict(stage, expectedVersion, null);
3738
- throw new StageStateMissing(stage);
3739
- }
3740
- if (expectedVersion !== void 0 && current.version !== expectedVersion) throw new StateVersionConflict(stage, expectedVersion, current.version);
3741
- const next = change(current.document);
3742
- next.serial = current.document.serial + 1;
3743
- next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3744
- return this.writeRaw(stage, encodeDocument(next), current.version);
3745
- }
3746
- /**
3747
- * Reads the stage's document, migrating v1 in place on the way. The
3748
- * migration is itself a conditional write: if another run migrated (or
3749
- * wrote) first, its result is read instead.
3750
- */
3751
- async readDocument(stage, attempt = 0) {
3752
- const raw = await this.readRaw(stage);
3753
- if (!raw) return null;
3754
- const decoded = decodeDocument(stage, raw.body, this.location(stage));
3755
- if (decoded.schemaVersion === 2) return {
3756
- document: decoded.document,
3757
- version: raw.version
3758
- };
3759
- await this.writeV1Backup(stage, raw.body);
3760
- const document = migrateV1(stage, decoded.state);
3761
- try {
3762
- return {
3763
- document,
3764
- version: await this.writeRaw(stage, encodeDocument(document), raw.version)
3765
- };
3766
- } catch (error) {
3767
- if (error instanceof StateVersionConflict && attempt < 2) return this.readDocument(stage, attempt + 1);
3768
- throw error;
3769
- }
3770
- }
3771
- };
3772
- function isStateStore(value) {
3773
- return typeof value === "object" && value !== null && typeof value.lock === "function" && typeof value.read === "function" && typeof value.write === "function" && typeof value.putResource === "function";
3774
- }
3775
- /**
3776
- * The store for a workspace's `state` config:
3777
- *
3778
- * - none / `'local'`: `LocalStateStore` under `.gkm/`
3779
- * - `'ssm'`: `SSMStateStore`, with no local cache — SSM is read directly, so
3780
- * a stale local copy can never win over it
3781
- * - `'s3'`: `S3StateStore`
3782
- * - a `StateStore` object: used as is
3783
- * - a `StateProvider` object: wrapped by `LegacyStateStore`
3784
- */
3785
- async function createStateStore(options) {
3786
- const { config, workspaceRoot, workspaceName } = options;
3787
- if (!config || config.provider === "local") {
3788
- const { LocalStateStore } = await Promise.resolve().then(() => require("./LocalStateStore-DiJqKyPy.cjs")).then((n) => n.LocalStateStore_exports);
3789
- return new LocalStateStore(workspaceRoot);
3790
- }
3791
- if (isStateStore(config.provider)) return config.provider;
3792
- if (isStateProvider(config.provider)) {
3793
- const { LegacyStateStore } = await Promise.resolve().then(() => require("./LegacyStateStore-C2abV49N.cjs"));
3794
- return new LegacyStateStore(config.provider, { warn: options.warn });
3795
- }
3796
- if (config.provider === "ssm") {
3797
- if (!workspaceName) throw new StateStoreNeedsWorkspaceName("ssm");
3798
- const { SSMStateStore } = await Promise.resolve().then(() => require("./SSMStateStore-CLipvx47.cjs"));
3799
- const ssm = config;
3800
- return SSMStateStore.create({
3801
- workspaceName,
3802
- region: ssm.region,
3803
- profile: ssm.profile
3804
- });
3805
- }
3806
- if (config.provider === "s3") {
3807
- if (!workspaceName) throw new StateStoreNeedsWorkspaceName("s3");
3808
- const { S3StateStore } = await Promise.resolve().then(() => require("./S3StateStore-Ds0V1Lgm.cjs"));
3809
- const s3 = config;
3810
- return S3StateStore.create({
3811
- workspaceName,
3812
- bucket: s3.bucket,
3813
- prefix: s3.prefix,
3814
- region: s3.region,
3815
- profile: s3.profile
3816
- });
3304
+ function hasTurboConfig(cwd = process.cwd()) {
3305
+ let dir = cwd;
3306
+ const root = (0, node_path.parse)(dir).root;
3307
+ while (dir !== root) {
3308
+ if ((0, node_fs.existsSync)((0, node_path.join)(dir, "turbo.json"))) return true;
3309
+ dir = (0, node_path.dirname)(dir);
3817
3310
  }
3818
- throw new UnknownStateProvider(config);
3311
+ return (0, node_fs.existsSync)((0, node_path.join)(root, "turbo.json"));
3819
3312
  }
3820
- //#endregion
3821
- //#region src/deploy/state.ts
3822
- /**
3823
- * Deploy state management for Dokploy deployments
3824
- *
3825
- * Stores resource IDs (applications, services) per stage to avoid
3826
- * re-creating resources on subsequent deploys.
3827
- */
3828
3313
  /**
3829
- * Create a new empty state for a stage
3314
+ * Get install command for turbo builds (without frozen lockfile)
3315
+ * Turbo prune creates a subset that may not perfectly match the lockfile
3830
3316
  */
3831
- function createEmptyState(stage, projectId, environmentId) {
3317
+ function getTurboInstallCmd(pm) {
3832
3318
  return {
3833
- provider: "dokploy",
3834
- stage,
3835
- projectId,
3836
- environmentId,
3837
- applications: {},
3838
- services: {},
3839
- lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
3840
- };
3841
- }
3842
- /**
3843
- * Set application ID in state (mutates state)
3844
- */
3845
- function setApplicationId(state, appName, applicationId) {
3846
- state.applications[appName] = applicationId;
3847
- }
3848
- /**
3849
- * Record the image an app was deployed with (mutates state)
3850
- */
3851
- function setDeployedImage(state, appName, image) {
3852
- state.images = {
3853
- ...state.images,
3854
- [appName]: image
3855
- };
3856
- }
3857
- /**
3858
- * Get a generated secret for an app
3859
- */
3860
- function getGeneratedSecret(state, appName, secretName) {
3861
- return state?.generatedSecrets?.[appName]?.[secretName];
3862
- }
3863
- /**
3864
- * Set a generated secret for an app (mutates state)
3865
- */
3866
- function setGeneratedSecret(state, appName, secretName, value) {
3867
- if (!state.generatedSecrets) state.generatedSecrets = {};
3868
- if (!state.generatedSecrets[appName]) state.generatedSecrets[appName] = {};
3869
- state.generatedSecrets[appName][secretName] = value;
3319
+ pnpm: "pnpm install",
3320
+ npm: "npm install",
3321
+ yarn: "yarn install",
3322
+ bun: "bun install"
3323
+ }[pm];
3870
3324
  }
3871
3325
  /**
3872
- * Set DNS verification record for a hostname (mutates state)
3326
+ * Get package manager specific commands and paths
3873
3327
  */
3874
- function setDnsVerification(state, hostname, serverIp) {
3875
- if (!state.dnsVerified) state.dnsVerified = {};
3876
- state.dnsVerified[hostname] = {
3877
- serverIp,
3878
- verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
3879
- };
3328
+ function getPmConfig(pm) {
3329
+ return {
3330
+ pnpm: {
3331
+ install: "corepack enable && corepack prepare pnpm@latest --activate",
3332
+ lockfile: "pnpm-lock.yaml",
3333
+ fetch: "pnpm fetch",
3334
+ installCmd: "pnpm install --frozen-lockfile --offline",
3335
+ cacheTarget: "/root/.local/share/pnpm/store",
3336
+ cacheId: "pnpm",
3337
+ run: "pnpm",
3338
+ exec: "pnpm exec",
3339
+ dlx: "pnpm dlx",
3340
+ addGlobal: "pnpm add -g"
3341
+ },
3342
+ npm: {
3343
+ install: "",
3344
+ lockfile: "package-lock.json",
3345
+ fetch: "",
3346
+ installCmd: "npm ci",
3347
+ cacheTarget: "/root/.npm",
3348
+ cacheId: "npm",
3349
+ run: "npm run",
3350
+ exec: "npx",
3351
+ dlx: "npx",
3352
+ addGlobal: "npm install -g"
3353
+ },
3354
+ yarn: {
3355
+ install: "corepack enable && corepack prepare yarn@stable --activate",
3356
+ lockfile: "yarn.lock",
3357
+ fetch: "",
3358
+ installCmd: "yarn install --frozen-lockfile",
3359
+ cacheTarget: "/root/.yarn/cache",
3360
+ cacheId: "yarn",
3361
+ run: "yarn",
3362
+ exec: "yarn exec",
3363
+ dlx: "yarn dlx",
3364
+ addGlobal: "yarn global add"
3365
+ },
3366
+ bun: {
3367
+ install: "npm install -g bun",
3368
+ lockfile: "bun.lockb",
3369
+ fetch: "",
3370
+ installCmd: "bun install --frozen-lockfile",
3371
+ cacheTarget: "/root/.bun/install/cache",
3372
+ cacheId: "bun",
3373
+ run: "bun run",
3374
+ exec: "bunx",
3375
+ dlx: "bunx",
3376
+ addGlobal: "bun add -g"
3377
+ }
3378
+ }[pm];
3880
3379
  }
3881
3380
  /**
3882
- * Check if a hostname is already verified with the given IP
3381
+ * Generate a multi-stage Dockerfile for building from source
3382
+ * Optimized for build speed with:
3383
+ * - BuildKit cache mounts for package manager store
3384
+ * - pnpm fetch for better layer caching (when using pnpm)
3385
+ * - Optional turbo prune for monorepos
3883
3386
  */
3884
- function isDnsVerified(state, hostname, serverIp) {
3885
- return (state?.dnsVerified?.[hostname])?.serverIp === serverIp;
3387
+ function generateMultiStageDockerfile(options) {
3388
+ const { baseImage, port, healthCheckPath, turbo, turboPackage, packageManager } = options;
3389
+ if (turbo) return generateTurboDockerfile({
3390
+ ...options,
3391
+ turboPackage: turboPackage ?? "api"
3392
+ });
3393
+ const pm = getPmConfig(packageManager);
3394
+ return `# syntax=docker/dockerfile:1
3395
+ # Stage 1: Dependencies
3396
+ FROM ${baseImage} AS deps
3397
+
3398
+ WORKDIR /app
3399
+ ${pm.install ? `\n# Install ${packageManager}\nRUN ${pm.install}\n` : ""}
3400
+ ${packageManager === "pnpm" ? `# Copy lockfile first for better caching
3401
+ COPY ${pm.lockfile} ./
3402
+
3403
+ # Fetch dependencies (downloads to virtual store, cached separately)
3404
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
3405
+ ${pm.fetch}
3406
+
3407
+ # Copy package.json after fetch
3408
+ COPY package.json ./
3409
+
3410
+ # Install from cache (fast - no network needed)
3411
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
3412
+ ${pm.installCmd}` : `# Copy package files
3413
+ COPY package.json ${pm.lockfile} ./
3414
+
3415
+ # Install dependencies with cache
3416
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
3417
+ ${pm.installCmd}`}
3418
+
3419
+ # Stage 2: Build
3420
+ FROM deps AS builder
3421
+
3422
+ WORKDIR /app
3423
+
3424
+ # Copy source (deps already installed)
3425
+ COPY . .
3426
+
3427
+ # Build any workspace packages the app depends on.
3428
+ #
3429
+ # A no-op for a project whose dependencies come from a registry, and required
3430
+ # for one whose CLI is a sibling package: \`turbo prune\` honours .gitignore, so a
3431
+ # workspace package's \`dist\` never reaches the image and its bin has nothing to
3432
+ # import. \`--if-present\` is what makes the same Dockerfile correct for both.
3433
+ RUN ${pm.run} --if-present build
3434
+
3435
+ # Build production server using gkm
3436
+ RUN ${pm.exec} gkm build --provider server --production
3437
+
3438
+ # Stage 3: Production
3439
+ FROM ${baseImage} AS runner
3440
+
3441
+ WORKDIR /app
3442
+
3443
+ # Install tini for proper signal handling as PID 1
3444
+ RUN apk add --no-cache tini
3445
+
3446
+ # Create non-root user
3447
+ RUN addgroup --system --gid 1001 nodejs && \\
3448
+ adduser --system --uid 1001 hono
3449
+
3450
+ # Copy bundled server
3451
+ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
3452
+
3453
+ # Environment
3454
+ ENV NODE_ENV=production
3455
+ ENV PORT=${port}
3456
+
3457
+ # Health check
3458
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
3459
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3460
+
3461
+ # Switch to non-root user
3462
+ USER hono
3463
+
3464
+ EXPOSE ${port}
3465
+
3466
+ # Use tini as entrypoint to handle PID 1 responsibilities
3467
+ ENTRYPOINT ["/sbin/tini", "--"]
3468
+ CMD ["node", "server.mjs"]
3469
+ `;
3886
3470
  }
3887
3471
  /**
3888
- * Get the key for a DNS record in state
3472
+ * Generate a Dockerfile optimized for Turbo monorepos
3473
+ * Uses turbo prune to create minimal Docker context
3889
3474
  */
3890
- function getDnsRecordKey(name, type) {
3891
- return `${name}:${type}`;
3892
- }
3893
- /**
3894
- * Set a created DNS record in state (mutates state)
3895
- */
3896
- function setDnsRecord(state, record) {
3897
- if (!state.dnsRecords) state.dnsRecords = {};
3898
- const key = getDnsRecordKey(record.name, record.type);
3899
- state.dnsRecords[key] = {
3900
- ...record,
3901
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
3902
- };
3475
+ function generateTurboDockerfile(options) {
3476
+ const { baseImage, port, healthCheckPath, turboPackage, packageManager } = options;
3477
+ const pm = getPmConfig(packageManager);
3478
+ const installPm = pm.install ? `RUN ${pm.install}` : "";
3479
+ const turboInstallCmd = getTurboInstallCmd(packageManager);
3480
+ return `# syntax=docker/dockerfile:1
3481
+ # Stage 1: Prune monorepo
3482
+ FROM ${baseImage} AS pruner
3483
+
3484
+ WORKDIR /app
3485
+
3486
+ ${installPm}
3487
+
3488
+ COPY . .
3489
+
3490
+ # Prune to only include necessary packages
3491
+ RUN ${packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo"} prune ${turboPackage} --docker
3492
+
3493
+ # Stage 2: Install dependencies
3494
+ FROM ${baseImage} AS deps
3495
+
3496
+ WORKDIR /app
3497
+
3498
+ ${installPm}
3499
+
3500
+ # Copy pruned lockfile and package.jsons
3501
+ COPY --from=pruner /app/out/${pm.lockfile} ./
3502
+ COPY --from=pruner /app/out/json/ ./
3503
+
3504
+ # Install dependencies (no frozen-lockfile since turbo prune creates a subset)
3505
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
3506
+ ${turboInstallCmd}
3507
+
3508
+ # Stage 3: Build
3509
+ FROM deps AS builder
3510
+
3511
+ WORKDIR /app
3512
+
3513
+ # Copy pruned source
3514
+ COPY --from=pruner /app/out/full/ ./
3515
+
3516
+ # Build any workspace packages the app depends on.
3517
+ #
3518
+ # A no-op for a project whose dependencies come from a registry, and required
3519
+ # for one whose CLI is a sibling package: \`turbo prune\` honours .gitignore, so a
3520
+ # workspace package's \`dist\` never reaches the image and its bin has nothing to
3521
+ # import. \`--if-present\` is what makes the same Dockerfile correct for both.
3522
+ RUN ${pm.run} --if-present build
3523
+
3524
+ # Build production server using gkm
3525
+ RUN ${pm.exec} gkm build --provider server --production
3526
+
3527
+ # Stage 4: Production
3528
+ FROM ${baseImage} AS runner
3529
+
3530
+ WORKDIR /app
3531
+
3532
+ RUN apk add --no-cache tini
3533
+
3534
+ RUN addgroup --system --gid 1001 nodejs && \\
3535
+ adduser --system --uid 1001 hono
3536
+
3537
+ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
3538
+
3539
+ ENV NODE_ENV=production
3540
+ ENV PORT=${port}
3541
+
3542
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
3543
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3544
+
3545
+ USER hono
3546
+
3547
+ EXPOSE ${port}
3548
+
3549
+ ENTRYPOINT ["/sbin/tini", "--"]
3550
+ CMD ["node", "server.mjs"]
3551
+ `;
3903
3552
  }
3904
3553
  /**
3905
- * Get backup state from state
3554
+ * Generate a slim Dockerfile for pre-built bundles
3906
3555
  */
3907
- function getBackupState(state) {
3908
- return state?.backups;
3556
+ function generateSlimDockerfile(options) {
3557
+ const { baseImage, port, healthCheckPath, bundle = ".gkm/server/dist/server.mjs" } = options;
3558
+ return `# Slim Dockerfile for pre-built production bundle
3559
+ FROM ${baseImage}
3560
+
3561
+ WORKDIR /app
3562
+
3563
+ # Install tini for proper signal handling as PID 1
3564
+ # Handles SIGTERM propagation and zombie process reaping
3565
+ RUN apk add --no-cache tini
3566
+
3567
+ # Create non-root user
3568
+ RUN addgroup --system --gid 1001 nodejs && \\
3569
+ adduser --system --uid 1001 hono
3570
+
3571
+ # Copy pre-built bundle
3572
+ COPY ${bundle} ./
3573
+
3574
+ # Environment
3575
+ ENV NODE_ENV=production
3576
+ ENV PORT=${port}
3577
+
3578
+ # Health check
3579
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
3580
+ CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3581
+
3582
+ # Switch to non-root user
3583
+ USER hono
3584
+
3585
+ EXPOSE ${port}
3586
+
3587
+ # Use tini as entrypoint to handle PID 1 responsibilities
3588
+ ENTRYPOINT ["/sbin/tini", "--"]
3589
+ CMD ["node", "server.mjs"]
3590
+ `;
3909
3591
  }
3910
3592
  /**
3911
- * Set backup state (mutates state)
3593
+ * Generate .dockerignore file
3912
3594
  */
3913
- function setBackupState(state, backupState) {
3914
- state.backups = backupState;
3595
+ function generateDockerignore() {
3596
+ return `# Dependencies
3597
+ node_modules
3598
+ .pnpm-store
3599
+
3600
+ # Build output (except what we need)
3601
+ .gkm/aws*
3602
+ .gkm/server/*.ts
3603
+ !.gkm/server/dist
3604
+
3605
+ # The key gkm build --stage encrypted secrets with: runtime env only, never
3606
+ # the build context
3607
+ .gkm/server/master.key
3608
+
3609
+ # gkm compose's stacks: each app's env file holds its stage's secrets
3610
+ .gkm/compose
3611
+
3612
+ # IDE and editor
3613
+ .idea
3614
+ .vscode
3615
+ *.swp
3616
+ *.swo
3617
+
3618
+ # Git
3619
+ .git
3620
+ .gitignore
3621
+
3622
+ # Logs
3623
+ *.log
3624
+ npm-debug.log*
3625
+ pnpm-debug.log*
3626
+
3627
+ # Test files
3628
+ **/*.test.ts
3629
+ **/*.spec.ts
3630
+ **/__tests__
3631
+ coverage
3632
+
3633
+ # Documentation
3634
+ docs
3635
+ *.md
3636
+ !README.md
3637
+
3638
+ # Environment files (handle secrets separately)
3639
+ .env
3640
+ .env.*
3641
+ !.env.example
3642
+
3643
+ # Docker files (don't copy recursively)
3644
+ Dockerfile*
3645
+ docker-compose*
3646
+ .dockerignore
3647
+ `;
3915
3648
  }
3916
3649
  /**
3917
- * Set postgres backup ID in state (mutates state)
3650
+ * Generate docker-entrypoint.sh for custom startup logic
3918
3651
  */
3919
- function setPostgresBackupId(state, backupId) {
3920
- if (state.backups) state.backups.postgresBackupId = backupId;
3652
+ function generateDockerEntrypoint() {
3653
+ return `#!/bin/sh
3654
+ set -e
3655
+
3656
+ # Run any custom startup scripts here
3657
+ # Example: wait for database
3658
+ # until nc -z $DB_HOST $DB_PORT; do
3659
+ # echo "Waiting for database..."
3660
+ # sleep 1
3661
+ # done
3662
+
3663
+ # Execute the main command
3664
+ exec "$@"
3665
+ `;
3921
3666
  }
3922
- //#endregion
3923
- //#region src/deploy/domain.ts
3924
- /** A stage was deployed that `deploy.domains` gives no domain. */
3925
- var NoDomainForStage = class extends Error {
3926
- stage;
3927
- constructor(stage) {
3928
- super(`No domain for stage "${stage}". Add it to gkm.config.ts: deploy: { domains: { ${stage}: 'example.com' } }.`);
3929
- this.stage = stage;
3930
- this.name = "NoDomainForStage";
3931
- }
3932
- };
3933
3667
  /**
3934
- * The hostname an app answers on for a stage.
3935
- *
3936
- * 1. An explicit `app.domain` — a string, or one per stage — wins.
3937
- * 2. Otherwise from the stage's base domain in `deploy.domains`: the root site
3938
- * answers on the domain itself, and every other app on
3939
- * `{subdomain}.{domain}` — its declaration's `subdomain`, or its own name.
3940
- *
3941
- * @throws {NoDomainForStage} when `deploy.domains` names no domain for it
3668
+ * Resolve Docker configuration from GkmConfig with defaults
3942
3669
  */
3943
- function resolveHost(appName, app, stage, domains, isMainFrontend) {
3944
- if (app.domain) {
3945
- if (typeof app.domain === "string") return app.domain;
3946
- if (app.domain[stage]) return app.domain[stage];
3947
- }
3948
- const baseDomain = domains?.[stage];
3949
- if (!baseDomain) throw new NoDomainForStage(stage);
3950
- if (isMainFrontend) return baseDomain;
3951
- return `${app.subdomain ?? appName}.${baseDomain}`;
3670
+ function resolveDockerConfig(config) {
3671
+ const docker = config.docker ?? {};
3672
+ return {
3673
+ registry: docker.registry ?? "",
3674
+ imageName: docker.imageName ?? config.name ?? "api",
3675
+ baseImage: docker.baseImage ?? "node:22-alpine",
3676
+ port: docker.port ?? 3e3
3677
+ };
3952
3678
  }
3953
3679
  /**
3954
- * Whether this app is the site the base domain points at — see `rootSite`,
3955
- * which the local edge asks too.
3956
- *
3957
- * @throws {AmbiguousRootSite} when several sites could be the root and none says it is
3680
+ * Generate a Dockerfile for Next.js frontend apps using standalone output.
3681
+ * Uses turbo prune for monorepo optimization.
3682
+ * @internal Exported for testing
3958
3683
  */
3959
- function isMainFrontendApp(appName, app, allApps) {
3960
- if (app.type !== "web") return false;
3961
- return rootSite(Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
3962
- name,
3963
- root: a.root
3964
- }))) === appName;
3965
- }
3966
- /**
3967
- * Get public URL arg names from app dependencies.
3968
- *
3969
- * @param app - The web/mobile app configuration
3970
- * @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks
3971
- * without a public prefix (e.g. Remix).
3972
- */
3973
- function getPublicUrlArgNames(app) {
3974
- const prefix = require_workspace.getPublicEnvPrefix(app.framework);
3975
- if (!prefix) return [];
3976
- return app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);
3977
- }
3978
- //#endregion
3979
- //#region src/docker/imageRef.ts
3980
- /**
3981
- * An image reference, checked before it reaches `docker`.
3982
- *
3983
- * The parts come from the workspace — a registry in `gkm.config.ts`, an image
3984
- * name from the project, a tag from `--tag` — and an argument array already
3985
- * keeps them out of a shell. What it cannot do is stop `docker` reading a value
3986
- * that starts with `-` as one of its own flags, so a ref is held to Docker's
3987
- * own grammar (distribution/reference) and anything else is refused by name
3988
- * before a build starts.
3989
- */
3990
- const DOMAIN_COMPONENT = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
3991
- const DOMAIN = `${DOMAIN_COMPONENT}(?:\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;
3992
- const PATH_COMPONENT = "[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*";
3993
- const REFERENCE = new RegExp(`^(?:${DOMAIN}/)?${PATH_COMPONENT}(?:/${PATH_COMPONENT})*(?::[\\w][\\w.-]{0,127})?(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,})?$`);
3994
- /** Docker refuses a repository name longer than this. */
3995
- const MAX_NAME_LENGTH = 255;
3996
- /** A value that is not an image reference was about to be built or pushed. */
3997
- var ImageRefInvalid = class extends Error {
3998
- ref;
3999
- constructor(ref) {
4000
- super(`'${ref}' is not a valid image reference. An image is \`[registry/]name[:tag]\`: the name lowercase letters, digits and . _ - separators, the tag up to 128 of letters, digits, _ . and -, not starting with . or -. Check docker.registry in gkm.config.ts and the --tag passed.`);
4001
- this.ref = ref;
4002
- this.name = "ImageRefInvalid";
4003
- }
4004
- };
4005
- /** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */
4006
- function validateImageRef(ref) {
4007
- const name = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
4008
- if (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) throw new ImageRefInvalid(ref);
4009
- return ref;
4010
- }
4011
- //#endregion
4012
- //#region src/docker/templates.ts
4013
- const LOCKFILES = [
4014
- ["pnpm-lock.yaml", "pnpm"],
4015
- ["bun.lockb", "bun"],
4016
- ["yarn.lock", "yarn"],
4017
- ["package-lock.json", "npm"]
4018
- ];
4019
- /**
4020
- * Detect package manager from lockfiles
4021
- * Walks up the directory tree to find lockfile (for monorepos)
4022
- */
4023
- function detectPackageManager(cwd = process.cwd()) {
4024
- let dir = cwd;
4025
- const root = (0, node_path.parse)(dir).root;
4026
- while (dir !== root) {
4027
- for (const [lockfile, pm] of LOCKFILES) if ((0, node_fs.existsSync)((0, node_path.join)(dir, lockfile))) return pm;
4028
- dir = (0, node_path.dirname)(dir);
4029
- }
4030
- for (const [lockfile, pm] of LOCKFILES) if ((0, node_fs.existsSync)((0, node_path.join)(root, lockfile))) return pm;
4031
- return "pnpm";
4032
- }
4033
- /**
4034
- * Find the lockfile path by walking up the directory tree
4035
- * Returns the full path to the lockfile, or null if not found
4036
- */
4037
- function findLockfilePath(cwd = process.cwd()) {
4038
- let dir = cwd;
4039
- const root = (0, node_path.parse)(dir).root;
4040
- while (dir !== root) {
4041
- for (const [lockfile] of LOCKFILES) {
4042
- const lockfilePath = (0, node_path.join)(dir, lockfile);
4043
- if ((0, node_fs.existsSync)(lockfilePath)) return lockfilePath;
4044
- }
4045
- dir = (0, node_path.dirname)(dir);
4046
- }
4047
- for (const [lockfile] of LOCKFILES) {
4048
- const lockfilePath = (0, node_path.join)(root, lockfile);
4049
- if ((0, node_fs.existsSync)(lockfilePath)) return lockfilePath;
4050
- }
4051
- return null;
4052
- }
4053
- /**
4054
- * Check if we're in a monorepo (lockfile is in a parent directory)
4055
- */
4056
- function isMonorepo(cwd = process.cwd()) {
4057
- const lockfilePath = findLockfilePath(cwd);
4058
- if (!lockfilePath) return false;
4059
- return (0, node_path.dirname)(lockfilePath) !== cwd;
4060
- }
4061
- /**
4062
- * Check if turbo.json exists (walks up directory tree)
4063
- */
4064
- function hasTurboConfig(cwd = process.cwd()) {
4065
- let dir = cwd;
4066
- const root = (0, node_path.parse)(dir).root;
4067
- while (dir !== root) {
4068
- if ((0, node_fs.existsSync)((0, node_path.join)(dir, "turbo.json"))) return true;
4069
- dir = (0, node_path.dirname)(dir);
4070
- }
4071
- return (0, node_fs.existsSync)((0, node_path.join)(root, "turbo.json"));
4072
- }
4073
- /**
4074
- * Get install command for turbo builds (without frozen lockfile)
4075
- * Turbo prune creates a subset that may not perfectly match the lockfile
4076
- */
4077
- function getTurboInstallCmd(pm) {
4078
- return {
4079
- pnpm: "pnpm install",
4080
- npm: "npm install",
4081
- yarn: "yarn install",
4082
- bun: "bun install"
4083
- }[pm];
4084
- }
4085
- /**
4086
- * Get package manager specific commands and paths
4087
- */
4088
- function getPmConfig(pm) {
4089
- return {
4090
- pnpm: {
4091
- install: "corepack enable && corepack prepare pnpm@latest --activate",
4092
- lockfile: "pnpm-lock.yaml",
4093
- fetch: "pnpm fetch",
4094
- installCmd: "pnpm install --frozen-lockfile --offline",
4095
- cacheTarget: "/root/.local/share/pnpm/store",
4096
- cacheId: "pnpm",
4097
- run: "pnpm",
4098
- exec: "pnpm exec",
4099
- dlx: "pnpm dlx",
4100
- addGlobal: "pnpm add -g"
4101
- },
4102
- npm: {
4103
- install: "",
4104
- lockfile: "package-lock.json",
4105
- fetch: "",
4106
- installCmd: "npm ci",
4107
- cacheTarget: "/root/.npm",
4108
- cacheId: "npm",
4109
- run: "npm run",
4110
- exec: "npx",
4111
- dlx: "npx",
4112
- addGlobal: "npm install -g"
4113
- },
4114
- yarn: {
4115
- install: "corepack enable && corepack prepare yarn@stable --activate",
4116
- lockfile: "yarn.lock",
4117
- fetch: "",
4118
- installCmd: "yarn install --frozen-lockfile",
4119
- cacheTarget: "/root/.yarn/cache",
4120
- cacheId: "yarn",
4121
- run: "yarn",
4122
- exec: "yarn exec",
4123
- dlx: "yarn dlx",
4124
- addGlobal: "yarn global add"
4125
- },
4126
- bun: {
4127
- install: "npm install -g bun",
4128
- lockfile: "bun.lockb",
4129
- fetch: "",
4130
- installCmd: "bun install --frozen-lockfile",
4131
- cacheTarget: "/root/.bun/install/cache",
4132
- cacheId: "bun",
4133
- run: "bun run",
4134
- exec: "bunx",
4135
- dlx: "bunx",
4136
- addGlobal: "bun add -g"
4137
- }
4138
- }[pm];
4139
- }
4140
- /**
4141
- * Generate a multi-stage Dockerfile for building from source
4142
- * Optimized for build speed with:
4143
- * - BuildKit cache mounts for package manager store
4144
- * - pnpm fetch for better layer caching (when using pnpm)
4145
- * - Optional turbo prune for monorepos
4146
- */
4147
- function generateMultiStageDockerfile(options) {
4148
- const { baseImage, port, healthCheckPath, turbo, turboPackage, packageManager } = options;
4149
- if (turbo) return generateTurboDockerfile({
4150
- ...options,
4151
- turboPackage: turboPackage ?? "api"
4152
- });
3684
+ function generateNextjsDockerfile(options) {
3685
+ const { baseImage, port, appPath, turboPackage, packageManager, publicUrlArgs = ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_AUTH_URL"] } = options;
4153
3686
  const pm = getPmConfig(packageManager);
3687
+ const installPm = pm.install ? `RUN ${pm.install}` : "";
3688
+ const turboInstallCmd = getTurboInstallCmd(packageManager);
3689
+ const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
3690
+ const publicUrlArgDeclarations = publicUrlArgs.map((arg) => `ARG ${arg}=""`).join("\n");
3691
+ const publicUrlEnvDeclarations = publicUrlArgs.map((arg) => `ENV ${arg}=$${arg}`).join("\n");
4154
3692
  return `# syntax=docker/dockerfile:1
4155
- # Stage 1: Dependencies
4156
- FROM ${baseImage} AS deps
3693
+ # Next.js standalone Dockerfile with turbo prune optimization
4157
3694
 
4158
- WORKDIR /app
4159
- ${pm.install ? `\n# Install ${packageManager}\nRUN ${pm.install}\n` : ""}
4160
- ${packageManager === "pnpm" ? `# Copy lockfile first for better caching
4161
- COPY ${pm.lockfile} ./
3695
+ # Stage 1: Prune monorepo
3696
+ FROM ${baseImage} AS pruner
4162
3697
 
4163
- # Fetch dependencies (downloads to virtual store, cached separately)
4164
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4165
- ${pm.fetch}
3698
+ WORKDIR /app
4166
3699
 
4167
- # Copy package.json after fetch
4168
- COPY package.json ./
3700
+ ${installPm}
4169
3701
 
4170
- # Install from cache (fast - no network needed)
4171
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4172
- ${pm.installCmd}` : `# Copy package files
4173
- COPY package.json ${pm.lockfile} ./
3702
+ COPY . .
4174
3703
 
4175
- # Install dependencies with cache
4176
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4177
- ${pm.installCmd}`}
3704
+ # Prune to only include necessary packages
3705
+ RUN ${turboCmd} prune ${turboPackage} --docker
4178
3706
 
4179
- # Stage 2: Build
4180
- FROM deps AS builder
3707
+ # Stage 2: Install dependencies
3708
+ FROM ${baseImage} AS deps
4181
3709
 
4182
3710
  WORKDIR /app
4183
3711
 
4184
- # Copy source (deps already installed)
4185
- COPY . .
3712
+ ${installPm}
4186
3713
 
4187
- # Build any workspace packages the app depends on.
4188
- #
4189
- # A no-op for a project whose dependencies come from a registry, and required
4190
- # for one whose CLI is a sibling package: \`turbo prune\` honours .gitignore, so a
4191
- # workspace package's \`dist\` never reaches the image and its bin has nothing to
4192
- # import. \`--if-present\` is what makes the same Dockerfile correct for both.
4193
- RUN ${pm.run} --if-present build
3714
+ # Copy pruned lockfile and package.jsons
3715
+ COPY --from=pruner /app/out/${pm.lockfile} ./
3716
+ COPY --from=pruner /app/out/json/ ./
4194
3717
 
4195
- # Build production server using gkm
4196
- RUN ${pm.exec} gkm build --provider server --production
3718
+ # Install dependencies
3719
+ RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
3720
+ ${turboInstallCmd}
4197
3721
 
4198
- # Stage 3: Production
4199
- FROM ${baseImage} AS runner
3722
+ # Stage 3: Build
3723
+ FROM deps AS builder
4200
3724
 
4201
3725
  WORKDIR /app
4202
3726
 
4203
- # Install tini for proper signal handling as PID 1
4204
- RUN apk add --no-cache tini
3727
+ # Build-time args for public API URLs (populated by gkm deploy)
3728
+ # These get baked into the Next.js build as public environment variables
3729
+ ${publicUrlArgDeclarations}
4205
3730
 
4206
- # Create non-root user
4207
- RUN addgroup --system --gid 1001 nodejs && \\
4208
- adduser --system --uid 1001 hono
3731
+ # Convert ARGs to ENVs for Next.js build
3732
+ ${publicUrlEnvDeclarations}
4209
3733
 
4210
- # Copy bundled server
4211
- COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
3734
+ # Copy pruned source
3735
+ COPY --from=pruner /app/out/full/ ./
4212
3736
 
4213
- # Environment
3737
+ # Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)
3738
+ # Using wildcard to make it optional for single-app projects
3739
+ COPY --from=pruner /app/tsconfig.* ./
3740
+
3741
+ # Ensure public directory exists (may be empty for scaffolded projects)
3742
+ RUN mkdir -p ${appPath}/public
3743
+
3744
+ # Set Next.js to produce standalone output
3745
+ ENV NEXT_TELEMETRY_DISABLED=1
3746
+
3747
+ # Build the application
3748
+ RUN ${turboCmd} run build --filter=${turboPackage}
3749
+
3750
+ # Stage 4: Production
3751
+ FROM ${baseImage} AS runner
3752
+
3753
+ WORKDIR /app
3754
+
3755
+ # Install tini for proper signal handling
3756
+ RUN apk add --no-cache tini
3757
+
3758
+ # Create non-root user
3759
+ RUN addgroup --system --gid 1001 nodejs && \\
3760
+ adduser --system --uid 1001 nextjs
3761
+
3762
+ # Set environment
4214
3763
  ENV NODE_ENV=production
3764
+ ENV NEXT_TELEMETRY_DISABLED=1
4215
3765
  ENV PORT=${port}
3766
+ ENV HOSTNAME="0.0.0.0"
4216
3767
 
4217
- # Health check
4218
- HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4219
- CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
3768
+ # Copy static files and standalone output
3769
+ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./
3770
+ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static
3771
+ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public
4220
3772
 
4221
- # Switch to non-root user
4222
- USER hono
3773
+ USER nextjs
4223
3774
 
4224
3775
  EXPOSE ${port}
4225
3776
 
4226
- # Use tini as entrypoint to handle PID 1 responsibilities
4227
3777
  ENTRYPOINT ["/sbin/tini", "--"]
4228
- CMD ["node", "server.mjs"]
3778
+ CMD ["node", "${appPath}/server.js"]
4229
3779
  `;
4230
3780
  }
4231
3781
  /**
4232
- * Generate a Dockerfile optimized for Turbo monorepos
4233
- * Uses turbo prune to create minimal Docker context
3782
+ * Generate a Dockerfile for backend apps in a workspace.
3783
+ * Uses turbo prune for monorepo optimization.
3784
+ * @internal Exported for testing
4234
3785
  */
4235
- function generateTurboDockerfile(options) {
4236
- const { baseImage, port, healthCheckPath, turboPackage, packageManager } = options;
3786
+ function generateBackendDockerfile(options) {
3787
+ const { baseImage, port, appPath, turboPackage, packageManager, healthCheckPath = "/health" } = options;
4237
3788
  const pm = getPmConfig(packageManager);
4238
3789
  const installPm = pm.install ? `RUN ${pm.install}` : "";
4239
3790
  const turboInstallCmd = getTurboInstallCmd(packageManager);
4240
3791
  return `# syntax=docker/dockerfile:1
3792
+ # Backend Dockerfile with turbo prune optimization
3793
+
4241
3794
  # Stage 1: Prune monorepo
4242
3795
  FROM ${baseImage} AS pruner
4243
3796
 
@@ -4261,7 +3814,7 @@ ${installPm}
4261
3814
  COPY --from=pruner /app/out/${pm.lockfile} ./
4262
3815
  COPY --from=pruner /app/out/json/ ./
4263
3816
 
4264
- # Install dependencies (no frozen-lockfile since turbo prune creates a subset)
3817
+ # Install dependencies
4265
3818
  RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4266
3819
  ${turboInstallCmd}
4267
3820
 
@@ -4273,6 +3826,22 @@ WORKDIR /app
4273
3826
  # Copy pruned source
4274
3827
  COPY --from=pruner /app/out/full/ ./
4275
3828
 
3829
+ # Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)
3830
+ # Using wildcard to make it optional for single-app projects
3831
+ COPY --from=pruner /app/gkm.config.* ./
3832
+ COPY --from=pruner /app/tsconfig.* ./
3833
+
3834
+ # Write encrypted credentials for gkm build to embed.
3835
+ # A build secret (\`docker build --secret id=gkm_credentials,src=…\`), not an
3836
+ # ARG: an ARG is recorded in the image history. Two lines, ciphertext then IV;
3837
+ # absent when the app has no secrets.
3838
+ RUN --mount=type=secret,id=gkm_credentials,required=false \
3839
+ if [ -s /run/secrets/gkm_credentials ]; then \
3840
+ mkdir -p ${appPath}/.gkm && \
3841
+ sed -n 1p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.enc && \
3842
+ sed -n 2p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.iv; \
3843
+ fi
3844
+
4276
3845
  # Build any workspace packages the app depends on.
4277
3846
  #
4278
3847
  # A no-op for a project whose dependencies come from a registry, and required
@@ -4282,7 +3851,7 @@ COPY --from=pruner /app/out/full/ ./
4282
3851
  RUN ${pm.run} --if-present build
4283
3852
 
4284
3853
  # Build production server using gkm
4285
- RUN ${pm.exec} gkm build --provider server --production
3854
+ RUN cd ${appPath} && ${pm.exec} gkm build --provider server --production
4286
3855
 
4287
3856
  # Stage 4: Production
4288
3857
  FROM ${baseImage} AS runner
@@ -4294,163 +3863,36 @@ RUN apk add --no-cache tini
4294
3863
  RUN addgroup --system --gid 1001 nodejs && \\
4295
3864
  adduser --system --uid 1001 hono
4296
3865
 
4297
- COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
4298
-
4299
- ENV NODE_ENV=production
4300
- ENV PORT=${port}
4301
-
4302
- HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4303
- CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
4304
-
4305
- USER hono
4306
-
4307
- EXPOSE ${port}
4308
-
4309
- ENTRYPOINT ["/sbin/tini", "--"]
4310
- CMD ["node", "server.mjs"]
4311
- `;
4312
- }
4313
- /**
4314
- * Generate a slim Dockerfile for pre-built bundles
4315
- */
4316
- function generateSlimDockerfile(options) {
4317
- const { baseImage, port, healthCheckPath, bundle = ".gkm/server/dist/server.mjs" } = options;
4318
- return `# Slim Dockerfile for pre-built production bundle
4319
- FROM ${baseImage}
4320
-
4321
- WORKDIR /app
4322
-
4323
- # Install tini for proper signal handling as PID 1
4324
- # Handles SIGTERM propagation and zombie process reaping
4325
- RUN apk add --no-cache tini
4326
-
4327
- # Create non-root user
4328
- RUN addgroup --system --gid 1001 nodejs && \\
4329
- adduser --system --uid 1001 hono
4330
-
4331
- # Copy pre-built bundle
4332
- COPY ${bundle} ./
3866
+ # Copy bundled server
3867
+ COPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.mjs ./
4333
3868
 
4334
- # Environment
4335
3869
  ENV NODE_ENV=production
4336
3870
  ENV PORT=${port}
4337
3871
 
4338
- # Health check
4339
3872
  HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4340
3873
  CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
4341
3874
 
4342
- # Switch to non-root user
4343
3875
  USER hono
4344
3876
 
4345
3877
  EXPOSE ${port}
4346
3878
 
4347
- # Use tini as entrypoint to handle PID 1 responsibilities
4348
3879
  ENTRYPOINT ["/sbin/tini", "--"]
4349
3880
  CMD ["node", "server.mjs"]
4350
3881
  `;
4351
3882
  }
4352
3883
  /**
4353
- * Generate .dockerignore file
4354
- */
4355
- function generateDockerignore() {
4356
- return `# Dependencies
4357
- node_modules
4358
- .pnpm-store
4359
-
4360
- # Build output (except what we need)
4361
- .gkm/aws*
4362
- .gkm/server/*.ts
4363
- !.gkm/server/dist
4364
-
4365
- # The key gkm build --stage encrypted secrets with: runtime env only, never
4366
- # the build context
4367
- .gkm/server/master.key
4368
-
4369
- # gkm compose's stacks: each app's env file holds its stage's secrets
4370
- .gkm/compose
4371
-
4372
- # IDE and editor
4373
- .idea
4374
- .vscode
4375
- *.swp
4376
- *.swo
4377
-
4378
- # Git
4379
- .git
4380
- .gitignore
4381
-
4382
- # Logs
4383
- *.log
4384
- npm-debug.log*
4385
- pnpm-debug.log*
4386
-
4387
- # Test files
4388
- **/*.test.ts
4389
- **/*.spec.ts
4390
- **/__tests__
4391
- coverage
4392
-
4393
- # Documentation
4394
- docs
4395
- *.md
4396
- !README.md
4397
-
4398
- # Environment files (handle secrets separately)
4399
- .env
4400
- .env.*
4401
- !.env.example
4402
-
4403
- # Docker files (don't copy recursively)
4404
- Dockerfile*
4405
- docker-compose*
4406
- .dockerignore
4407
- `;
4408
- }
4409
- /**
4410
- * Generate docker-entrypoint.sh for custom startup logic
4411
- */
4412
- function generateDockerEntrypoint() {
4413
- return `#!/bin/sh
4414
- set -e
4415
-
4416
- # Run any custom startup scripts here
4417
- # Example: wait for database
4418
- # until nc -z $DB_HOST $DB_PORT; do
4419
- # echo "Waiting for database..."
4420
- # sleep 1
4421
- # done
4422
-
4423
- # Execute the main command
4424
- exec "$@"
4425
- `;
4426
- }
4427
- /**
4428
- * Resolve Docker configuration from GkmConfig with defaults
4429
- */
4430
- function resolveDockerConfig(config) {
4431
- const docker = config.docker ?? {};
4432
- return {
4433
- registry: docker.registry ?? "",
4434
- imageName: docker.imageName ?? config.name ?? "api",
4435
- baseImage: docker.baseImage ?? "node:22-alpine",
4436
- port: docker.port ?? 3e3
4437
- };
4438
- }
4439
- /**
4440
- * Generate a Dockerfile for Next.js frontend apps using standalone output.
4441
- * Uses turbo prune for monorepo optimization.
3884
+ * Generate a Dockerfile for apps with a custom entry point.
3885
+ * Uses esbuild to bundle the entry point into dist/index.mjs with all dependencies.
3886
+ * This is used for apps that don't use gkm routes (e.g., Better Auth servers).
4442
3887
  * @internal Exported for testing
4443
3888
  */
4444
- function generateNextjsDockerfile(options) {
4445
- const { baseImage, port, appPath, turboPackage, packageManager, publicUrlArgs = ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_AUTH_URL"] } = options;
3889
+ function generateEntryDockerfile(options) {
3890
+ const { baseImage, port, appPath, entry, turboPackage, packageManager, healthCheckPath = "/health" } = options;
4446
3891
  const pm = getPmConfig(packageManager);
4447
3892
  const installPm = pm.install ? `RUN ${pm.install}` : "";
4448
3893
  const turboInstallCmd = getTurboInstallCmd(packageManager);
4449
- const turboCmd = packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo";
4450
- const publicUrlArgDeclarations = publicUrlArgs.map((arg) => `ARG ${arg}=""`).join("\n");
4451
- const publicUrlEnvDeclarations = publicUrlArgs.map((arg) => `ENV ${arg}=$${arg}`).join("\n");
4452
3894
  return `# syntax=docker/dockerfile:1
4453
- # Next.js standalone Dockerfile with turbo prune optimization
3895
+ # Entry-based Dockerfile with turbo prune + tsdown bundling
4454
3896
 
4455
3897
  # Stage 1: Prune monorepo
4456
3898
  FROM ${baseImage} AS pruner
@@ -4462,7 +3904,7 @@ ${installPm}
4462
3904
  COPY . .
4463
3905
 
4464
3906
  # Prune to only include necessary packages
4465
- RUN ${turboCmd} prune ${turboPackage} --docker
3907
+ RUN ${packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo"} prune ${turboPackage} --docker
4466
3908
 
4467
3909
  # Stage 2: Install dependencies
4468
3910
  FROM ${baseImage} AS deps
@@ -4479,18 +3921,11 @@ COPY --from=pruner /app/out/json/ ./
4479
3921
  RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4480
3922
  ${turboInstallCmd}
4481
3923
 
4482
- # Stage 3: Build
3924
+ # Stage 3: Build with tsdown
4483
3925
  FROM deps AS builder
4484
3926
 
4485
3927
  WORKDIR /app
4486
3928
 
4487
- # Build-time args for public API URLs (populated by gkm deploy)
4488
- # These get baked into the Next.js build as public environment variables
4489
- ${publicUrlArgDeclarations}
4490
-
4491
- # Convert ARGs to ENVs for Next.js build
4492
- ${publicUrlEnvDeclarations}
4493
-
4494
3929
  # Copy pruned source
4495
3930
  COPY --from=pruner /app/out/full/ ./
4496
3931
 
@@ -4498,222 +3933,27 @@ COPY --from=pruner /app/out/full/ ./
4498
3933
  # Using wildcard to make it optional for single-app projects
4499
3934
  COPY --from=pruner /app/tsconfig.* ./
4500
3935
 
4501
- # Ensure public directory exists (may be empty for scaffolded projects)
4502
- RUN mkdir -p ${appPath}/public
4503
-
4504
- # Set Next.js to produce standalone output
4505
- ENV NEXT_TELEMETRY_DISABLED=1
4506
-
4507
- # Build the application
4508
- RUN ${turboCmd} run build --filter=${turboPackage}
4509
-
4510
- # Stage 4: Production
4511
- FROM ${baseImage} AS runner
4512
-
4513
- WORKDIR /app
4514
-
4515
- # Install tini for proper signal handling
4516
- RUN apk add --no-cache tini
4517
-
4518
- # Create non-root user
4519
- RUN addgroup --system --gid 1001 nodejs && \\
4520
- adduser --system --uid 1001 nextjs
4521
-
4522
- # Set environment
4523
- ENV NODE_ENV=production
4524
- ENV NEXT_TELEMETRY_DISABLED=1
4525
- ENV PORT=${port}
4526
- ENV HOSTNAME="0.0.0.0"
4527
-
4528
- # Copy static files and standalone output
4529
- COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./
4530
- COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static
4531
- COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public
4532
-
4533
- USER nextjs
4534
-
4535
- EXPOSE ${port}
4536
-
4537
- ENTRYPOINT ["/sbin/tini", "--"]
4538
- CMD ["node", "${appPath}/server.js"]
4539
- `;
4540
- }
4541
- /**
4542
- * Generate a Dockerfile for backend apps in a workspace.
4543
- * Uses turbo prune for monorepo optimization.
4544
- * @internal Exported for testing
4545
- */
4546
- function generateBackendDockerfile(options) {
4547
- const { baseImage, port, appPath, turboPackage, packageManager, healthCheckPath = "/health" } = options;
4548
- const pm = getPmConfig(packageManager);
4549
- const installPm = pm.install ? `RUN ${pm.install}` : "";
4550
- const turboInstallCmd = getTurboInstallCmd(packageManager);
4551
- return `# syntax=docker/dockerfile:1
4552
- # Backend Dockerfile with turbo prune optimization
4553
-
4554
- # Stage 1: Prune monorepo
4555
- FROM ${baseImage} AS pruner
4556
-
4557
- WORKDIR /app
4558
-
4559
- ${installPm}
4560
-
4561
- COPY . .
4562
-
4563
- # Prune to only include necessary packages
4564
- RUN ${packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo"} prune ${turboPackage} --docker
4565
-
4566
- # Stage 2: Install dependencies
4567
- FROM ${baseImage} AS deps
4568
-
4569
- WORKDIR /app
4570
-
4571
- ${installPm}
4572
-
4573
- # Copy pruned lockfile and package.jsons
4574
- COPY --from=pruner /app/out/${pm.lockfile} ./
4575
- COPY --from=pruner /app/out/json/ ./
4576
-
4577
- # Install dependencies
4578
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4579
- ${turboInstallCmd}
4580
-
4581
- # Stage 3: Build
4582
- FROM deps AS builder
4583
-
4584
- WORKDIR /app
4585
-
4586
- # Copy pruned source
4587
- COPY --from=pruner /app/out/full/ ./
4588
-
4589
- # Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)
4590
- # Using wildcard to make it optional for single-app projects
4591
- COPY --from=pruner /app/gkm.config.* ./
4592
- COPY --from=pruner /app/tsconfig.* ./
4593
-
4594
- # Write encrypted credentials for gkm build to embed.
4595
- # A build secret (\`docker build --secret id=gkm_credentials,src=…\`), not an
4596
- # ARG: an ARG is recorded in the image history. Two lines, ciphertext then IV;
4597
- # absent when the app has no secrets.
4598
- RUN --mount=type=secret,id=gkm_credentials,required=false \
4599
- if [ -s /run/secrets/gkm_credentials ]; then \
4600
- mkdir -p ${appPath}/.gkm && \
4601
- sed -n 1p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.enc && \
4602
- sed -n 2p /run/secrets/gkm_credentials > ${appPath}/.gkm/credentials.iv; \
4603
- fi
4604
-
4605
- # Build any workspace packages the app depends on.
4606
- #
4607
- # A no-op for a project whose dependencies come from a registry, and required
4608
- # for one whose CLI is a sibling package: \`turbo prune\` honours .gitignore, so a
4609
- # workspace package's \`dist\` never reaches the image and its bin has nothing to
4610
- # import. \`--if-present\` is what makes the same Dockerfile correct for both.
4611
- RUN ${pm.run} --if-present build
4612
-
4613
- # Build production server using gkm
4614
- RUN cd ${appPath} && ${pm.exec} gkm build --provider server --production
4615
-
4616
- # Stage 4: Production
4617
- FROM ${baseImage} AS runner
4618
-
4619
- WORKDIR /app
4620
-
4621
- RUN apk add --no-cache tini
4622
-
4623
- RUN addgroup --system --gid 1001 nodejs && \\
4624
- adduser --system --uid 1001 hono
4625
-
4626
- # Copy bundled server
4627
- COPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.mjs ./
4628
-
4629
- ENV NODE_ENV=production
4630
- ENV PORT=${port}
4631
-
4632
- HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
4633
- CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
4634
-
4635
- USER hono
4636
-
4637
- EXPOSE ${port}
4638
-
4639
- ENTRYPOINT ["/sbin/tini", "--"]
4640
- CMD ["node", "server.mjs"]
4641
- `;
4642
- }
4643
- /**
4644
- * Generate a Dockerfile for apps with a custom entry point.
4645
- * Uses esbuild to bundle the entry point into dist/index.mjs with all dependencies.
4646
- * This is used for apps that don't use gkm routes (e.g., Better Auth servers).
4647
- * @internal Exported for testing
4648
- */
4649
- function generateEntryDockerfile(options) {
4650
- const { baseImage, port, appPath, entry, turboPackage, packageManager, healthCheckPath = "/health" } = options;
4651
- const pm = getPmConfig(packageManager);
4652
- const installPm = pm.install ? `RUN ${pm.install}` : "";
4653
- const turboInstallCmd = getTurboInstallCmd(packageManager);
4654
- return `# syntax=docker/dockerfile:1
4655
- # Entry-based Dockerfile with turbo prune + tsdown bundling
4656
-
4657
- # Stage 1: Prune monorepo
4658
- FROM ${baseImage} AS pruner
4659
-
4660
- WORKDIR /app
4661
-
4662
- ${installPm}
4663
-
4664
- COPY . .
4665
-
4666
- # Prune to only include necessary packages
4667
- RUN ${packageManager === "pnpm" ? "pnpm dlx turbo" : "npx turbo"} prune ${turboPackage} --docker
4668
-
4669
- # Stage 2: Install dependencies
4670
- FROM ${baseImage} AS deps
4671
-
4672
- WORKDIR /app
4673
-
4674
- ${installPm}
4675
-
4676
- # Copy pruned lockfile and package.jsons
4677
- COPY --from=pruner /app/out/${pm.lockfile} ./
4678
- COPY --from=pruner /app/out/json/ ./
4679
-
4680
- # Install dependencies
4681
- RUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\
4682
- ${turboInstallCmd}
4683
-
4684
- # Stage 3: Build with tsdown
4685
- FROM deps AS builder
4686
-
4687
- WORKDIR /app
4688
-
4689
- # Copy pruned source
4690
- COPY --from=pruner /app/out/full/ ./
4691
-
4692
- # Copy workspace root configs for turbo builds (turbo prune doesn't include root configs)
4693
- # Using wildcard to make it optional for single-app projects
4694
- COPY --from=pruner /app/tsconfig.* ./
4695
-
4696
- # Bundle entry point with esbuild (outputs to dist/index.mjs)
4697
- # Creates a fully standalone bundle with all dependencies included
4698
- # Use define to embed credentials if present.
4699
- # They arrive as a build secret (\`docker build --secret id=gkm_credentials,src=…\`),
4700
- # not an ARG: an ARG is recorded in the image history. Two lines, ciphertext
4701
- # then IV; absent when the app has no secrets.
4702
- RUN --mount=type=secret,id=gkm_credentials,required=false \
4703
- cd ${appPath} && \
4704
- if [ -s /run/secrets/gkm_credentials ]; then \
4705
- CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \
4706
- IV=$(sed -n 2p /run/secrets/gkm_credentials) && \
4707
- npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
4708
- --outfile=dist/index.mjs --packages=bundle \
4709
- --banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);' \
4710
- --define:__GKM_ENCRYPTED_CREDENTIALS__="'\\"$CREDS\\"'" \
4711
- --define:__GKM_CREDENTIALS_IV__="'\\"$IV\\"'"; \
4712
- else \
4713
- npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
4714
- --outfile=dist/index.mjs --packages=bundle \
4715
- --banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);'; \
4716
- fi
3936
+ # Bundle entry point with esbuild (outputs to dist/index.mjs)
3937
+ # Creates a fully standalone bundle with all dependencies included
3938
+ # Use define to embed credentials if present.
3939
+ # They arrive as a build secret (\`docker build --secret id=gkm_credentials,src=…\`),
3940
+ # not an ARG: an ARG is recorded in the image history. Two lines, ciphertext
3941
+ # then IV; absent when the app has no secrets.
3942
+ RUN --mount=type=secret,id=gkm_credentials,required=false \
3943
+ cd ${appPath} && \
3944
+ if [ -s /run/secrets/gkm_credentials ]; then \
3945
+ CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \
3946
+ IV=$(sed -n 2p /run/secrets/gkm_credentials) && \
3947
+ npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
3948
+ --outfile=dist/index.mjs --packages=bundle \
3949
+ --banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);' \
3950
+ --define:__GKM_ENCRYPTED_CREDENTIALS__="'\\"$CREDS\\"'" \
3951
+ --define:__GKM_CREDENTIALS_IV__="'\\"$IV\\"'"; \
3952
+ else \
3953
+ npx esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \
3954
+ --outfile=dist/index.mjs --packages=bundle \
3955
+ --banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);'; \
3956
+ fi
4717
3957
 
4718
3958
  # Stage 4: Production
4719
3959
  FROM ${baseImage} AS runner
@@ -5167,1092 +4407,264 @@ function printRunInstructions(workspace) {
5167
4407
  logger.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
5168
4408
  }
5169
4409
  //#endregion
5170
- //#region src/deploy/dokploy-api.ts
5171
- var dokploy_api_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
5172
- DEFAULT_DOKPLOY_TIMEOUT_MS: () => DEFAULT_DOKPLOY_TIMEOUT_MS,
5173
- DokployApi: () => DokployApi,
5174
- DokployApiError: () => DokployApiError,
5175
- DokployRequestTimedOut: () => DokployRequestTimedOut
5176
- });
5177
- /** Per request, so a stalled server fails the deploy, not the CI job. */
5178
- const DEFAULT_DOKPLOY_TIMEOUT_MS = 3e4;
5179
- /**
5180
- * Dokploy accepted the connection but did not answer in time.
5181
- *
5182
- * Not retried: a request that arrived and stalled may still be acted on, and
5183
- * sending it again could repeat a deploy or a create the server is already
5184
- * doing.
5185
- */
5186
- var DokployRequestTimedOut = class extends Error {
5187
- baseUrl;
5188
- endpoint;
5189
- timeoutMs;
5190
- constructor(baseUrl, endpoint, timeoutMs) {
5191
- super(`Dokploy at ${baseUrl} did not answer ${endpoint} within ${timeoutMs}ms. Check that the Dokploy server is healthy, or raise \`timeoutMs\` if this call is known to be slow.`);
5192
- this.baseUrl = baseUrl;
5193
- this.endpoint = endpoint;
5194
- this.timeoutMs = timeoutMs;
5195
- this.name = "DokployRequestTimedOut";
5196
- }
5197
- };
4410
+ //#region src/deploy/generated.ts
5198
4411
  /**
5199
- * Waits `ms` between retries, or rejects with the signal's reason the moment
5200
- * it aborts — a caller who gave up should not sit through the backoff.
4412
+ * The values a deployed stage generates once and keeps: what nothing can
4413
+ * provision and nobody else issues.
4414
+ *
4415
+ * - **A seed**, which salts every password a deploy derives — a database's
4416
+ * master and roles, a bucket's root user. Derived from repo facts alone
4417
+ * (project, stage, construct id), those passwords were computable by anyone
4418
+ * who could read the repo.
4419
+ * - **Each `secret` construct's value** — an auth server's signing secret —
4420
+ * random, rather than derived.
4421
+ * - **Each `encryption` construct's keyring** — one random key and an index
4422
+ * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.
4423
+ *
4424
+ * Both are generated on the first deploy that needs them and written to the
4425
+ * stage's store, so every later deploy reads the same values: sessions survive
4426
+ * a redeploy, and a role's password does not move. A value set by hand with
4427
+ * `gkm secrets:set` is kept, never replaced.
5201
4428
  */
5202
- function pause(ms, signal) {
5203
- return new Promise((resolve, reject) => {
5204
- if (signal?.aborted) {
5205
- reject(signal.reason);
5206
- return;
5207
- }
5208
- const onAbort = () => {
5209
- clearTimeout(timer);
5210
- reject(signal?.reason);
5211
- };
5212
- const timer = setTimeout(() => {
5213
- signal?.removeEventListener("abort", onAbort);
5214
- resolve();
5215
- }, ms);
5216
- signal?.addEventListener("abort", onAbort, { once: true });
5217
- });
5218
- }
5219
- var DokployApiError = class extends Error {
5220
- status;
5221
- statusText;
5222
- issues;
5223
- constructor(message, status, statusText, issues) {
5224
- super(message);
5225
- this.status = status;
5226
- this.statusText = statusText;
5227
- this.issues = issues;
5228
- this.name = "DokployApiError";
5229
- }
5230
- };
5231
4429
  /**
5232
- * Dokploy API client
4430
+ * The stage's secrets with its seed and every declared `secret`'s value
4431
+ * present, generating what is missing. Pure: the caller writes the result
4432
+ * back to the stage's store when `generated` is not empty.
5233
4433
  */
5234
- var DokployApi = class {
5235
- baseUrl;
5236
- token;
5237
- timeoutMs;
5238
- signal;
5239
- constructor(options) {
5240
- this.baseUrl = options.baseUrl.replace(/\/$/, "");
5241
- this.token = options.token;
5242
- this.timeoutMs = options.timeoutMs ?? 3e4;
5243
- this.signal = options.signal;
5244
- }
5245
- /**
5246
- * Make a GET request to the Dokploy API
5247
- */
5248
- async get(endpoint) {
5249
- return this.request("GET", endpoint);
4434
+ function withGeneratedSecrets(secrets, manifest) {
4435
+ const generated = [];
4436
+ const custom = { ...secrets.custom };
4437
+ for (const declaration of Object.values(manifest)) {
4438
+ if (declaration?.kind !== "secret" && declaration?.kind !== "encryption") continue;
4439
+ const key = declaration.provides?.[0];
4440
+ if (!key || custom[key]) continue;
4441
+ custom[key] = declaration.kind === "encryption" ? (0, _geekmidas_constructs_encryption.generateKeyring)() : random();
4442
+ generated.push(key);
5250
4443
  }
5251
- /**
5252
- * Make a POST request to the Dokploy API
5253
- */
5254
- async post(endpoint, body) {
5255
- return this.request("POST", endpoint, body);
5256
- }
5257
- /**
5258
- * Make a request to the Dokploy API
5259
- */
5260
- async request(method, endpoint, body) {
5261
- const url = `${this.baseUrl}/api/${endpoint}`;
5262
- let response;
5263
- let timeout;
5264
- let lastError;
5265
- for (let attempt = 1; attempt <= 3; attempt++) {
5266
- this.signal?.throwIfAborted();
5267
- timeout = AbortSignal.timeout(this.timeoutMs);
5268
- try {
5269
- response = await fetch(url, {
5270
- method,
5271
- headers: {
5272
- "Content-Type": "application/json",
5273
- "x-api-key": this.token
5274
- },
5275
- body: body ? JSON.stringify(body) : void 0,
5276
- signal: this.signal ? AbortSignal.any([this.signal, timeout]) : timeout
5277
- });
5278
- break;
5279
- } catch (error) {
5280
- const stopped = this.stopped(timeout, endpoint);
5281
- if (stopped) throw stopped;
5282
- lastError = error;
5283
- if (attempt === 3) break;
5284
- await pause(attempt * 750, this.signal);
5285
- }
5286
- }
5287
- if (!response || !timeout) {
5288
- const detail = lastError instanceof Error ? lastError.message : String(lastError);
5289
- throw new DokployApiError(`Could not reach Dokploy at ${this.baseUrl} (${endpoint}): ${detail}`, 0, "Network error");
5290
- }
5291
- try {
5292
- return await this.read(response);
5293
- } catch (error) {
5294
- throw this.stopped(timeout, endpoint) ?? error;
5295
- }
5296
- }
5297
- /**
5298
- * What a request cut short should reject with: the caller's own reason
5299
- * when they aborted, `DokployRequestTimedOut` when the deadline passed,
5300
- * nothing when it was neither.
5301
- */
5302
- stopped(timeout, endpoint) {
5303
- if (this.signal?.aborted) return this.signal.reason;
5304
- if (timeout.aborted) return new DokployRequestTimedOut(this.baseUrl, endpoint, this.timeoutMs);
5305
- }
5306
- /** Turns a response into its JSON body, or a `DokployApiError`. */
5307
- async read(response) {
5308
- if (!response.ok) {
5309
- let errorMessage = `Dokploy API error: ${response.status} ${response.statusText}`;
5310
- let issues;
5311
- try {
5312
- const errorBody = await response.json();
5313
- if (errorBody.message) errorMessage = `Dokploy API error: ${errorBody.message}`;
5314
- if (errorBody.issues?.length) {
5315
- issues = errorBody.issues;
5316
- errorMessage += `\n Issues: ${errorBody.issues.map((i) => i.message).join(", ")}`;
5317
- }
5318
- } catch {}
5319
- throw new DokployApiError(errorMessage, response.status, response.statusText, issues);
5320
- }
5321
- const text = await response.text();
5322
- if (!text || text.trim() === "") return;
5323
- return JSON.parse(text);
5324
- }
5325
- /**
5326
- * Every external port published on this server, across every project.
5327
- *
5328
- * Dokploy knows what it has bound; guessing does not. A port that looks free
5329
- * from here can be held by a service in a project this deploy has never
5330
- * heard of — which surfaces as `Port 5432 is already in use by container
5331
- * "…"`, naming something the caller cannot see.
5332
- *
5333
- * `project.all` is a summary, so each project is fetched: the per-service
5334
- * `externalPort` only appears on `project.one`.
5335
- */
5336
- async publishedPorts() {
5337
- const ports = /* @__PURE__ */ new Set();
5338
- const projects = await this.listProjects();
5339
- for (const project of projects) {
5340
- const details = await this.getProject(project.projectId);
5341
- for (const environment of details.environments ?? []) {
5342
- const resources = [
5343
- ...environment.postgres ?? [],
5344
- ...environment.redis ?? [],
5345
- ...environment.applications ?? []
5346
- ];
5347
- for (const resource of resources) if (typeof resource.externalPort === "number") ports.add(resource.externalPort);
5348
- }
5349
- }
5350
- return ports;
5351
- }
5352
- /**
5353
- * Validate the API token by making a test request
5354
- */
5355
- async validateToken() {
5356
- try {
5357
- await this.get("project.all");
5358
- return true;
5359
- } catch {
5360
- return false;
5361
- }
5362
- }
5363
- /**
5364
- * List all projects
5365
- */
5366
- async listProjects() {
5367
- return this.get("project.all");
5368
- }
5369
- /**
5370
- * Get a single project by ID
5371
- */
5372
- async getProject(projectId) {
5373
- return this.get(`project.one?projectId=${projectId}`);
5374
- }
5375
- /**
5376
- * Create a new project
5377
- */
5378
- async createProject(name, description) {
5379
- return this.post("project.create", {
5380
- name,
5381
- description: description ?? `Created by gkm CLI`
5382
- });
5383
- }
5384
- /**
5385
- * Change a project's name or description.
5386
- *
5387
- * The name is sent with the description because `project.update` validates
5388
- * the whole project on some Dokploy versions, and a missing name fails it.
5389
- */
5390
- async updateProject(projectId, updates) {
5391
- await this.post("project.update", {
5392
- projectId,
5393
- ...updates
5394
- });
5395
- }
5396
- /**
5397
- * Delete a project and all its resources
5398
- */
5399
- async deleteProject(projectId) {
5400
- await this.post("project.remove", { projectId });
5401
- }
5402
- /**
5403
- * Create an environment in a project
5404
- */
5405
- async createEnvironment(projectId, name, description) {
5406
- return this.post("environment.create", {
5407
- projectId,
5408
- name,
5409
- description: description ?? `${name} environment`
5410
- });
5411
- }
5412
- /**
5413
- * List all applications in a project
5414
- */
5415
- async listApplications(projectId) {
5416
- return this.listInProject(projectId, (env) => env.applications);
5417
- }
5418
- /**
5419
- * Everything of one kind in a project, across its environments.
5420
- *
5421
- * From `project.one`, because the per-kind `*.all?projectId=` endpoints
5422
- * return **404** on current Dokploy: resources live under environments now.
5423
- * Those calls used to be wrapped in `catch { return [] }`, which turned "I
5424
- * could not ask" into "there are none" — and for a find-or-create that is
5425
- * the worst available answer. Every deploy created a second application, a
5426
- * second Postgres and a second Redis, silently, for as long as the project
5427
- * existed.
5428
- *
5429
- * A failure here is not swallowed: creating a duplicate is worse than
5430
- * stopping.
5431
- */
5432
- async listInProject(projectId, pick) {
5433
- return ((await this.getProject(projectId)).environments ?? []).flatMap((environment) => (pick(environment) ?? []).map((resource) => ({
5434
- ...resource,
5435
- environmentId: environment.environmentId
5436
- })));
5437
- }
5438
- /**
5439
- * Find an application by name in a project
5440
- */
5441
- async findApplicationByName(projectId, name, environmentId) {
5442
- const applications = await this.listApplications(projectId);
5443
- const normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
5444
- return applications.find((app) => (!environmentId || app.environmentId === environmentId) && (app.name === name || app.appName === normalizedName));
5445
- }
5446
- /**
5447
- * Create a new application
5448
- */
5449
- async createApplication(name, projectId, environmentId) {
5450
- return this.post("application.create", {
5451
- name,
5452
- projectId,
5453
- environmentId,
5454
- appName: name.toLowerCase().replace(/[^a-z0-9-]/g, "-")
5455
- });
5456
- }
5457
- /**
5458
- * Find or create an application by name
5459
- */
5460
- async findOrCreateApplication(name, projectId, environmentId) {
5461
- const existing = await this.findApplicationByName(projectId, name, environmentId);
5462
- if (existing) return {
5463
- application: existing,
5464
- created: false
5465
- };
5466
- return {
5467
- application: await this.createApplication(name, projectId, environmentId),
5468
- created: true
5469
- };
5470
- }
5471
- /**
5472
- * Get an application by ID
5473
- */
5474
- async getApplication(applicationId) {
5475
- try {
5476
- return await this.get(`application.one?applicationId=${applicationId}`);
5477
- } catch {
5478
- return null;
5479
- }
5480
- }
5481
- /**
5482
- * Update an application
5483
- */
5484
- async updateApplication(applicationId, updates) {
5485
- await this.post("application.update", {
5486
- applicationId,
5487
- ...updates
5488
- });
5489
- }
5490
- /**
5491
- * Save environment variables for an application
5492
- */
5493
- async saveApplicationEnv(applicationId, env, options) {
5494
- await this.post("application.saveEnvironment", {
5495
- applicationId,
5496
- env,
5497
- buildArgs: options?.buildArgs ?? null,
5498
- buildSecrets: options?.buildSecrets ?? null,
5499
- createEnvFile: false
5500
- });
5501
- }
5502
- /**
5503
- * Configure application to use Docker provider (pull from registry)
5504
- *
5505
- * For private registries, either:
5506
- * - Use `registryId` if the registry is configured in Dokploy
5507
- * - Or provide `username`, `password`, and `registryUrl` directly
5508
- */
5509
- async saveDockerProvider(applicationId, dockerImage, options) {
5510
- await this.post("application.saveDockerProvider", {
5511
- applicationId,
5512
- dockerImage,
5513
- ...options,
5514
- username: options?.username ?? null,
5515
- password: options?.password ?? null,
5516
- registryUrl: options?.registryUrl ?? null
5517
- });
5518
- }
5519
- /**
5520
- * Deploy an application
5521
- */
5522
- async deployApplication(applicationId) {
5523
- await this.post("application.deploy", { applicationId });
5524
- }
5525
- /**
5526
- * Delete an application
5527
- */
5528
- async deleteApplication(applicationId) {
5529
- await this.post("application.remove", { applicationId });
5530
- }
5531
- /**
5532
- * List all registries
5533
- */
5534
- async listRegistries() {
5535
- return this.get("registry.all");
5536
- }
5537
- /**
5538
- * Create a new registry
5539
- */
5540
- async createRegistry(registryName, registryUrl, username, password, options) {
5541
- return this.post("registry.create", {
5542
- registryName,
5543
- registryUrl,
5544
- username,
5545
- password,
5546
- imagePrefix: options?.imagePrefix
5547
- });
5548
- }
5549
- /**
5550
- * Get a registry by ID
5551
- */
5552
- async getRegistry(registryId) {
5553
- return this.get(`registry.one?registryId=${registryId}`);
5554
- }
5555
- /**
5556
- * Update a registry
5557
- */
5558
- async updateRegistry(registryId, updates) {
5559
- await this.post("registry.update", {
5560
- registryId,
5561
- ...updates
5562
- });
5563
- }
5564
- /**
5565
- * Delete a registry
5566
- */
5567
- async deleteRegistry(registryId) {
5568
- await this.post("registry.remove", { registryId });
5569
- }
5570
- /**
5571
- * List all Postgres databases in a project
5572
- */
5573
- async listPostgres(projectId) {
5574
- return this.listInProject(projectId, (env) => env.postgres);
5575
- }
5576
- /**
5577
- * Find a Postgres database by name in a project
5578
- */
5579
- async findPostgresByName(projectId, name) {
5580
- const databases = await this.listPostgres(projectId);
5581
- const normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
5582
- const match = databases.find((db) => db.name === name || db.appName === normalizedName);
5583
- if (!match) return void 0;
5584
- return this.getPostgres(match.postgresId);
5585
- }
5586
- /**
5587
- * Create a new Postgres database
5588
- */
5589
- async createPostgres(name, projectId, environmentId, options) {
5590
- return this.post("postgres.create", {
5591
- name,
5592
- projectId,
5593
- environmentId,
5594
- appName: options?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
5595
- databaseName: options?.databaseName,
5596
- databaseUser: options?.databaseUser ?? "postgres",
5597
- databasePassword: options?.databasePassword,
5598
- dockerImage: options?.dockerImage ?? "postgres:18",
5599
- description: options?.description ?? `Postgres database for ${name}`
5600
- });
5601
- }
5602
- /**
5603
- * Find or create a Postgres database by name
5604
- */
5605
- async findOrCreatePostgres(name, projectId, environmentId, options) {
5606
- const existing = await this.findPostgresByName(projectId, name);
5607
- if (existing) return {
5608
- postgres: existing,
5609
- created: false
5610
- };
5611
- return {
5612
- postgres: await this.createPostgres(name, projectId, environmentId, options),
5613
- created: true
5614
- };
5615
- }
5616
- /**
5617
- * Get a Postgres database by ID
5618
- */
5619
- async getPostgres(postgresId) {
5620
- return this.get(`postgres.one?postgresId=${postgresId}`);
5621
- }
5622
- /**
5623
- * Deploy a Postgres database
5624
- */
5625
- async deployPostgres(postgresId) {
5626
- await this.post("postgres.deploy", { postgresId });
5627
- }
5628
- /**
5629
- * Save environment variables for Postgres
5630
- */
5631
- async savePostgresEnv(postgresId, env) {
5632
- await this.post("postgres.saveEnvironment", {
5633
- postgresId,
5634
- env
5635
- });
5636
- }
5637
- /**
5638
- * Set external port for Postgres (for external access)
5639
- */
5640
- async savePostgresExternalPort(postgresId, externalPort) {
5641
- await this.post("postgres.saveExternalPort", {
5642
- postgresId,
5643
- externalPort
5644
- });
5645
- }
5646
- /**
5647
- * Update Postgres configuration
5648
- */
5649
- async updatePostgres(postgresId, updates) {
5650
- await this.post("postgres.update", {
5651
- postgresId,
5652
- ...updates
5653
- });
5654
- }
5655
- /**
5656
- * Delete a Postgres database
5657
- */
5658
- async deletePostgres(postgresId) {
5659
- await this.post("postgres.remove", { postgresId });
5660
- }
5661
- /**
5662
- * The compose stacks in a project.
5663
- *
5664
- * Dokploy has first-class primitives for Postgres and Redis and none for
5665
- * MinIO, so an object store is a compose stack — the one kind whose contents
5666
- * this target writes rather than configures.
5667
- */
5668
- async listCompose(projectId) {
5669
- return this.listInProject(projectId, (env) => env.compose);
5670
- }
5671
- /** A compose stack by name, hydrated — `project.one` returns summaries. */
5672
- async findComposeByName(projectId, name) {
5673
- const summary = (await this.listCompose(projectId)).find((compose) => compose.name === name);
5674
- return summary ? this.getCompose(summary.composeId) : void 0;
5675
- }
5676
- async getCompose(composeId) {
5677
- return this.get(`compose.one?composeId=${composeId}`);
5678
- }
5679
- /**
5680
- * A compose stack carrying the file this target wrote.
5681
- *
5682
- * Created empty and then updated, because `compose.create` takes no file:
5683
- * it defaults to `sourceType: 'github'` and expects a repository. `raw` is
5684
- * what makes the file ours.
5685
- */
5686
- async findOrCreateCompose(name, projectId, environmentId, composeFile) {
5687
- const existing = await this.findComposeByName(projectId, name);
5688
- const compose = existing ?? await this.post("compose.create", {
5689
- name,
5690
- environmentId,
5691
- composeType: "docker-compose"
5692
- });
5693
- await this.post("compose.update", {
5694
- composeId: compose.composeId,
5695
- sourceType: "raw",
5696
- composeFile
5697
- });
5698
- return {
5699
- compose,
5700
- created: !existing
5701
- };
5702
- }
5703
- async deployCompose(composeId) {
5704
- await this.post("compose.deploy", { composeId });
5705
- }
5706
- async deleteCompose(composeId) {
5707
- await this.post("compose.delete", {
5708
- composeId,
5709
- deleteVolumes: false
5710
- });
5711
- }
5712
- /**
5713
- * List all Redis instances in a project
5714
- */
5715
- async listRedis(projectId) {
5716
- return this.listInProject(projectId, (env) => env.redis);
5717
- }
5718
- /**
5719
- * Find a Redis instance by name in a project
5720
- */
5721
- async findRedisByName(projectId, name) {
5722
- const instances = await this.listRedis(projectId);
5723
- const normalizedName = name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
5724
- const match = instances.find((redis) => redis.name === name || redis.appName === normalizedName);
5725
- return match ? this.getRedis(match.redisId) : void 0;
5726
- }
5727
- /**
5728
- * Create a new Redis instance
5729
- */
5730
- async createRedis(name, projectId, environmentId, options) {
5731
- return this.post("redis.create", {
5732
- name,
5733
- projectId,
5734
- environmentId,
5735
- appName: options?.appName ?? name.toLowerCase().replace(/[^a-z0-9-]/g, "-"),
5736
- databasePassword: options?.databasePassword,
5737
- dockerImage: options?.dockerImage ?? "redis:8",
5738
- description: options?.description ?? `Redis instance for ${name}`
5739
- });
5740
- }
5741
- /**
5742
- * Find or create a Redis instance by name
5743
- */
5744
- async findOrCreateRedis(name, projectId, environmentId, options) {
5745
- const existing = await this.findRedisByName(projectId, name);
5746
- if (existing) return {
5747
- redis: existing,
5748
- created: false
5749
- };
5750
- return {
5751
- redis: await this.createRedis(name, projectId, environmentId, options),
5752
- created: true
5753
- };
5754
- }
5755
- /**
5756
- * Get a Redis instance by ID
5757
- */
5758
- async getRedis(redisId) {
5759
- return this.get(`redis.one?redisId=${redisId}`);
5760
- }
5761
- /**
5762
- * Deploy a Redis instance
5763
- */
5764
- async deployRedis(redisId) {
5765
- await this.post("redis.deploy", { redisId });
5766
- }
5767
- /**
5768
- * Save environment variables for Redis
5769
- */
5770
- async saveRedisEnv(redisId, env) {
5771
- await this.post("redis.saveEnvironment", {
5772
- redisId,
5773
- env
5774
- });
5775
- }
5776
- /**
5777
- * Set external port for Redis (for external access)
5778
- */
5779
- async saveRedisExternalPort(redisId, externalPort) {
5780
- await this.post("redis.saveExternalPort", {
5781
- redisId,
5782
- externalPort
5783
- });
5784
- }
5785
- /**
5786
- * Update Redis configuration
5787
- */
5788
- async updateRedis(redisId, updates) {
5789
- await this.post("redis.update", {
5790
- redisId,
5791
- ...updates
5792
- });
5793
- }
5794
- /**
5795
- * Delete a Redis instance
5796
- */
5797
- async deleteRedis(redisId) {
5798
- await this.post("redis.remove", { redisId });
5799
- }
5800
- /**
5801
- * Create a new domain for an application
5802
- */
5803
- async createDomain(options) {
5804
- return this.post("domain.create", options);
5805
- }
5806
- /**
5807
- * Update an existing domain
5808
- */
5809
- async updateDomain(domainId, updates) {
5810
- await this.post("domain.update", {
5811
- domainId,
5812
- ...updates
5813
- });
5814
- }
5815
- /**
5816
- * Delete a domain
5817
- */
5818
- async deleteDomain(domainId) {
5819
- await this.post("domain.delete", { domainId });
5820
- }
5821
- /**
5822
- * Get a domain by ID
5823
- */
5824
- async getDomain(domainId) {
5825
- return this.get(`domain.one?domainId=${domainId}`);
5826
- }
5827
- /**
5828
- * Get all domains for an application
5829
- */
5830
- async getDomainsByApplicationId(applicationId) {
5831
- return this.get(`domain.byApplicationId?applicationId=${applicationId}`);
5832
- }
5833
- /**
5834
- * Validate a domain and trigger SSL certificate generation
5835
- *
5836
- * This should be called after DNS records are created and propagated.
5837
- * It triggers Let's Encrypt certificate generation for HTTPS domains.
5838
- *
5839
- * @param domain - The domain hostname to validate (e.g., 'api.example.com')
5840
- */
5841
- async validateDomain(domain) {
5842
- return this.post("domain.validateDomain", { domain });
5843
- }
5844
- /**
5845
- * Auto-generate a domain name for an application
5846
- */
5847
- async generateDomain(appName, serverId) {
5848
- return this.post("domain.generateDomain", {
5849
- appName,
5850
- serverId
5851
- });
5852
- }
5853
- /**
5854
- * List all backup destinations
5855
- */
5856
- async listDestinations() {
5857
- return this.get("destination.all");
5858
- }
5859
- /**
5860
- * Get a destination by ID
5861
- */
5862
- async getDestination(destinationId) {
5863
- return this.get(`destination.one?destinationId=${destinationId}`);
5864
- }
5865
- /**
5866
- * Create a new S3 backup destination
5867
- */
5868
- async createDestination(options) {
5869
- return this.post("destination.create", { ...options });
5870
- }
5871
- /**
5872
- * Find a destination by name
5873
- */
5874
- async findDestinationByName(name) {
5875
- return (await this.listDestinations()).find((d) => d.name === name);
5876
- }
5877
- /**
5878
- * Find or create a destination by name
5879
- */
5880
- async findOrCreateDestination(name, options) {
5881
- const existing = await this.findDestinationByName(name);
5882
- if (existing) return {
5883
- destination: existing,
5884
- created: false
5885
- };
5886
- return {
5887
- destination: await this.createDestination({
5888
- name,
5889
- ...options
5890
- }),
5891
- created: true
5892
- };
5893
- }
5894
- /**
5895
- * Update a destination
5896
- */
5897
- async updateDestination(destinationId, updates) {
5898
- await this.post("destination.update", {
5899
- destinationId,
5900
- ...updates
5901
- });
5902
- }
5903
- /**
5904
- * Delete a destination
5905
- */
5906
- async deleteDestination(destinationId) {
5907
- await this.post("destination.remove", { destinationId });
5908
- }
5909
- /**
5910
- * Test connection to a destination
5911
- */
5912
- async testDestinationConnection(destinationId) {
5913
- return this.post("destination.testConnection", { destinationId });
4444
+ const seed = secrets.seed ?? random();
4445
+ if (!secrets.seed) generated.push("seed");
4446
+ return {
4447
+ secrets: generated.length ? {
4448
+ ...secrets,
4449
+ seed,
4450
+ custom,
4451
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4452
+ } : secrets,
4453
+ generated
4454
+ };
4455
+ }
4456
+ /** 256 bits, URL-safe. */
4457
+ function random() {
4458
+ return (0, node_crypto.randomBytes)(32).toString("base64url");
4459
+ }
4460
+ //#endregion
4461
+ //#region src/deploy/journal.ts
4462
+ var DeployJournal = class DeployJournal {
4463
+ store;
4464
+ stage;
4465
+ state;
4466
+ resources;
4467
+ version;
4468
+ existed;
4469
+ constructor(store, stage, state, resources, version, existed) {
4470
+ this.store = store;
4471
+ this.stage = stage;
4472
+ this.state = state;
4473
+ this.resources = resources;
4474
+ this.version = version;
4475
+ this.existed = existed;
5914
4476
  }
5915
4477
  /**
5916
- * Create a backup schedule for postgres
4478
+ * The stage's journal — its stored state, or `initial` written as the
4479
+ * stage's first state, so there is somewhere to record the very first
4480
+ * resource before it is created.
5917
4481
  */
5918
- async createPostgresBackup(options) {
5919
- return this.post("backup.create", {
5920
- ...options,
5921
- databaseType: "postgres"
4482
+ static async open(store, stage, initial) {
4483
+ const stored = await store.read(stage);
4484
+ if (stored) return new DeployJournal(store, stage, stored.state, stored.resources, stored.version, true);
4485
+ const state = initial();
4486
+ const version = await store.write(stage, state, { expectedVersion: null });
4487
+ return new DeployJournal(store, stage, state, {}, version, false);
4488
+ }
4489
+ record(key) {
4490
+ return this.resources[key];
4491
+ }
4492
+ /** The keys a previous run marked `pending` and never saw created. */
4493
+ unfinished() {
4494
+ return Object.values(this.resources).filter((record) => record.status === "pending").map((record) => record.key);
4495
+ }
4496
+ /** Records that `entry` is about to be created. */
4497
+ async pending(entry) {
4498
+ await this.put({
4499
+ ...entry,
4500
+ status: "pending"
5922
4501
  });
5923
4502
  }
5924
- /**
5925
- * List backups for a postgres database
5926
- */
5927
- async listPostgresBackups(postgresId) {
5928
- return this.get(`backup.all?postgresId=${postgresId}&databaseType=postgres`);
5929
- }
5930
- /**
5931
- * Get a backup by ID
5932
- */
5933
- async getBackup(backupId) {
5934
- return this.get(`backup.one?backupId=${backupId}`);
5935
- }
5936
- /**
5937
- * Update a backup schedule
5938
- */
5939
- async updateBackup(backupId, updates) {
5940
- await this.post("backup.update", {
5941
- backupId,
5942
- ...updates
4503
+ /** Records `entry` as existing with `id`; a no-op if it already is. */
4504
+ async ready(entry, id) {
4505
+ const current = this.resources[entry.key];
4506
+ if (current?.status === "ready" && current.id === id) return;
4507
+ await this.put({
4508
+ ...entry,
4509
+ status: "ready",
4510
+ id
5943
4511
  });
5944
4512
  }
5945
4513
  /**
5946
- * Delete a backup schedule
4514
+ * The resource `entry` names: by its recorded id, else by looking it up,
4515
+ * else created — with `pending` written before the create and `ready`
4516
+ * after, so a run that dies in between leaves a record the next run
4517
+ * resolves by looking the resource up rather than creating another.
5947
4518
  */
5948
- async deleteBackup(backupId) {
5949
- await this.post("backup.remove", { backupId });
4519
+ async ensure(entry, steps) {
4520
+ const record = this.resources[entry.key];
4521
+ let staleId;
4522
+ if (record?.status === "ready" && record.id && steps.get) {
4523
+ const known = await steps.get(record.id);
4524
+ if (known) {
4525
+ await this.ready(entry, steps.id(known));
4526
+ return {
4527
+ resource: known,
4528
+ via: "recorded"
4529
+ };
4530
+ }
4531
+ staleId = record.id;
4532
+ }
4533
+ const existing = await steps.find();
4534
+ if (existing) {
4535
+ await this.ready(entry, steps.id(existing));
4536
+ return {
4537
+ resource: existing,
4538
+ via: record?.status === "pending" ? "resumed" : "found",
4539
+ ...staleId ? { staleId } : {}
4540
+ };
4541
+ }
4542
+ await this.pending(entry);
4543
+ const created = await steps.create();
4544
+ await this.ready(entry, steps.id(created));
4545
+ return {
4546
+ resource: created,
4547
+ via: "created",
4548
+ ...staleId ? { staleId } : {}
4549
+ };
5950
4550
  }
5951
- /**
5952
- * Trigger a manual backup run
5953
- */
5954
- async runBackupManually(backupId) {
5955
- await this.post("backup.manualBackup", { backupId });
4551
+ /** Writes the stage's state, keeping its resource records. */
4552
+ async save() {
4553
+ this.state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
4554
+ this.version = await this.store.write(this.stage, this.state, { expectedVersion: this.version });
4555
+ }
4556
+ async put(record) {
4557
+ this.version = await this.store.putResource(this.stage, record, { expectedVersion: this.version });
4558
+ this.resources = {
4559
+ ...this.resources,
4560
+ [record.key]: {
4561
+ ...record,
4562
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4563
+ }
4564
+ };
5956
4565
  }
5957
4566
  };
5958
4567
  //#endregion
5959
- //#region src/deploy/sniffer.ts
5960
- const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
5961
- const __dirname$1 = (0, node_path.dirname)(__filename$1);
4568
+ //#region src/deploy/state.ts
5962
4569
  /**
5963
- * Resolve the path to a sniffer helper file.
5964
- * Handles both dev (.ts with tsx) and production (.mjs from dist).
5965
- *
5966
- * In production: sniffer.ts is bundled into dist/index.mjs, but sniffer helper
5967
- * files are output to dist/deploy/ as standalone modules for subprocess loading.
4570
+ * Deploy state management for Dokploy deployments
5968
4571
  *
5969
- * In development: All files are in src/deploy/ and loaded via tsx.
4572
+ * Stores resource IDs (applications, services) per stage to avoid
4573
+ * re-creating resources on subsequent deploys.
5970
4574
  */
5971
- function resolveSnifferFile(baseName) {
5972
- const deployMjsPath = (0, node_path.resolve)(__dirname$1, "deploy", `${baseName}.mjs`);
5973
- if ((0, node_fs.existsSync)(deployMjsPath)) return deployMjsPath;
5974
- const mjsPath = (0, node_path.resolve)(__dirname$1, `${baseName}.mjs`);
5975
- if ((0, node_fs.existsSync)(mjsPath)) return mjsPath;
5976
- const tsPath = (0, node_path.resolve)(__dirname$1, `${baseName}.ts`);
5977
- if ((0, node_fs.existsSync)(tsPath)) return tsPath;
5978
- return tsPath;
5979
- }
5980
4575
  /**
5981
- * Run a sniffer worker in the sandbox, with the sandbox's environment and
5982
- * nothing of the deploy's: the deploy's Dokploy token, registry login and AWS
5983
- * keys stay where they are. A worker that outlives the timeout is killed, and
5984
- * comes back as the error the caller reports.
4576
+ * Create a new empty state for a stage
5985
4577
  */
5986
- async function runSniffer(run, workspacePath, cwd, nodeArgs) {
5987
- const sandbox = run.sandbox ?? require_sandbox.activeSandbox() ?? new require_local.LocalSandbox({ root: (0, node_path.resolve)(workspacePath) });
5988
- try {
5989
- const result = await sandbox.exec("node", nodeArgs, {
5990
- cwd,
5991
- env: { ...sandbox.env },
5992
- timeoutMs: run.timeoutMs ?? 3e4,
5993
- output: "capture"
5994
- });
5995
- return {
5996
- stdout: result.stdout,
5997
- stderr: result.stderr,
5998
- code: result.exitCode
5999
- };
6000
- } catch (error) {
6001
- return { error: error instanceof Error ? error : new Error(String(error)) };
6002
- }
4578
+ function createEmptyState(stage, projectId, environmentId) {
4579
+ return {
4580
+ provider: "dokploy",
4581
+ stage,
4582
+ projectId,
4583
+ environmentId,
4584
+ applications: {},
4585
+ services: {},
4586
+ lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
4587
+ };
6003
4588
  }
6004
- /** The worker's JSON answer: the last object on stdout naming `envVars`. */
6005
- function snifferAnswer(stdout) {
6006
- try {
6007
- const jsonMatch = stdout.match(/\{[^{}]*"envVars"[^{}]*\}[^{]*$/);
6008
- return jsonMatch ? JSON.parse(jsonMatch[0]) : void 0;
6009
- } catch {
6010
- return;
6011
- }
4589
+ /**
4590
+ * Set application ID in state (mutates state)
4591
+ */
4592
+ function setApplicationId(state, appName, applicationId) {
4593
+ state.applications[appName] = applicationId;
6012
4594
  }
6013
4595
  /**
6014
- * Get required environment variables for an app.
6015
- *
6016
- * Detection strategy (in order):
6017
- * 1. Frontend apps: Returns empty (no server secrets)
6018
- * 2. Apps with `requiredEnv`: Uses explicit list from config
6019
- * 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls
6020
- * 4. Route-based apps: Loads route files and calls getEnvironment() on each construct
6021
- * 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage
6022
- * 6. Apps with neither: Returns empty
6023
- *
6024
- * This function handles "fire and forget" async operations gracefully,
6025
- * capturing errors and unhandled rejections without failing the build.
6026
- *
6027
- * @param app - The normalized app configuration
6028
- * @param appName - The name of the app
6029
- * @param workspacePath - Absolute path to the workspace root
6030
- * @param options - Optional configuration for sniffing behavior
6031
- * @returns The sniffed environment with required variables
4596
+ * Record the image an app was deployed with (mutates state)
6032
4597
  */
6033
- async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
6034
- const { logWarnings = true, markOptional = false } = options;
6035
- const run = {
6036
- ...options.sandbox ? { sandbox: options.sandbox } : {},
6037
- ...options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}
6038
- };
6039
- if (app.type === "web" || app.type === "mobile") {
6040
- const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
6041
- const depVars = publicPrefix ? (app.dependencies ?? []).map((dep) => `${publicPrefix}${dep.toUpperCase()}_URL`) : [];
6042
- if (app.config) {
6043
- const sniffedVars = [];
6044
- const sniffedOptional = [];
6045
- const configPaths = [];
6046
- if (app.config.client) configPaths.push(app.config.client);
6047
- if (app.config.server) configPaths.push(app.config.server);
6048
- for (const configPath of configPaths) {
6049
- const result = await sniffEntryFile(configPath, app.path, workspacePath, run);
6050
- if (logWarnings && result.error) require_discover.output.warn(`[sniffer] ${appName}: Config file "${configPath}" threw error during sniffing (env vars still captured): ${result.error.message}`);
6051
- sniffedVars.push(...result.envVars);
6052
- sniffedOptional.push(...result.optionalEnvVars);
6053
- }
6054
- const optionalEnvVars = [...new Set(sniffedOptional)];
6055
- return {
6056
- appName,
6057
- requiredEnvVars: applyMarkOptional([.../* @__PURE__ */ new Set([...depVars, ...sniffedVars])], optionalEnvVars, markOptional),
6058
- optionalEnvVars
6059
- };
6060
- }
6061
- return {
6062
- appName,
6063
- requiredEnvVars: depVars,
6064
- optionalEnvVars: []
6065
- };
6066
- }
6067
- if (app.entry) {
6068
- const result = await sniffEntryFile(app.entry, app.path, workspacePath, run);
6069
- if (logWarnings && result.error) require_discover.output.warn(`[sniffer] ${appName}: Entry file threw error during sniffing (env vars still captured): ${result.error.message}`);
6070
- return {
6071
- appName,
6072
- requiredEnvVars: applyMarkOptional(result.envVars, result.optionalEnvVars, markOptional),
6073
- optionalEnvVars: result.optionalEnvVars
6074
- };
6075
- }
6076
- if (app.routes) {
6077
- const result = await sniffRouteFiles(normalizeRoutes(app.routes), app.path, workspacePath, run);
6078
- if (logWarnings && result.error) require_discover.output.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
6079
- return {
6080
- appName,
6081
- requiredEnvVars: applyMarkOptional(result.envVars, result.optionalEnvVars, markOptional),
6082
- optionalEnvVars: result.optionalEnvVars
6083
- };
6084
- }
6085
- if (app.envParser) {
6086
- const result = await sniffEnvParser(app.envParser, app.path, workspacePath, run);
6087
- if (logWarnings) {
6088
- if (result.error) require_discover.output.warn(`[sniffer] ${appName}: envParser threw error during sniffing (env vars still captured): ${result.error.message}`);
6089
- if (result.unhandledRejections.length > 0) require_discover.output.warn(`[sniffer] ${appName}: Fire-and-forget rejections during sniffing (suppressed): ${result.unhandledRejections.map((e) => e.message).join(", ")}`);
6090
- }
6091
- return {
6092
- appName,
6093
- requiredEnvVars: applyMarkOptional(result.envVars, result.optionalEnvVars, markOptional),
6094
- optionalEnvVars: result.optionalEnvVars
6095
- };
6096
- }
6097
- return {
6098
- appName,
6099
- requiredEnvVars: [],
6100
- optionalEnvVars: []
4598
+ function setDeployedImage(state, appName, image) {
4599
+ state.images = {
4600
+ ...state.images,
4601
+ [appName]: image
6101
4602
  };
6102
4603
  }
6103
4604
  /**
6104
- * Apply the `?` suffix to optional variables in an env var list.
6105
- * Only modifies the list when `markOptional` is true and there are optional vars.
4605
+ * Get a generated secret for an app
6106
4606
  */
6107
- function applyMarkOptional(envVars, optionalEnvVars, markOptional) {
6108
- if (!markOptional || optionalEnvVars.length === 0) return envVars;
6109
- const optionalSet = new Set(optionalEnvVars);
6110
- return envVars.map((v) => optionalSet.has(v) ? `${v}?` : v);
4607
+ function getGeneratedSecret(state, appName, secretName) {
4608
+ return state?.generatedSecrets?.[appName]?.[secretName];
6111
4609
  }
6112
4610
  /**
6113
- * Sniff an entry file by importing it in a subprocess.
6114
- *
6115
- * Entry apps call `config.parse()` at module load time. To capture which
6116
- * env vars are accessed, we:
6117
- * 1. Spawn a subprocess with a module loader hook
6118
- * 2. The loader intercepts `@geekmidas/envkit` and replaces EnvironmentParser
6119
- * with SnifferEnvironmentParser
6120
- * 3. Import the entry file (triggers config.parse())
6121
- * 4. Capture and return the accessed env var names
6122
- *
6123
- * This approach provides process isolation - each app is sniffed in its own
6124
- * subprocess, preventing module cache pollution.
6125
- *
6126
- * @param entryPath - Relative path to the entry file (e.g., './src/index.ts')
6127
- * @param appPath - The app's path relative to workspace (e.g., 'apps/auth')
6128
- * @param workspacePath - Absolute path to workspace root
6129
- * @returns EntrySniffResult with env vars and optional error
4611
+ * Set a generated secret for an app (mutates state)
6130
4612
  */
6131
- async function sniffEntryFile(entryPath, appPath, workspacePath, run = {}) {
6132
- const fullEntryPath = (0, node_path.resolve)(workspacePath, appPath, entryPath);
6133
- const loaderPath = resolveSnifferFile("sniffer-loader");
6134
- const workerPath = resolveSnifferFile("sniffer-worker");
6135
- const ran = await runSniffer(run, workspacePath, (0, node_path.resolve)(workspacePath, appPath), require_discover.nodeWithTsx(workerPath, [fullEntryPath], [loaderPath]));
6136
- if ("error" in ran) return {
6137
- envVars: [],
6138
- optionalEnvVars: [],
6139
- error: ran.error
6140
- };
6141
- const answer = snifferAnswer(ran.stdout);
6142
- if (answer) return {
6143
- envVars: answer.envVars || [],
6144
- optionalEnvVars: answer.optionalEnvVars || [],
6145
- error: answer.error ? new Error(answer.error) : void 0
6146
- };
6147
- return {
6148
- envVars: [],
6149
- optionalEnvVars: [],
6150
- error: /* @__PURE__ */ new Error(`Failed to sniff entry file (exit code ${ran.code}): ${ran.stderr || ran.stdout || "No output"}`)
6151
- };
4613
+ function setGeneratedSecret(state, appName, secretName, value) {
4614
+ if (!state.generatedSecrets) state.generatedSecrets = {};
4615
+ if (!state.generatedSecrets[appName]) state.generatedSecrets[appName] = {};
4616
+ state.generatedSecrets[appName][secretName] = value;
6152
4617
  }
6153
4618
  /**
6154
- * Sniff route files by loading constructs and calling getEnvironment().
6155
- *
6156
- * Route-based apps have endpoints, functions, crons, and subscribers that
6157
- * use services. Each service's register() method accesses environment variables.
6158
- *
6159
- * This runs in a subprocess with tsx loader to properly handle TypeScript
6160
- * compilation and path alias resolution (e.g., `src/...` imports).
6161
- *
6162
- * @param routes - Glob pattern(s) for route files
6163
- * @param appPath - The app's path relative to workspace (e.g., 'apps/api')
6164
- * @param workspacePath - Absolute path to workspace root
6165
- * @returns EntrySniffResult with env vars and optional error
4619
+ * Set DNS verification record for a hostname (mutates state)
6166
4620
  */
6167
- async function sniffRouteFiles(routes, appPath, workspacePath, run = {}) {
6168
- const fullAppPath = (0, node_path.resolve)(workspacePath, appPath);
6169
- const workerPath = resolveSnifferFile("sniffer-routes-worker");
6170
- const pattern = (Array.isArray(routes) ? routes : [routes])[0];
6171
- if (!pattern) return {
6172
- envVars: [],
6173
- optionalEnvVars: [],
6174
- error: /* @__PURE__ */ new Error("No route patterns provided")
6175
- };
6176
- const ran = await runSniffer(run, workspacePath, fullAppPath, require_discover.nodeWithTsx(workerPath, [fullAppPath, pattern]));
6177
- if ("error" in ran) return {
6178
- envVars: [],
6179
- optionalEnvVars: [],
6180
- error: ran.error
6181
- };
6182
- if (ran.stderr) ran.stderr.split("\n").filter((line) => line.trim()).forEach((line) => require_discover.output.warn(line));
6183
- const answer = snifferAnswer(ran.stdout);
6184
- if (answer) return {
6185
- envVars: answer.envVars || [],
6186
- optionalEnvVars: answer.optionalEnvVars || [],
6187
- error: answer.error ? new Error(answer.error) : void 0
6188
- };
6189
- return {
6190
- envVars: [],
6191
- optionalEnvVars: [],
6192
- error: /* @__PURE__ */ new Error(`Failed to sniff route files (exit code ${ran.code}): ${ran.stderr || ran.stdout || "No output"}`)
4621
+ function setDnsVerification(state, hostname, serverIp) {
4622
+ if (!state.dnsVerified) state.dnsVerified = {};
4623
+ state.dnsVerified[hostname] = {
4624
+ serverIp,
4625
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
6193
4626
  };
6194
4627
  }
6195
4628
  /**
6196
- * Run the SnifferEnvironmentParser on an envParser module to detect
6197
- * which environment variables it accesses.
6198
- *
6199
- * In a subprocess, like the entry and route sniffers: the module is the
6200
- * project's code, and it was imported into the deploy itself before — with
6201
- * every credential the deploy held in reach. Fire-and-forget rejections are
6202
- * collected there with the shared `sniffWithFireAndForget`.
6203
- *
6204
- * @param envParserPath - The envParser config (e.g., './src/config/env#envParser')
6205
- * @param appPath - The app's path relative to workspace
6206
- * @param workspacePath - Absolute path to workspace root
6207
- * @returns SniffResult with env vars and any errors encountered
4629
+ * Check if a hostname is already verified with the given IP
6208
4630
  */
6209
- async function sniffEnvParser(envParserPath, appPath, workspacePath, run = {}) {
6210
- const [modulePath, exportName = "default"] = envParserPath.split("#");
6211
- if (!modulePath) return {
6212
- envVars: [],
6213
- optionalEnvVars: [],
6214
- unhandledRejections: []
6215
- };
6216
- const appRoot = (0, node_path.resolve)(workspacePath, appPath);
6217
- const fullPath = (0, node_path.resolve)(appRoot, modulePath);
6218
- const workerPath = resolveSnifferFile("sniffer-envparser-worker");
6219
- const ran = await runSniffer(run, workspacePath, appRoot, require_discover.nodeWithTsx(workerPath, [fullPath, exportName]));
6220
- if ("error" in ran) return {
6221
- envVars: [],
6222
- optionalEnvVars: [],
6223
- unhandledRejections: [],
6224
- error: ran.error
6225
- };
6226
- const answer = snifferAnswer(ran.stdout);
6227
- if (!answer) return {
6228
- envVars: [],
6229
- optionalEnvVars: [],
6230
- unhandledRejections: [],
6231
- error: /* @__PURE__ */ new Error(`Failed to sniff envParser (exit code ${ran.code}): ${ran.stderr || ran.stdout || "No output"}`)
6232
- };
6233
- for (const warning of answer.warnings ?? []) require_discover.output.warn(warning);
6234
- return {
6235
- envVars: answer.envVars ?? [],
6236
- optionalEnvVars: answer.optionalEnvVars ?? [],
6237
- unhandledRejections: (answer.unhandledRejections ?? []).map((message) => new Error(message)),
6238
- ...answer.error ? { error: new Error(answer.error) } : {}
4631
+ function isDnsVerified(state, hostname, serverIp) {
4632
+ return (state?.dnsVerified?.[hostname])?.serverIp === serverIp;
4633
+ }
4634
+ /**
4635
+ * Get the key for a DNS record in state
4636
+ */
4637
+ function getDnsRecordKey(name, type) {
4638
+ return `${name}:${type}`;
4639
+ }
4640
+ /**
4641
+ * Set a created DNS record in state (mutates state)
4642
+ */
4643
+ function setDnsRecord(state, record) {
4644
+ if (!state.dnsRecords) state.dnsRecords = {};
4645
+ const key = getDnsRecordKey(record.name, record.type);
4646
+ state.dnsRecords[key] = {
4647
+ ...record,
4648
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6239
4649
  };
6240
4650
  }
6241
4651
  /**
6242
- * Sniff environment requirements for multiple apps.
6243
- *
6244
- * @param apps - Map of app name to app config
6245
- * @param workspacePath - Absolute path to workspace root
6246
- * @param options - Optional configuration for sniffing behavior
6247
- * @returns Map of app name to sniffed environment
4652
+ * Get backup state from state
6248
4653
  */
6249
- async function sniffAllApps(apps, workspacePath, options = {}) {
6250
- const results = /* @__PURE__ */ new Map();
6251
- for (const [appName, app] of Object.entries(apps)) {
6252
- const sniffed = await sniffAppEnvironment(app, appName, workspacePath, options);
6253
- results.set(appName, sniffed);
6254
- }
6255
- return results;
4654
+ function getBackupState(state) {
4655
+ return state?.backups;
4656
+ }
4657
+ /**
4658
+ * Set backup state (mutates state)
4659
+ */
4660
+ function setBackupState(state, backupState) {
4661
+ state.backups = backupState;
4662
+ }
4663
+ /**
4664
+ * Set postgres backup ID in state (mutates state)
4665
+ */
4666
+ function setPostgresBackupId(state, backupId) {
4667
+ if (state.backups) state.backups.postgresBackupId = backupId;
6256
4668
  }
6257
4669
  //#endregion
6258
4670
  Object.defineProperty(exports, "DEFAULT_IMAGES", {
@@ -6261,34 +4673,22 @@ Object.defineProperty(exports, "DEFAULT_IMAGES", {
6261
4673
  return DEFAULT_IMAGES;
6262
4674
  }
6263
4675
  });
6264
- Object.defineProperty(exports, "DockerBuildFailed", {
6265
- enumerable: true,
6266
- get: function() {
6267
- return DockerBuildFailed;
6268
- }
6269
- });
6270
- Object.defineProperty(exports, "DockerPushFailed", {
6271
- enumerable: true,
6272
- get: function() {
6273
- return DockerPushFailed;
6274
- }
6275
- });
6276
- Object.defineProperty(exports, "DocumentStateStore", {
4676
+ Object.defineProperty(exports, "DeployJournal", {
6277
4677
  enumerable: true,
6278
4678
  get: function() {
6279
- return DocumentStateStore;
4679
+ return DeployJournal;
6280
4680
  }
6281
4681
  });
6282
- Object.defineProperty(exports, "DokployApi", {
4682
+ Object.defineProperty(exports, "DockerBuildFailed", {
6283
4683
  enumerable: true,
6284
4684
  get: function() {
6285
- return DokployApi;
4685
+ return DockerBuildFailed;
6286
4686
  }
6287
4687
  });
6288
- Object.defineProperty(exports, "DokployApiError", {
4688
+ Object.defineProperty(exports, "DockerPushFailed", {
6289
4689
  enumerable: true,
6290
4690
  get: function() {
6291
- return DokployApiError;
4691
+ return DockerPushFailed;
6292
4692
  }
6293
4693
  });
6294
4694
  Object.defineProperty(exports, "FAKE_ENV", {
@@ -6303,36 +4703,6 @@ Object.defineProperty(exports, "LOCAL_CA_PATH", {
6303
4703
  return LOCAL_CA_PATH;
6304
4704
  }
6305
4705
  });
6306
- Object.defineProperty(exports, "StageStateMissing", {
6307
- enumerable: true,
6308
- get: function() {
6309
- return StageStateMissing;
6310
- }
6311
- });
6312
- Object.defineProperty(exports, "StateLocked", {
6313
- enumerable: true,
6314
- get: function() {
6315
- return StateLocked;
6316
- }
6317
- });
6318
- Object.defineProperty(exports, "StateStoreBusy", {
6319
- enumerable: true,
6320
- get: function() {
6321
- return StateStoreBusy;
6322
- }
6323
- });
6324
- Object.defineProperty(exports, "StateStoreWithoutLocking", {
6325
- enumerable: true,
6326
- get: function() {
6327
- return StateStoreWithoutLocking;
6328
- }
6329
- });
6330
- Object.defineProperty(exports, "StateVersionConflict", {
6331
- enumerable: true,
6332
- get: function() {
6333
- return StateVersionConflict;
6334
- }
6335
- });
6336
4706
  Object.defineProperty(exports, "appEnvKeys", {
6337
4707
  enumerable: true,
6338
4708
  get: function() {
@@ -6363,12 +4733,6 @@ Object.defineProperty(exports, "cacheBackendFor", {
6363
4733
  return cacheBackendFor;
6364
4734
  }
6365
4735
  });
6366
- Object.defineProperty(exports, "canPrompt", {
6367
- enumerable: true,
6368
- get: function() {
6369
- return canPrompt;
6370
- }
6371
- });
6372
4736
  Object.defineProperty(exports, "composeFor", {
6373
4737
  enumerable: true,
6374
4738
  get: function() {
@@ -6381,12 +4745,6 @@ Object.defineProperty(exports, "constructGlobs", {
6381
4745
  return constructGlobs;
6382
4746
  }
6383
4747
  });
6384
- Object.defineProperty(exports, "contentVersion", {
6385
- enumerable: true,
6386
- get: function() {
6387
- return contentVersion;
6388
- }
6389
- });
6390
4748
  Object.defineProperty(exports, "createCredentialsPreload", {
6391
4749
  enumerable: true,
6392
4750
  get: function() {
@@ -6405,12 +4763,6 @@ Object.defineProperty(exports, "createEntryWrapper", {
6405
4763
  return createEntryWrapper;
6406
4764
  }
6407
4765
  });
6408
- Object.defineProperty(exports, "createStateStore", {
6409
- enumerable: true,
6410
- get: function() {
6411
- return createStateStore;
6412
- }
6413
- });
6414
4766
  Object.defineProperty(exports, "derivedContainers", {
6415
4767
  enumerable: true,
6416
4768
  get: function() {
@@ -6435,18 +4787,6 @@ Object.defineProperty(exports, "dockerCommand", {
6435
4787
  return dockerCommand;
6436
4788
  }
6437
4789
  });
6438
- Object.defineProperty(exports, "encryptSecrets", {
6439
- enumerable: true,
6440
- get: function() {
6441
- return encryptSecrets;
6442
- }
6443
- });
6444
- Object.defineProperty(exports, "encryption_exports", {
6445
- enumerable: true,
6446
- get: function() {
6447
- return encryption_exports;
6448
- }
6449
- });
6450
4790
  Object.defineProperty(exports, "envFor", {
6451
4791
  enumerable: true,
6452
4792
  get: function() {
@@ -6525,12 +4865,6 @@ Object.defineProperty(exports, "isPortAvailable", {
6525
4865
  return isPortAvailable;
6526
4866
  }
6527
4867
  });
6528
- Object.defineProperty(exports, "keyFingerprint", {
6529
- enumerable: true,
6530
- get: function() {
6531
- return keyFingerprint;
6532
- }
6533
- });
6534
4868
  Object.defineProperty(exports, "loadEnvFiles", {
6535
4869
  enumerable: true,
6536
4870
  get: function() {
@@ -6549,30 +4883,12 @@ Object.defineProperty(exports, "localRolePassword", {
6549
4883
  return localRolePassword;
6550
4884
  }
6551
4885
  });
6552
- Object.defineProperty(exports, "loginCommand", {
6553
- enumerable: true,
6554
- get: function() {
6555
- return loginCommand;
6556
- }
6557
- });
6558
- Object.defineProperty(exports, "logoutCommand", {
6559
- enumerable: true,
6560
- get: function() {
6561
- return logoutCommand;
6562
- }
6563
- });
6564
4886
  Object.defineProperty(exports, "networkEnv", {
6565
4887
  enumerable: true,
6566
4888
  get: function() {
6567
4889
  return networkEnv;
6568
4890
  }
6569
4891
  });
6570
- Object.defineProperty(exports, "newLockHolder", {
6571
- enumerable: true,
6572
- get: function() {
6573
- return newLockHolder;
6574
- }
6575
- });
6576
4892
  Object.defineProperty(exports, "pgClient", {
6577
4893
  enumerable: true,
6578
4894
  get: function() {
@@ -6615,12 +4931,6 @@ Object.defineProperty(exports, "primaryPortKey", {
6615
4931
  return primaryPortKey;
6616
4932
  }
6617
4933
  });
6618
- Object.defineProperty(exports, "prompt", {
6619
- enumerable: true,
6620
- get: function() {
6621
- return prompt;
6622
- }
6623
- });
6624
4934
  Object.defineProperty(exports, "providerOf", {
6625
4935
  enumerable: true,
6626
4936
  get: function() {
@@ -6699,42 +5009,18 @@ Object.defineProperty(exports, "siteDockerfile", {
6699
5009
  return siteDockerfile;
6700
5010
  }
6701
5011
  });
6702
- Object.defineProperty(exports, "sniffAllApps", {
6703
- enumerable: true,
6704
- get: function() {
6705
- return sniffAllApps;
6706
- }
6707
- });
6708
- Object.defineProperty(exports, "sniffAppEnvironment", {
6709
- enumerable: true,
6710
- get: function() {
6711
- return sniffAppEnvironment;
6712
- }
6713
- });
6714
5012
  Object.defineProperty(exports, "storageBackendFor", {
6715
5013
  enumerable: true,
6716
5014
  get: function() {
6717
5015
  return storageBackendFor;
6718
5016
  }
6719
5017
  });
6720
- Object.defineProperty(exports, "validateDokployToken", {
6721
- enumerable: true,
6722
- get: function() {
6723
- return validateDokployToken;
6724
- }
6725
- });
6726
5018
  Object.defineProperty(exports, "validateImageRef", {
6727
5019
  enumerable: true,
6728
5020
  get: function() {
6729
5021
  return validateImageRef;
6730
5022
  }
6731
5023
  });
6732
- Object.defineProperty(exports, "whoamiCommand", {
6733
- enumerable: true,
6734
- get: function() {
6735
- return whoamiCommand;
6736
- }
6737
- });
6738
5024
  Object.defineProperty(exports, "withGeneratedSecrets", {
6739
5025
  enumerable: true,
6740
5026
  get: function() {
@@ -6748,4 +5034,4 @@ Object.defineProperty(exports, "workspace_exports", {
6748
5034
  }
6749
5035
  });
6750
5036
 
6751
- //# sourceMappingURL=sniffer-ByhbEW5A.cjs.map
5037
+ //# sourceMappingURL=state-DF26e_lo.cjs.map