hatchkit 0.1.4 → 0.1.6

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 (71) hide show
  1. package/dist/adopt.d.ts +2 -0
  2. package/dist/adopt.d.ts.map +1 -0
  3. package/dist/adopt.js +819 -0
  4. package/dist/adopt.js.map +1 -0
  5. package/dist/completion.d.ts.map +1 -1
  6. package/dist/completion.js +3 -0
  7. package/dist/completion.js.map +1 -1
  8. package/dist/config.d.ts +30 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +108 -0
  11. package/dist/config.js.map +1 -1
  12. package/dist/deploy/coolify-app.d.ts +35 -0
  13. package/dist/deploy/coolify-app.d.ts.map +1 -0
  14. package/dist/deploy/coolify-app.js +238 -0
  15. package/dist/deploy/coolify-app.js.map +1 -0
  16. package/dist/deploy/pages.js +50 -9
  17. package/dist/deploy/pages.js.map +1 -1
  18. package/dist/deploy/rename-domain.d.ts.map +1 -1
  19. package/dist/deploy/rename-domain.js +26 -6
  20. package/dist/deploy/rename-domain.js.map +1 -1
  21. package/dist/deploy/rollback.d.ts +10 -0
  22. package/dist/deploy/rollback.d.ts.map +1 -0
  23. package/dist/deploy/rollback.js +295 -0
  24. package/dist/deploy/rollback.js.map +1 -0
  25. package/dist/deploy/terraform.d.ts +10 -1
  26. package/dist/deploy/terraform.d.ts.map +1 -1
  27. package/dist/deploy/terraform.js +177 -42
  28. package/dist/deploy/terraform.js.map +1 -1
  29. package/dist/doctor.d.ts.map +1 -1
  30. package/dist/doctor.js +25 -0
  31. package/dist/doctor.js.map +1 -1
  32. package/dist/explain.d.ts.map +1 -1
  33. package/dist/explain.js +5 -0
  34. package/dist/explain.js.map +1 -1
  35. package/dist/index.js +377 -122
  36. package/dist/index.js.map +1 -1
  37. package/dist/prompts.d.ts.map +1 -1
  38. package/dist/prompts.js +283 -11
  39. package/dist/prompts.js.map +1 -1
  40. package/dist/provision/stripe.d.ts +19 -0
  41. package/dist/provision/stripe.d.ts.map +1 -0
  42. package/dist/provision/stripe.js +58 -0
  43. package/dist/provision/stripe.js.map +1 -0
  44. package/dist/scaffold/dotenvx.d.ts.map +1 -1
  45. package/dist/scaffold/dotenvx.js +35 -11
  46. package/dist/scaffold/dotenvx.js.map +1 -1
  47. package/dist/scaffold/infra.d.ts +21 -1
  48. package/dist/scaffold/infra.d.ts.map +1 -1
  49. package/dist/scaffold/infra.js +66 -20
  50. package/dist/scaffold/infra.js.map +1 -1
  51. package/dist/status.d.ts.map +1 -1
  52. package/dist/status.js +7 -0
  53. package/dist/status.js.map +1 -1
  54. package/dist/utils/cloudflare-api.d.ts +23 -0
  55. package/dist/utils/cloudflare-api.d.ts.map +1 -1
  56. package/dist/utils/cloudflare-api.js +31 -0
  57. package/dist/utils/cloudflare-api.js.map +1 -1
  58. package/dist/utils/coolify-api.d.ts +64 -3
  59. package/dist/utils/coolify-api.d.ts.map +1 -1
  60. package/dist/utils/coolify-api.js +99 -3
  61. package/dist/utils/coolify-api.js.map +1 -1
  62. package/dist/utils/run-ledger.d.ts +68 -0
  63. package/dist/utils/run-ledger.d.ts.map +1 -0
  64. package/dist/utils/run-ledger.js +99 -0
  65. package/dist/utils/run-ledger.js.map +1 -0
  66. package/dist/utils/secrets.d.ts +2 -0
  67. package/dist/utils/secrets.d.ts.map +1 -1
  68. package/dist/utils/secrets.js +2 -0
  69. package/dist/utils/secrets.js.map +1 -1
  70. package/package.json +2 -2
  71. package/scripts/release-prep.mjs +130 -95
@@ -0,0 +1,295 @@
1
+ /*
2
+ * Rollback — print a tailored cleanup recipe for a partial `hatchkit
3
+ * create` and (optionally) execute it.
4
+ *
5
+ * Two entry points:
6
+ * - `handleCreateFailure(ledger, err)` — called from the create-flow
7
+ * try/catch. Prints the error, the recipe, and a 3-way prompt:
8
+ * roll back now / show recipe again / leave it. Destructive steps
9
+ * (rm -rf appDir, github repo delete, terraform destroy) get a
10
+ * per-step confirmation before they run, so a single 'y' at the
11
+ * top doesn't unleash a cascade of irreversible operations.
12
+ * - `runRollback(ledger, opts)` — used by `hatchkit destroy` for the
13
+ * same execution path, with `--yes` to skip per-step prompts.
14
+ *
15
+ * Steps undo in reverse order. Failures within an undo are logged but
16
+ * don't abort the rest of the rollback — we want to claw back as much
17
+ * state as we can and surface what's left.
18
+ */
19
+ import { existsSync, rmSync, unlinkSync } from "node:fs";
20
+ import { confirm, select } from "@inquirer/prompts";
21
+ import chalk from "chalk";
22
+ import { getCoolifyConfig, getDnsConfig } from "../config.js";
23
+ import { CoolifyApi } from "../utils/coolify-api.js";
24
+ import { exec } from "../utils/exec.js";
25
+ import { RunLedger } from "../utils/run-ledger.js";
26
+ import { deleteSecret } from "../utils/secrets.js";
27
+ /* ─────────────────────────────────────────────────────────────────────── */
28
+ /* Failure handler — called from the create-flow try/catch */
29
+ /* ─────────────────────────────────────────────────────────────────────── */
30
+ export async function handleCreateFailure(ledger, err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ console.log(chalk.bold.red(`\n ✗ hatchkit create failed: ${message}`));
33
+ if (ledger.steps.length === 0) {
34
+ console.log(chalk.dim(" No steps completed before failure — nothing to clean up.\n"));
35
+ ledger.delete();
36
+ return;
37
+ }
38
+ console.log(chalk.dim(` Completed ${ledger.steps.length} step${ledger.steps.length === 1 ? "" : "s"} before failing. ` +
39
+ `Ledger: ${ledger.path}\n`));
40
+ printRecipe(ledger);
41
+ // Loop the prompt so "show recipe" doesn't drop the user back into
42
+ // an unreviewed default.
43
+ for (;;) {
44
+ const choice = await select({
45
+ message: "How would you like to handle the partial state?",
46
+ choices: [
47
+ { name: "Roll back now (with confirmation per destructive step)", value: "rollback" },
48
+ { name: "Show the recipe again", value: "recipe" },
49
+ { name: "Leave it — I'll clean up later", value: "leave" },
50
+ ],
51
+ default: "leave",
52
+ });
53
+ if (choice === "recipe") {
54
+ printRecipe(ledger);
55
+ continue;
56
+ }
57
+ if (choice === "leave") {
58
+ console.log(chalk.dim(`\n Ledger preserved at ${ledger.path}. Run \`hatchkit destroy ${ledger.name}\` later to undo.\n`));
59
+ return;
60
+ }
61
+ await runRollback(ledger, { yes: false });
62
+ return;
63
+ }
64
+ }
65
+ /* ─────────────────────────────────────────────────────────────────────── */
66
+ /* Recipe printer — copy-pasteable bash commands */
67
+ /* ─────────────────────────────────────────────────────────────────────── */
68
+ export function printRecipe(ledger) {
69
+ console.log(chalk.bold(" ── Cleanup recipe ──────────────────────────────────────────\n"));
70
+ console.log(chalk.dim(" Run these in order to manually undo the partial create:\n"));
71
+ // Reverse order: undo the latest step first.
72
+ const lines = [];
73
+ for (const step of [...ledger.steps].reverse()) {
74
+ const cmd = recipeFor(step);
75
+ if (cmd)
76
+ lines.push(cmd);
77
+ }
78
+ for (const line of lines) {
79
+ console.log(` ${line}`);
80
+ }
81
+ console.log();
82
+ }
83
+ function recipeFor(step) {
84
+ switch (step.kind) {
85
+ case "mlService":
86
+ return chalk.dim(`# manual: delete ${step.name} from your ${step.platform} dashboard`);
87
+ case "coolifyDb":
88
+ return chalk.dim(`# manual: delete database ${step.uuid} from the Coolify dashboard`);
89
+ case "coolifyApp":
90
+ return chalk.dim(`# manual: delete application ${step.uuid} from the Coolify dashboard`);
91
+ case "terraformApplied":
92
+ return `cd ${shellEscape(step.stackDir)} && terraform destroy -var-file=${shellEscape(step.tfvarsPath)}`;
93
+ case "coolifyEnv":
94
+ return `rm ${shellEscape(step.path)}`;
95
+ case "tfvars":
96
+ return `rm ${shellEscape(step.path)}`;
97
+ case "glitchtip":
98
+ return `hatchkit remove ${shellEscape(step.project)} glitchtip --yes`;
99
+ case "github":
100
+ return `gh repo delete ${shellEscape(step.repo)} --yes`;
101
+ case "scaffold":
102
+ return `rm -rf ${shellEscape(step.path)}`;
103
+ case "keychain":
104
+ return `security delete-generic-password -s hatchkit -a ${shellEscape(step.account)}`;
105
+ }
106
+ }
107
+ function shellEscape(s) {
108
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(s))
109
+ return s;
110
+ return `'${s.replace(/'/g, "'\\''")}'`;
111
+ }
112
+ /* ─────────────────────────────────────────────────────────────────────── */
113
+ /* Executor — reverse-order undo */
114
+ /* ─────────────────────────────────────────────────────────────────────── */
115
+ export async function runRollback(ledger, opts = {}) {
116
+ console.log(chalk.bold("\n ── Rolling back ────────────────────────────────────────────\n"));
117
+ let undone = 0;
118
+ let skipped = 0;
119
+ let failed = 0;
120
+ const remaining = [];
121
+ for (const step of [...ledger.steps].reverse()) {
122
+ const label = describeStep(step);
123
+ // Per-step confirmation for the irreversible ones unless --yes.
124
+ if (!opts.yes && isDestructive(step)) {
125
+ const ok = await confirm({
126
+ message: `${chalk.red("Destructive:")} ${label}. Proceed?`,
127
+ default: false,
128
+ });
129
+ if (!ok) {
130
+ console.log(chalk.dim(` skipped: ${label}`));
131
+ skipped += 1;
132
+ remaining.push(step);
133
+ continue;
134
+ }
135
+ }
136
+ process.stdout.write(` ${label} … `);
137
+ try {
138
+ const result = await undoStep(step);
139
+ if (result === "skipped") {
140
+ console.log(chalk.dim("skipped"));
141
+ skipped += 1;
142
+ remaining.push(step);
143
+ }
144
+ else if (result === "not-found") {
145
+ console.log(chalk.dim("already gone"));
146
+ undone += 1;
147
+ }
148
+ else {
149
+ console.log(chalk.green("✓"));
150
+ undone += 1;
151
+ }
152
+ }
153
+ catch (err) {
154
+ console.log(chalk.red(`✗ ${err.message}`));
155
+ failed += 1;
156
+ remaining.push(step);
157
+ }
158
+ }
159
+ console.log(chalk.bold("\n ── Rollback summary ────────────────────────────────────────\n"));
160
+ console.log(` Undone: ${chalk.green(undone)}`);
161
+ console.log(` Skipped: ${chalk.dim(skipped)}`);
162
+ console.log(` Failed: ${failed > 0 ? chalk.red(failed) : chalk.dim(failed)}`);
163
+ if (remaining.length === 0) {
164
+ ledger.delete();
165
+ console.log(chalk.dim(`\n Ledger removed.\n`));
166
+ }
167
+ else {
168
+ // Replace the ledger with what's left so the next `hatchkit destroy`
169
+ // run picks up where we paused.
170
+ rewriteRemaining(ledger, remaining);
171
+ console.log(chalk.yellow(`\n ${remaining.length} step(s) still pending. Re-run \`hatchkit destroy ${ledger.name}\` to retry.\n`));
172
+ }
173
+ }
174
+ /** Rewrite the on-disk ledger to keep only the steps that weren't
175
+ * undone (because they failed or the user skipped them). Done by
176
+ * calling `start` to clobber the file, then re-recording each
177
+ * remaining step in original (forward) order. */
178
+ function rewriteRemaining(ledger, remaining) {
179
+ // Steps were processed in reverse; remaining is in reverse too.
180
+ // Restore original order so a future rollback walks them correctly.
181
+ const fresh = RunLedger.start(ledger.name);
182
+ for (const step of [...remaining].reverse()) {
183
+ fresh.record(step);
184
+ }
185
+ }
186
+ function isDestructive(step) {
187
+ return (step.kind === "scaffold" ||
188
+ step.kind === "github" ||
189
+ step.kind === "terraformApplied" ||
190
+ step.kind === "coolifyApp" ||
191
+ step.kind === "coolifyDb");
192
+ }
193
+ function describeStep(step) {
194
+ switch (step.kind) {
195
+ case "scaffold":
196
+ return `delete local repo ${chalk.cyan(step.path)}`;
197
+ case "github":
198
+ return `delete GitHub repo ${chalk.cyan(step.repo)}`;
199
+ case "glitchtip":
200
+ return `delete GlitchTip project ${chalk.cyan(step.project)}`;
201
+ case "tfvars":
202
+ return `remove ${chalk.cyan(step.path)}`;
203
+ case "coolifyEnv":
204
+ return `remove ${chalk.cyan(step.path)}`;
205
+ case "keychain":
206
+ return `delete keychain entry ${chalk.cyan(step.account)}`;
207
+ case "terraformApplied":
208
+ return `terraform destroy in ${chalk.cyan(step.stackDir)}`;
209
+ case "coolifyApp":
210
+ return `delete Coolify app ${chalk.cyan(step.uuid)}`;
211
+ case "coolifyDb":
212
+ return `delete Coolify db ${chalk.cyan(step.uuid)}`;
213
+ case "mlService":
214
+ return `${chalk.cyan(step.platform)} ML service ${chalk.cyan(step.name)}`;
215
+ }
216
+ }
217
+ async function undoStep(step) {
218
+ switch (step.kind) {
219
+ case "scaffold": {
220
+ if (!existsSync(step.path))
221
+ return "not-found";
222
+ rmSync(step.path, { recursive: true, force: true });
223
+ return "done";
224
+ }
225
+ case "github": {
226
+ const res = await exec("gh", ["repo", "delete", step.repo, "--yes"], { silent: true });
227
+ if (res.exitCode !== 0) {
228
+ // Already gone, or the user lacks delete:repo scope.
229
+ if (/not found|404/i.test(res.stderr ?? ""))
230
+ return "not-found";
231
+ throw new Error(`gh repo delete failed: ${res.stderr || res.stdout}`);
232
+ }
233
+ return "done";
234
+ }
235
+ case "glitchtip": {
236
+ const { deleteGlitchtipClient } = await import("../provision/glitchtip.js");
237
+ const result = await deleteGlitchtipClient(step.project);
238
+ return result === "not-found" ? "not-found" : "done";
239
+ }
240
+ case "tfvars":
241
+ case "coolifyEnv": {
242
+ if (!existsSync(step.path))
243
+ return "not-found";
244
+ unlinkSync(step.path);
245
+ return "done";
246
+ }
247
+ case "keychain": {
248
+ const ok = await deleteSecret(step.account);
249
+ return ok ? "done" : "not-found";
250
+ }
251
+ case "terraformApplied": {
252
+ // We need the same env vars `runTerraform` used. Re-derive from
253
+ // the user's saved DNS config; if creds aren't there anymore,
254
+ // the destroy won't work and we say so.
255
+ const dns = await getDnsConfig();
256
+ const env = {};
257
+ if (dns?.provider === "cloudflare" && dns.apiToken) {
258
+ env.TF_VAR_cloudflare_api_token = dns.apiToken;
259
+ }
260
+ else if (dns?.provider === "inwx" && dns.username && dns.password) {
261
+ env.TF_VAR_inwx_username = dns.username;
262
+ env.TF_VAR_inwx_password = dns.password;
263
+ }
264
+ else {
265
+ throw new Error("DNS credentials no longer in keychain — re-add them, then retry");
266
+ }
267
+ const res = await exec("terraform", ["destroy", "-auto-approve", `-var-file=${step.tfvarsPath}`], { cwd: step.stackDir, env, silent: true });
268
+ if (res.exitCode !== 0) {
269
+ throw new Error(`terraform destroy failed: ${res.stderr || res.stdout}`);
270
+ }
271
+ return "done";
272
+ }
273
+ case "coolifyApp": {
274
+ const cfg = await getCoolifyConfig();
275
+ if (!cfg)
276
+ throw new Error("Coolify config no longer present");
277
+ const api = new CoolifyApi({ url: cfg.url, token: cfg.token });
278
+ const result = await api.deleteApplication(step.uuid);
279
+ return result === "not-found" ? "not-found" : "done";
280
+ }
281
+ case "coolifyDb": {
282
+ const cfg = await getCoolifyConfig();
283
+ if (!cfg)
284
+ throw new Error("Coolify config no longer present");
285
+ const api = new CoolifyApi({ url: cfg.url, token: cfg.token });
286
+ const result = await api.deleteDatabase(step.uuid);
287
+ return result === "not-found" ? "not-found" : "done";
288
+ }
289
+ case "mlService":
290
+ // Per-platform delete is platform-specific (Modal/RunPod/HF/Replicate).
291
+ // Surface in the recipe but skip auto-undo for now.
292
+ return "skipped";
293
+ }
294
+ }
295
+ //# sourceMappingURL=rollback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollback.js","sourceRoot":"","sources":["../../src/deploy/rollback.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,13 @@
1
1
  import type { ProjectConfig } from "../prompts.js";
2
+ /** Result of `runTerraform`. Returns metadata the caller can record in
3
+ * the run ledger so a later rollback knows which stack to destroy. */
4
+ export interface RunTerraformResult {
5
+ /** Set when an apply actually ran. Undefined for manual-DNS or skip. */
6
+ applied?: {
7
+ stackDir: string;
8
+ tfvarsPath: string;
9
+ };
10
+ }
2
11
  /** Run Terraform for the project. */
3
- export declare function runTerraform(config: ProjectConfig, repoRoot: string): Promise<void>;
12
+ export declare function runTerraform(config: ProjectConfig, repoRoot: string): Promise<RunTerraformResult>;
4
13
  //# sourceMappingURL=terraform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terraform.d.ts","sourceRoot":"","sources":["../../src/deploy/terraform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAInD,qCAAqC;AACrC,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2FzF"}
1
+ {"version":3,"file":"terraform.d.ts","sourceRoot":"","sources":["../../src/deploy/terraform.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiBnD;uEACuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD;AAED,qCAAqC;AACrC,wBAAsB,YAAY,CAChC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAoI7B"}
@@ -1,37 +1,66 @@
1
- import { join } from "node:path";
1
+ import { promises as dnsPromises } from "node:dns";
2
+ import { select } from "@inquirer/prompts";
2
3
  import chalk from "chalk";
3
- import { getDnsConfig, getHetznerToken, getS3Config } from "../config.js";
4
+ import { getDnsConfig, getHetznerToken, getS3Config, promptAndSaveInwxRegistrarCreds, } from "../config.js";
5
+ import { resolveStackDir } from "../scaffold/infra.js";
6
+ import { CloudflareApi } from "../utils/cloudflare-api.js";
4
7
  import { exec, execStream } from "../utils/exec.js";
5
8
  import { InwxApi } from "../utils/inwx-api.js";
6
9
  /** Run Terraform for the project. */
7
10
  export async function runTerraform(config, repoRoot) {
8
11
  const hetznerToken = await getHetznerToken();
9
12
  const dnsConfig = await getDnsConfig();
10
- const stackDir = config.deployTarget === "new"
11
- ? join(repoRoot, "terraform", "stacks", "node-realtime")
12
- : join(repoRoot, "terraform", "stacks", "dns-only");
13
- const tfvarsFile = join(stackDir, `${config.name}.tfvars`);
14
- // Build env vars for Terraform
13
+ const dnsProvider = (dnsConfig?.provider ?? "inwx");
14
+ console.log(chalk.bold("\n ── Terraform ──────────────────────────────────────────────\n"));
15
+ // Manual DNS on an existing server: nothing for Terraform to do.
16
+ if (config.deployTarget === "existing" && dnsProvider === "manual") {
17
+ console.log(chalk.dim(" Manual DNS no Terraform stack to apply. Set DNS records yourself."));
18
+ return {};
19
+ }
20
+ const stackDir = resolveStackDir(repoRoot, config.deployTarget, dnsProvider);
21
+ if (!stackDir) {
22
+ console.log(chalk.dim(" No matching Terraform stack — skipping."));
23
+ return {};
24
+ }
25
+ const tfvarsFile = `${stackDir}/${config.name}.tfvars`;
26
+ // Build env vars for Terraform — only what the chosen stack needs.
15
27
  const env = {};
16
28
  if (hetznerToken) {
17
29
  env.TF_VAR_hcloud_token = hetznerToken;
18
30
  }
19
- // DNS credentials. The stack's dns_provider variable decides which
20
- // provider actually *applies* records, but the INWX Terraform provider
21
- // calls account.login during Configure() regardless, so valid INWX
22
- // creds must always be passed through when we have them. Either the
23
- // primary DNS creds (provider = "inwx") or the registrar creds
24
- // (provider = "cloudflare" with INWX still holding the domain) satisfy
25
- // that requirement.
26
- if (dnsConfig?.provider === "inwx") {
27
- env.TF_VAR_inwx_username = dnsConfig.username || "";
28
- env.TF_VAR_inwx_password = dnsConfig.password || "";
29
- }
30
- else if (dnsConfig?.provider === "cloudflare") {
31
- env.TF_VAR_cloudflare_api_token = dnsConfig.apiToken || "";
32
- if (dnsConfig.registrarUsername && dnsConfig.registrarPassword) {
33
- env.TF_VAR_inwx_username = dnsConfig.registrarUsername;
34
- env.TF_VAR_inwx_password = dnsConfig.registrarPassword;
31
+ // Preflight + DNS creds. The dns-only-* stacks each only declare one
32
+ // DNS provider, so we pass only that provider's creds. The legacy
33
+ // node-realtime stack still bundles both providers (count-gated
34
+ // modules) and inherits the older "INWX creds required even for CF"
35
+ // footgun fixing that is a follow-up.
36
+ let registrarPlan = null;
37
+ if (dnsProvider === "inwx") {
38
+ if (!dnsConfig?.username || !dnsConfig.password) {
39
+ throw new Error("INWX is configured as the DNS provider but credentials are missing. Re-run `hatchkit config add dns`.");
40
+ }
41
+ env.TF_VAR_inwx_username = dnsConfig.username;
42
+ env.TF_VAR_inwx_password = dnsConfig.password;
43
+ }
44
+ else if (dnsProvider === "cloudflare") {
45
+ if (!dnsConfig?.apiToken) {
46
+ throw new Error("Cloudflare is configured as the DNS provider but the API token is missing from the keychain.");
47
+ }
48
+ env.TF_VAR_cloudflare_api_token = dnsConfig.apiToken;
49
+ // dns-only-cloudflare doesn't ask for INWX creds at all. The
50
+ // node-realtime path still does (legacy footgun) — we mirror the
51
+ // old behavior there until that stack is split too.
52
+ if (config.deployTarget === "new") {
53
+ if (dnsConfig.registrarUsername && dnsConfig.registrarPassword) {
54
+ env.TF_VAR_inwx_username = dnsConfig.registrarUsername;
55
+ env.TF_VAR_inwx_password = dnsConfig.registrarPassword;
56
+ }
57
+ }
58
+ else {
59
+ // dns-only-cloudflare: figure out whether a registrar NS-flip is
60
+ // even needed *before* terraform runs, and prompt for INWX creds
61
+ // inline if it is. We only block on a working answer here so that
62
+ // the post-apply step has what it needs (or knows to skip).
63
+ registrarPlan = await preflightRegistrarFlip(config.baseDomain, dnsConfig);
35
64
  }
36
65
  }
37
66
  if (config.features.includes("s3") &&
@@ -43,7 +72,6 @@ export async function runTerraform(config, repoRoot) {
43
72
  env.TF_VAR_s3_secret_key = s3.secretKey;
44
73
  }
45
74
  }
46
- console.log(chalk.bold("\n ── Terraform ──────────────────────────────────────────────\n"));
47
75
  // Init
48
76
  const initResult = await exec("terraform", ["init"], {
49
77
  cwd: stackDir,
@@ -66,24 +94,136 @@ export async function runTerraform(config, repoRoot) {
66
94
  throw new Error("Terraform apply failed");
67
95
  }
68
96
  console.log(chalk.green("\n ✓ Terraform apply complete"));
69
- // Post-apply: if we just deployed to Cloudflare but the domain is
70
- // still registered at INWX, update INWX's delegated NS to point at
71
- // Cloudflare. This is the "auto-wire" step that would otherwise
72
- // require clicking through the INWX web UI for every domain.
73
- if (dnsConfig?.provider === "cloudflare" &&
74
- dnsConfig.registrarUsername &&
97
+ // Post-apply: NS-flip if dns-only-cloudflare path decided one was
98
+ // needed and we now have creds. Both decisions live in registrarPlan
99
+ // so this code doesn't re-derive intent.
100
+ if (registrarPlan?.action === "flip") {
101
+ await updateInwxNameserversFromTfOutput(stackDir, env, {
102
+ username: registrarPlan.registrarUsername,
103
+ password: registrarPlan.registrarPassword,
104
+ });
105
+ }
106
+ else if (registrarPlan?.action === "skip") {
107
+ console.log(chalk.dim(` (registrar NS-flip skipped — ${registrarPlan.reason})`));
108
+ }
109
+ else if (config.deployTarget === "new" &&
110
+ dnsProvider === "cloudflare" &&
111
+ dnsConfig?.registrarUsername &&
75
112
  dnsConfig.registrarPassword) {
76
- await updateInwxNameserversFromTfOutput(stackDir, env, dnsConfig);
113
+ // Legacy node-realtime path — preserve the original behavior.
114
+ await updateInwxNameserversFromTfOutput(stackDir, env, {
115
+ username: dnsConfig.registrarUsername,
116
+ password: dnsConfig.registrarPassword,
117
+ });
77
118
  }
119
+ return { applied: { stackDir, tfvarsPath: tfvarsFile } };
120
+ }
121
+ /**
122
+ * Decide whether the registrar NS-flip is worth doing for this deploy:
123
+ *
124
+ * 1. Look the domain up in Cloudflare → its assigned name_servers.
125
+ * 2. Resolve the current public NS for the domain.
126
+ * 3. If they already match → skip (NS already pointed at Cloudflare).
127
+ * 4. If they don't match → a flip is needed. Use saved INWX registrar
128
+ * creds if present; otherwise prompt for them inline (with an
129
+ * "I'll do it manually" escape hatch).
130
+ *
131
+ * Returns a plan describing what post-apply should do. Network errors
132
+ * during the lookup (CF API down, no internet) degrade gracefully into
133
+ * "skip" with a note — the user can re-run `hatchkit dns
134
+ * link-to-cloudflare` later.
135
+ */
136
+ async function preflightRegistrarFlip(domain, dnsConfig) {
137
+ console.log(chalk.dim("\n Checking whether the registrar NS-flip is needed..."));
138
+ // Strip subdomain — registrar delegation is at the apex.
139
+ const apex = apexDomain(domain);
140
+ let expectedNs;
141
+ try {
142
+ if (!dnsConfig.apiToken) {
143
+ return { action: "skip", reason: "no Cloudflare token to look up zone" };
144
+ }
145
+ const cf = new CloudflareApi({ token: dnsConfig.apiToken, accountId: dnsConfig.accountId });
146
+ const zone = await cf.getZoneByName(apex);
147
+ if (!zone) {
148
+ return {
149
+ action: "skip",
150
+ reason: `${apex} isn't in your Cloudflare account yet — add the zone, then re-run`,
151
+ };
152
+ }
153
+ expectedNs = zone.name_servers.map((n) => n.toLowerCase()).sort();
154
+ }
155
+ catch (err) {
156
+ return { action: "skip", reason: `Cloudflare lookup failed: ${err.message}` };
157
+ }
158
+ let currentNs;
159
+ try {
160
+ const ns = await dnsPromises.resolveNs(apex);
161
+ currentNs = ns.map((n) => n.toLowerCase()).sort();
162
+ }
163
+ catch {
164
+ // ENOTFOUND on a brand-new domain just means "no NS yet" — we
165
+ // definitely need the flip.
166
+ currentNs = [];
167
+ }
168
+ const same = currentNs.length === expectedNs.length && currentNs.every((n, i) => n === expectedNs[i]);
169
+ if (same) {
170
+ return { action: "skip", reason: "NS already point at Cloudflare" };
171
+ }
172
+ console.log(chalk.dim(` expected NS: ${expectedNs.join(", ")}`));
173
+ console.log(chalk.dim(` current NS: ${currentNs.length ? currentNs.join(", ") : "(none)"}`));
174
+ // Need a flip. Try saved creds first.
175
+ if (dnsConfig.registrarUsername && dnsConfig.registrarPassword) {
176
+ return {
177
+ action: "flip",
178
+ registrarUsername: dnsConfig.registrarUsername,
179
+ registrarPassword: dnsConfig.registrarPassword,
180
+ };
181
+ }
182
+ // No saved creds — ask inline.
183
+ console.log(chalk.yellow(`\n ${apex} needs its registrar NS pointed at Cloudflare, but INWX registrar creds aren't configured.`));
184
+ const choice = await select({
185
+ message: "How would you like to proceed?",
186
+ choices: [
187
+ {
188
+ name: "Provide INWX registrar credentials now (saved to keychain for reuse)",
189
+ value: "provide",
190
+ },
191
+ {
192
+ name: "Skip — I'll point NS at Cloudflare manually (or my registrar isn't INWX)",
193
+ value: "skip",
194
+ },
195
+ ],
196
+ });
197
+ if (choice === "skip") {
198
+ return { action: "skip", reason: "user opted to flip NS manually" };
199
+ }
200
+ const creds = await promptAndSaveInwxRegistrarCreds();
201
+ return {
202
+ action: "flip",
203
+ registrarUsername: creds.username,
204
+ registrarPassword: creds.password,
205
+ };
206
+ }
207
+ /** "ai.example.com" → "example.com". TLDs with second-level domains
208
+ * (".co.uk") aren't auto-detected — registrar lookups only happen on
209
+ * the apex anyway, so we just take the last two labels. Good enough
210
+ * for >99% of cases; users with country-code 2LDs can pre-configure
211
+ * registrar creds. */
212
+ function apexDomain(domain) {
213
+ const parts = domain.split(".");
214
+ if (parts.length <= 2)
215
+ return domain;
216
+ return parts.slice(-2).join(".");
78
217
  }
79
218
  /**
80
219
  * Read the stack's dns_provider + dns_domain + dns_nameservers outputs
81
220
  * and push them to INWX as the new delegated NS for that domain. No-op
82
- * if the stack didn't actually apply a Cloudflare zone (e.g. user kept
83
- * dns_provider = "inwx" in tfvars even though their CLI config says
84
- * Cloudflare).
221
+ * if the stack didn't actually apply a Cloudflare zone (e.g. legacy
222
+ * node-realtime stack with dns_provider = "inwx" in tfvars). Caller is
223
+ * responsible for the decision to call this — the registrar plan in
224
+ * `runTerraform` handles preflight + creds.
85
225
  */
86
- async function updateInwxNameserversFromTfOutput(stackDir, env, dnsConfig) {
226
+ async function updateInwxNameserversFromTfOutput(stackDir, env, registrarCreds) {
87
227
  const out = await exec("terraform", ["output", "-json"], {
88
228
  cwd: stackDir,
89
229
  env,
@@ -116,14 +256,9 @@ async function updateInwxNameserversFromTfOutput(stackDir, env, dnsConfig) {
116
256
  console.log(chalk.bold("\n ── INWX registrar: pointing NS at Cloudflare ────────────\n"));
117
257
  console.log(chalk.dim(` Domain: ${domain}`));
118
258
  console.log(chalk.dim(` New NS: ${nameservers.join(", ")}`));
119
- // registrarUsername/Password presence is guaranteed by the caller's
120
- // guard — assert here to keep the types clean.
121
- if (!dnsConfig.registrarUsername || !dnsConfig.registrarPassword) {
122
- return;
123
- }
124
259
  const inwx = new InwxApi({
125
- username: dnsConfig.registrarUsername,
126
- password: dnsConfig.registrarPassword,
260
+ username: registrarCreds.username,
261
+ password: registrarCreds.password,
127
262
  sandbox: process.env.INWX_SANDBOX === "1",
128
263
  });
129
264
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"terraform.js","sourceRoot":"","sources":["../../src/deploy/terraform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terraform.js","sourceRoot":"","sources":["../../src/deploy/terraform.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAsBA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAkVD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAYnE;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C5E"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAsBA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AA6WD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAanE;AAED,wBAAsB,SAAS,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C5E"}