hatchkit 0.1.2 → 0.1.3
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.
- package/dist/completion.d.ts +2 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +207 -0
- package/dist/completion.js.map +1 -0
- package/dist/config.d.ts +33 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +439 -127
- package/dist/config.js.map +1 -1
- package/dist/deploy/keys.d.ts +6 -2
- package/dist/deploy/keys.d.ts.map +1 -1
- package/dist/deploy/keys.js +16 -2
- package/dist/deploy/keys.js.map +1 -1
- package/dist/deploy/pages.d.ts +2 -0
- package/dist/deploy/pages.d.ts.map +1 -0
- package/dist/deploy/pages.js +537 -0
- package/dist/deploy/pages.js.map +1 -0
- package/dist/deploy/rename-domain.d.ts +55 -0
- package/dist/deploy/rename-domain.d.ts.map +1 -0
- package/dist/deploy/rename-domain.js +290 -0
- package/dist/deploy/rename-domain.js.map +1 -0
- package/dist/deploy/terraform.d.ts.map +1 -1
- package/dist/deploy/terraform.js +90 -0
- package/dist/deploy/terraform.js.map +1 -1
- package/dist/dns.d.ts +7 -0
- package/dist/dns.d.ts.map +1 -0
- package/dist/dns.js +124 -0
- package/dist/dns.js.map +1 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +368 -0
- package/dist/doctor.js.map +1 -0
- package/dist/explain.d.ts +4 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +173 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.js +477 -46
- package/dist/index.js.map +1 -1
- package/dist/provision/glitchtip.d.ts +3 -0
- package/dist/provision/glitchtip.d.ts.map +1 -1
- package/dist/provision/glitchtip.js +18 -0
- package/dist/provision/glitchtip.js.map +1 -1
- package/dist/provision/index.d.ts +26 -0
- package/dist/provision/index.d.ts.map +1 -1
- package/dist/provision/index.js +435 -60
- package/dist/provision/index.js.map +1 -1
- package/dist/provision/openpanel.d.ts +7 -0
- package/dist/provision/openpanel.d.ts.map +1 -1
- package/dist/provision/openpanel.js +113 -48
- package/dist/provision/openpanel.js.map +1 -1
- package/dist/provision/resend.d.ts +23 -1
- package/dist/provision/resend.d.ts.map +1 -1
- package/dist/provision/resend.js +62 -1
- package/dist/provision/resend.js.map +1 -1
- package/dist/provision/write-env.d.ts +31 -0
- package/dist/provision/write-env.d.ts.map +1 -0
- package/dist/provision/write-env.js +94 -0
- package/dist/provision/write-env.js.map +1 -0
- package/dist/scaffold/infra.d.ts.map +1 -1
- package/dist/scaffold/infra.js +18 -1
- package/dist/scaffold/infra.js.map +1 -1
- package/dist/status.d.ts +30 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +169 -0
- package/dist/status.js.map +1 -0
- package/dist/templates/addons/analytics/sentry.ts.hbs +6 -0
- package/dist/utils/cloudflare-api.d.ts +30 -0
- package/dist/utils/cloudflare-api.d.ts.map +1 -0
- package/dist/utils/cloudflare-api.js +85 -0
- package/dist/utils/cloudflare-api.js.map +1 -0
- package/dist/utils/coolify-api.d.ts +3 -1
- package/dist/utils/coolify-api.d.ts.map +1 -1
- package/dist/utils/coolify-api.js +29 -4
- package/dist/utils/coolify-api.js.map +1 -1
- package/dist/utils/inwx-api.d.ts +36 -0
- package/dist/utils/inwx-api.d.ts.map +1 -0
- package/dist/utils/inwx-api.js +105 -0
- package/dist/utils/inwx-api.js.map +1 -0
- package/dist/utils/secrets.d.ts +8 -1
- package/dist/utils/secrets.d.ts.map +1 -1
- package/dist/utils/secrets.js +8 -1
- package/dist/utils/secrets.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { confirm } from "@inquirer/prompts";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { ensureCoolify,
|
|
5
|
+
import { ensureCoolify, ensureGitHub, ensureHetzner, ensureS3, getConfig, getConfigPath, getMlServices, isFirstRun, reconfigureProvider, resetConfig, runOnboarding, } from "./config.js";
|
|
6
6
|
import { runCoolifySetup } from "./deploy/coolify.js";
|
|
7
7
|
import { setupGitHub } from "./deploy/github.js";
|
|
8
8
|
import { deployMlServices } from "./deploy/gpu.js";
|
|
9
9
|
import { pushProjectKeyToCoolify, showProjectKey } from "./deploy/keys.js";
|
|
10
10
|
import { runTerraform } from "./deploy/terraform.js";
|
|
11
11
|
import { collectProjectConfig } from "./prompts.js";
|
|
12
|
-
import { runProvision } from "./provision/index.js";
|
|
12
|
+
import { runProvision, runUnprovision } from "./provision/index.js";
|
|
13
13
|
import { scaffoldApp } from "./scaffold/app.js";
|
|
14
14
|
import { scaffoldInfra } from "./scaffold/infra.js";
|
|
15
15
|
import { mlEnvVarName, printMlSummary, resolveMlServices } from "./scaffold/ml-client.js";
|
|
@@ -35,10 +35,15 @@ async function main() {
|
|
|
35
35
|
console.log(getCliVersion());
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
if (
|
|
41
|
-
|
|
38
|
+
const isJson = args.includes("--json");
|
|
39
|
+
// Suppress the banner for machine-readable output so stdout is pure JSON.
|
|
40
|
+
if (!isJson) {
|
|
41
|
+
console.log(chalk.bold(`\n hatchkit v${getCliVersion()}\n`));
|
|
42
|
+
}
|
|
43
|
+
// Global --help / help subcommand (with optional topic).
|
|
44
|
+
if (command === "--help" || command === "-h" || command === "help") {
|
|
45
|
+
const topic = command === "help" ? args[1] : undefined;
|
|
46
|
+
printHelp(topic);
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
49
|
switch (command) {
|
|
@@ -53,12 +58,46 @@ async function main() {
|
|
|
53
58
|
return printHelp("config");
|
|
54
59
|
await handleConfig();
|
|
55
60
|
break;
|
|
61
|
+
case "status": {
|
|
62
|
+
if (args.includes("--help"))
|
|
63
|
+
return printHelp("status");
|
|
64
|
+
const { collectStatus, renderStatusHuman } = await import("./status.js");
|
|
65
|
+
const s = collectStatus();
|
|
66
|
+
if (isJson) {
|
|
67
|
+
console.log(JSON.stringify(s, null, 2));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log(renderStatusHuman(s));
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "explain": {
|
|
75
|
+
if (args.includes("--help"))
|
|
76
|
+
return printHelp("explain");
|
|
77
|
+
const { renderExplain } = await import("./explain.js");
|
|
78
|
+
console.log(renderExplain({ json: isJson }));
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case "completion": {
|
|
82
|
+
if (args.includes("--help"))
|
|
83
|
+
return printHelp("completion");
|
|
84
|
+
const { renderCompletion } = await import("./completion.js");
|
|
85
|
+
const shell = (args[1] ?? "").toLowerCase();
|
|
86
|
+
if (shell !== "zsh" && shell !== "bash" && shell !== "fish") {
|
|
87
|
+
console.log("Usage: hatchkit completion <zsh|bash|fish>");
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
console.log(renderCompletion(shell));
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
56
93
|
case "create":
|
|
57
|
-
case undefined:
|
|
58
94
|
if (args.includes("--help"))
|
|
59
95
|
return printHelp("create");
|
|
60
96
|
await handleCreate();
|
|
61
97
|
break;
|
|
98
|
+
case undefined:
|
|
99
|
+
await handleNoArgs();
|
|
100
|
+
break;
|
|
62
101
|
case "update":
|
|
63
102
|
if (args.includes("--help"))
|
|
64
103
|
return printHelp("update");
|
|
@@ -74,10 +113,72 @@ async function main() {
|
|
|
74
113
|
return printHelp("add");
|
|
75
114
|
await handleAdd();
|
|
76
115
|
break;
|
|
116
|
+
case "remove":
|
|
117
|
+
if (args.includes("--help"))
|
|
118
|
+
return printHelp("remove");
|
|
119
|
+
await handleRemove();
|
|
120
|
+
break;
|
|
121
|
+
case "rename-domain": {
|
|
122
|
+
if (args.includes("--help"))
|
|
123
|
+
return printHelp("rename-domain");
|
|
124
|
+
const { runRenameDomainCli } = await import("./deploy/rename-domain.js");
|
|
125
|
+
await runRenameDomainCli(args.slice(1), MONOREPO_ROOT);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "doctor": {
|
|
129
|
+
if (args.includes("--help"))
|
|
130
|
+
return printHelp("doctor");
|
|
131
|
+
const { runDoctor } = await import("./doctor.js");
|
|
132
|
+
await runDoctor({ json: isJson });
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "dns": {
|
|
136
|
+
if (args.includes("--help"))
|
|
137
|
+
return printHelp("dns");
|
|
138
|
+
await handleDns();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "gh-pages":
|
|
142
|
+
case "pages": {
|
|
143
|
+
if (args.includes("--help"))
|
|
144
|
+
return printHelp("gh-pages");
|
|
145
|
+
if (command === "pages") {
|
|
146
|
+
console.log(chalk.yellow(" Note: `hatchkit pages` has been renamed to `hatchkit gh-pages`."));
|
|
147
|
+
}
|
|
148
|
+
const { runPagesSetup } = await import("./deploy/pages.js");
|
|
149
|
+
await runPagesSetup(resolve("."));
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
77
152
|
default:
|
|
78
153
|
printHelp();
|
|
79
154
|
}
|
|
80
155
|
}
|
|
156
|
+
/** No-args: show the status-aware menu. If stdin is a TTY, also offer
|
|
157
|
+
* to kick off the most likely next step (setup or create). Agents
|
|
158
|
+
* running non-interactively just get the menu + exit 0. */
|
|
159
|
+
async function handleNoArgs() {
|
|
160
|
+
const { collectStatus, renderMenu } = await import("./status.js");
|
|
161
|
+
const s = collectStatus();
|
|
162
|
+
console.log(renderMenu(s));
|
|
163
|
+
if (!process.stdin.isTTY)
|
|
164
|
+
return;
|
|
165
|
+
const hasCore = s.providers.find((p) => p.key === "coolify")?.configured &&
|
|
166
|
+
s.providers.find((p) => p.key === "hetzner")?.configured &&
|
|
167
|
+
s.providers.find((p) => p.key === "dns")?.configured &&
|
|
168
|
+
s.providers.find((p) => p.key === "github")?.configured;
|
|
169
|
+
if (!hasCore) {
|
|
170
|
+
const ok = await confirm({ message: "Run `hatchkit setup` now?", default: true });
|
|
171
|
+
if (ok)
|
|
172
|
+
await runOnboarding();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const ok = await confirm({
|
|
176
|
+
message: "Scaffold a new project now (`hatchkit create`)?",
|
|
177
|
+
default: true,
|
|
178
|
+
});
|
|
179
|
+
if (ok)
|
|
180
|
+
await handleCreate();
|
|
181
|
+
}
|
|
81
182
|
async function handleKeys() {
|
|
82
183
|
const sub = args[1];
|
|
83
184
|
const projectName = args[2];
|
|
@@ -85,9 +186,10 @@ async function handleKeys() {
|
|
|
85
186
|
console.log("Usage: hatchkit keys <show|push> <project-name>");
|
|
86
187
|
process.exit(1);
|
|
87
188
|
}
|
|
189
|
+
const isJson = args.includes("--json");
|
|
88
190
|
switch (sub) {
|
|
89
191
|
case "show":
|
|
90
|
-
await showProjectKey(projectName);
|
|
192
|
+
await showProjectKey(projectName, { json: isJson });
|
|
91
193
|
break;
|
|
92
194
|
case "push":
|
|
93
195
|
await pushProjectKeyToCoolify(projectName);
|
|
@@ -142,7 +244,126 @@ async function handleAdd() {
|
|
|
142
244
|
}
|
|
143
245
|
services = requested;
|
|
144
246
|
}
|
|
145
|
-
|
|
247
|
+
// Flag parsing:
|
|
248
|
+
// --no-write → never write; print a cache summary only
|
|
249
|
+
// --enable-dev-obs → also populate .env.development with GlitchTip/OpenPanel creds
|
|
250
|
+
// --surfaces=<shared|separate|server-only|client-only>
|
|
251
|
+
// --server-dir <path> → absolute or project-relative env dir for the server
|
|
252
|
+
// --client-dir <path> → same for the client
|
|
253
|
+
// (no surface flags) → prompt interactively
|
|
254
|
+
const noWrite = args.includes("--no-write");
|
|
255
|
+
const enableDevObs = args.includes("--enable-dev-obs");
|
|
256
|
+
const surfaceFlag = args.find((a) => a.startsWith("--surfaces="))?.slice("--surfaces=".length);
|
|
257
|
+
const serverDirIdx = args.indexOf("--server-dir");
|
|
258
|
+
const clientDirIdx = args.indexOf("--client-dir");
|
|
259
|
+
const serverDirFlag = serverDirIdx >= 0 ? args[serverDirIdx + 1] : undefined;
|
|
260
|
+
const clientDirFlag = clientDirIdx >= 0 ? args[clientDirIdx + 1] : undefined;
|
|
261
|
+
const { resolve: resolvePath } = await import("node:path");
|
|
262
|
+
const validSurfaceModes = ["shared", "separate", "server-only", "client-only"];
|
|
263
|
+
let surfaces = undefined;
|
|
264
|
+
if (noWrite) {
|
|
265
|
+
surfaces = false;
|
|
266
|
+
}
|
|
267
|
+
else if (surfaceFlag || serverDirFlag || clientDirFlag) {
|
|
268
|
+
// Non-interactive surface config: require every field we need.
|
|
269
|
+
if (!surfaceFlag || !validSurfaceModes.includes(surfaceFlag)) {
|
|
270
|
+
console.log(chalk.red(` --surfaces=<mode> is required when --server-dir/--client-dir is passed.\n Valid: ${validSurfaceModes.join(", ")}`));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
const mode = surfaceFlag;
|
|
274
|
+
const needsServer = mode === "shared" || mode === "separate" || mode === "server-only";
|
|
275
|
+
const needsClient = mode === "shared" || mode === "separate" || mode === "client-only";
|
|
276
|
+
if (needsServer && !serverDirFlag) {
|
|
277
|
+
console.log(chalk.red(" --server-dir <path> is required for this --surfaces mode."));
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
if (needsClient && !clientDirFlag) {
|
|
281
|
+
console.log(chalk.red(" --client-dir <path> is required for this --surfaces mode."));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
surfaces = {
|
|
285
|
+
mode,
|
|
286
|
+
serverEnvDir: needsServer ? resolvePath(serverDirFlag) : undefined,
|
|
287
|
+
clientEnvDir: needsClient ? resolvePath(clientDirFlag) : undefined,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
await runProvision({ baseName, services, surfaces, enableDevObs });
|
|
291
|
+
}
|
|
292
|
+
async function handleRemove() {
|
|
293
|
+
// Mirrors handleAdd: `hatchkit remove [<name>] [<services>] [--dry-run] [--yes]`
|
|
294
|
+
// hatchkit remove (fully interactive)
|
|
295
|
+
// hatchkit remove raptor-runner (prompts for services)
|
|
296
|
+
// hatchkit remove raptor-runner all
|
|
297
|
+
// hatchkit remove raptor-runner glitchtip,resend
|
|
298
|
+
// hatchkit remove raptor-runner all --yes (skip confirmation)
|
|
299
|
+
const positional = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
300
|
+
const dryRun = args.includes("--dry-run");
|
|
301
|
+
const skipConfirm = args.includes("--yes") || args.includes("-y");
|
|
302
|
+
let baseName = positional[0];
|
|
303
|
+
const rawService = positional[1];
|
|
304
|
+
const allServices = ["glitchtip", "openpanel", "resend"];
|
|
305
|
+
if (!baseName) {
|
|
306
|
+
const { input } = await import("@inquirer/prompts");
|
|
307
|
+
const { validateProjectName } = await import("./utils/validate.js");
|
|
308
|
+
baseName = await input({
|
|
309
|
+
message: "Project name to remove (e.g. raptor-runner):",
|
|
310
|
+
validate: validateProjectName,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
let services;
|
|
314
|
+
if (!rawService) {
|
|
315
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
316
|
+
services = await checkbox({
|
|
317
|
+
message: "Which services to remove (-dev AND -prod clients each)?",
|
|
318
|
+
choices: [
|
|
319
|
+
{ name: "GlitchTip (deletes the project)", value: "glitchtip", checked: true },
|
|
320
|
+
{ name: "OpenPanel (deletes the project)", value: "openpanel", checked: true },
|
|
321
|
+
{ name: "Resend (deletes the API key)", value: "resend", checked: true },
|
|
322
|
+
],
|
|
323
|
+
required: true,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
else if (rawService === "all") {
|
|
327
|
+
services = allServices;
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const requested = rawService.split(",").map((s) => s.trim().toLowerCase());
|
|
331
|
+
const invalid = requested.filter((s) => !allServices.includes(s));
|
|
332
|
+
if (invalid.length > 0) {
|
|
333
|
+
console.log(chalk.red(` Unknown service(s): ${invalid.join(", ")}`));
|
|
334
|
+
console.log(chalk.dim(` Valid: ${allServices.join(", ")}, or 'all'`));
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
services = requested;
|
|
338
|
+
}
|
|
339
|
+
// Confirmation — deletion is permanent upstream. Skip on --yes or --dry-run.
|
|
340
|
+
if (!skipConfirm && !dryRun) {
|
|
341
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
342
|
+
const ok = await confirm({
|
|
343
|
+
message: `Delete -dev and -prod clients of "${baseName}" from ${services.join(", ")}? This can't be undone.`,
|
|
344
|
+
default: false,
|
|
345
|
+
});
|
|
346
|
+
if (!ok) {
|
|
347
|
+
console.log(chalk.dim(" Cancelled."));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
await runUnprovision({ baseName, services, dryRun });
|
|
352
|
+
}
|
|
353
|
+
async function handleDns() {
|
|
354
|
+
const sub = args[1];
|
|
355
|
+
switch (sub) {
|
|
356
|
+
case "link-to-cloudflare": {
|
|
357
|
+
const rest = args.slice(2);
|
|
358
|
+
const dryRun = rest.includes("--dry-run");
|
|
359
|
+
const domains = rest.filter((a) => !a.startsWith("--"));
|
|
360
|
+
const { runDnsLinkToCloudflare } = await import("./dns.js");
|
|
361
|
+
await runDnsLinkToCloudflare({ domains, dryRun });
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
default:
|
|
365
|
+
printHelp("dns");
|
|
366
|
+
}
|
|
146
367
|
}
|
|
147
368
|
// ---------------------------------------------------------------------------
|
|
148
369
|
// Commands
|
|
@@ -373,22 +594,12 @@ async function handleConfig() {
|
|
|
373
594
|
const isGpuPlatform = (p) => gpuPlatforms.includes(p);
|
|
374
595
|
switch (provider) {
|
|
375
596
|
case "coolify":
|
|
376
|
-
await ensureCoolify();
|
|
377
|
-
break;
|
|
378
597
|
case "hetzner":
|
|
379
|
-
await ensureHetzner();
|
|
380
|
-
break;
|
|
381
598
|
case "dns":
|
|
382
|
-
await ensureDns();
|
|
383
|
-
break;
|
|
384
599
|
case "glitchtip":
|
|
385
|
-
await ensureGlitchtip();
|
|
386
|
-
break;
|
|
387
600
|
case "openpanel":
|
|
388
|
-
await ensureOpenpanel();
|
|
389
|
-
break;
|
|
390
601
|
case "resend":
|
|
391
|
-
await
|
|
602
|
+
await reconfigureProvider(provider);
|
|
392
603
|
break;
|
|
393
604
|
case "s3": {
|
|
394
605
|
const { select } = await import("@inquirer/prompts");
|
|
@@ -400,7 +611,7 @@ async function handleConfig() {
|
|
|
400
611
|
{ name: "R2", value: "r2" },
|
|
401
612
|
],
|
|
402
613
|
});
|
|
403
|
-
await
|
|
614
|
+
await reconfigureProvider(`s3.${p}`);
|
|
404
615
|
break;
|
|
405
616
|
}
|
|
406
617
|
default:
|
|
@@ -409,7 +620,7 @@ async function handleConfig() {
|
|
|
409
620
|
console.log(chalk.dim(" Valid: coolify, hetzner, dns, s3, modal, runpod, hf, replicate, glitchtip, openpanel, resend"));
|
|
410
621
|
return;
|
|
411
622
|
}
|
|
412
|
-
await
|
|
623
|
+
await reconfigureProvider(`gpu.${provider}`);
|
|
413
624
|
}
|
|
414
625
|
break;
|
|
415
626
|
}
|
|
@@ -520,37 +731,199 @@ function printHelp(topic) {
|
|
|
520
731
|
|
|
521
732
|
${chalk.bold("Removal is not supported.")} Removing features could delete
|
|
522
733
|
user code — remove manually + edit the manifest.
|
|
734
|
+
`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (topic === "gh-pages") {
|
|
738
|
+
console.log(`
|
|
739
|
+
${chalk.bold("hatchkit gh-pages")} — wire GitHub Pages for the current repo
|
|
740
|
+
|
|
741
|
+
${chalk.bold("Usage:")}
|
|
742
|
+
cd <project-dir> && hatchkit gh-pages
|
|
743
|
+
|
|
744
|
+
${chalk.bold("What it does:")}
|
|
745
|
+
1. Reads the repo via \`gh repo view\` (must be a GitHub repo you own).
|
|
746
|
+
2. Scans the repo root + ${chalk.dim("docs/ site/ www/ web/")} for candidate sites:
|
|
747
|
+
- ${chalk.cyan("jekyll")} (Gemfile + _config.yml)
|
|
748
|
+
- ${chalk.cyan("node-build")} (package.json with a \`build\` script)
|
|
749
|
+
- ${chalk.cyan("static")} (index.html)
|
|
750
|
+
If multiple sites are found, prompts you to pick. If none are
|
|
751
|
+
found, prompts for kind + location manually.
|
|
752
|
+
3. Enables Pages via the GitHub API with ${chalk.dim("build_type=workflow")}.
|
|
753
|
+
4. Writes ${chalk.cyan(".github/workflows/gh-pages.yml")} tailored to the site kind.
|
|
754
|
+
Refuses to overwrite any existing Pages workflow in the repo.
|
|
755
|
+
5. Optionally registers a custom domain + wires DNS:
|
|
756
|
+
- Cloudflare: auto-configured via API (uses your stored token)
|
|
757
|
+
- INWX / manual: prints the records you need to add
|
|
758
|
+
Also writes a ${chalk.cyan("CNAME")} file into the published folder (or
|
|
759
|
+
${chalk.dim("public/")} for build-step projects).
|
|
760
|
+
|
|
761
|
+
${chalk.bold("After running:")}
|
|
762
|
+
git add -A && git commit -m "ci: deploy to GitHub Pages" && git push
|
|
763
|
+
|
|
764
|
+
${chalk.bold("Notes:")}
|
|
765
|
+
- Private repos need a paid GitHub plan for Pages. Free-tier repos
|
|
766
|
+
must be made public first.
|
|
767
|
+
- For ${chalk.dim("node-build")} sites, confirm the detected publish dir matches what
|
|
768
|
+
your build tool actually outputs (Vite → dist, CRA → build, etc).
|
|
769
|
+
- Monorepos / hybrids: if both the root and ${chalk.dim("docs/")} have sites, you'll
|
|
770
|
+
be prompted to pick one. Run the command twice if you want both.
|
|
771
|
+
`);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (topic === "dns") {
|
|
775
|
+
console.log(`
|
|
776
|
+
${chalk.bold("hatchkit dns")} — DNS reconciliation helpers
|
|
777
|
+
|
|
778
|
+
${chalk.bold("Subcommands:")}
|
|
779
|
+
link-to-cloudflare [domain...]
|
|
780
|
+
For each Cloudflare zone, push its nameservers to INWX as the
|
|
781
|
+
registrar delegation. Use after importing zones into Cloudflare
|
|
782
|
+
when you don't want to click through INWX per-domain.
|
|
783
|
+
|
|
784
|
+
No args → processes every zone the token can see.
|
|
785
|
+
Args → space-separated domain names, filters to those.
|
|
786
|
+
${chalk.dim("--dry-run")} → print-only, no API calls.
|
|
787
|
+
${chalk.dim("INWX_SANDBOX=1")} → use the OTE sandbox instead of production.
|
|
788
|
+
|
|
789
|
+
${chalk.bold("Prerequisites:")}
|
|
790
|
+
Run ${chalk.cyan("hatchkit config add dns")} and choose Cloudflare, then answer
|
|
791
|
+
${chalk.cyan("yes")} to "Is INWX your domain registrar?" when prompted.
|
|
792
|
+
`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
if (topic === "doctor") {
|
|
796
|
+
console.log(`
|
|
797
|
+
${chalk.bold("hatchkit doctor")} — verify every configured provider
|
|
798
|
+
|
|
799
|
+
Runs a read-only API call against each provider whose credentials are
|
|
800
|
+
stored (Coolify /version, Hetzner /servers, Cloudflare /tokens/verify,
|
|
801
|
+
Resend /domains, …). Reports ok / fail / not-configured per provider
|
|
802
|
+
and exits non-zero if any check fails. Safe to run repeatedly.
|
|
523
803
|
`);
|
|
524
804
|
return;
|
|
525
805
|
}
|
|
526
806
|
if (topic === "add") {
|
|
527
807
|
console.log(`
|
|
528
|
-
${chalk.bold("hatchkit add")} —
|
|
808
|
+
${chalk.bold("hatchkit add")} — provision per-project clients and write env files
|
|
529
809
|
|
|
530
810
|
${chalk.bold("Usage:")}
|
|
531
|
-
hatchkit add [<project-name>] [<services>]
|
|
811
|
+
hatchkit add [<project-name>] [<services>] [flags]
|
|
812
|
+
|
|
813
|
+
${chalk.bold("What it does:")}
|
|
814
|
+
· GlitchTip / OpenPanel: ${chalk.bold("one project per product")}, events tagged by
|
|
815
|
+
\`environment\` so dev / staging / prod share the same dashboard.
|
|
816
|
+
Written to ${chalk.cyan(".env.production")} only — dev noise pollutes real metrics.
|
|
817
|
+
Pass ${chalk.cyan("--enable-dev-obs")} to populate ${chalk.cyan(".env.development")} too.
|
|
818
|
+
· Resend: separate ${chalk.cyan("-dev")} and ${chalk.cyan("-prod")} API keys (audience
|
|
819
|
+
safety). Written to the server's dev + prod env respectively.
|
|
820
|
+
· ${chalk.cyan(".env.production")} is dotenvx-encrypted — commit-safe.
|
|
821
|
+
${chalk.cyan(".env.development")} is plaintext — gitignored, not encrypted.
|
|
822
|
+
· A 0600 cache of every value is saved under
|
|
823
|
+
${chalk.dim("<config-dir>/provisioned/<project>.*.env")} for recoverability.
|
|
824
|
+
${chalk.dim("Secret values never hit stdout.")}
|
|
825
|
+
|
|
826
|
+
${chalk.bold("Surfaces:")}
|
|
827
|
+
hatchkit asks which surfaces your project has. Options:
|
|
828
|
+
· ${chalk.cyan("shared")} — server + client, one obs project (recommended)
|
|
829
|
+
· ${chalk.cyan("server-only")} — no browser bundle (API, CLI, worker)
|
|
830
|
+
· ${chalk.cyan("client-only")} — static site / SPA with no backend
|
|
831
|
+
· ${chalk.cyan("separate")} — server + client, one obs project per surface
|
|
832
|
+
|
|
833
|
+
Env for each surface is written to its own directory (e.g.
|
|
834
|
+
${chalk.dim("packages/server/.env.production")}, ${chalk.dim("packages/client/.env.production")}).
|
|
835
|
+
|
|
836
|
+
${chalk.bold("Services:")}
|
|
837
|
+
glitchtip GLITCHTIP_DSN (server) / PUBLIC_GLITCHTIP_DSN (client)
|
|
838
|
+
openpanel OPENPANEL_* (server) / PUBLIC_OPENPANEL_* (client)
|
|
839
|
+
resend RESEND_API_KEY (server only)
|
|
840
|
+
|
|
841
|
+
${chalk.bold("Flags:")}
|
|
842
|
+
--enable-dev-obs Also populate .env.development with obs creds.
|
|
843
|
+
--no-write Skip writing; save 0600 cache only.
|
|
844
|
+
--surfaces=<mode> shared | server-only | client-only | separate
|
|
845
|
+
--server-dir <path> Server env directory (skips prompt when set).
|
|
846
|
+
--client-dir <path> Client env directory (skips prompt when set).
|
|
532
847
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
848
|
+
${chalk.bold("Examples:")}
|
|
849
|
+
hatchkit add
|
|
850
|
+
hatchkit add raptor-runner
|
|
851
|
+
hatchkit add raptor-runner all --enable-dev-obs
|
|
852
|
+
hatchkit add raptor-runner glitchtip,resend --no-write
|
|
853
|
+
hatchkit add raptor-runner all --surfaces=shared \\
|
|
854
|
+
--server-dir ./raptor-runner/packages/server \\
|
|
855
|
+
--client-dir ./raptor-runner/packages/client
|
|
856
|
+
`);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (topic === "remove") {
|
|
860
|
+
console.log(`
|
|
861
|
+
${chalk.bold("hatchkit remove")} — inverse of ${chalk.cyan("add")}: tear down per-project clients
|
|
862
|
+
|
|
863
|
+
${chalk.bold("Usage:")}
|
|
864
|
+
hatchkit remove [<project-name>] [<services>] [--dry-run] [--yes]
|
|
865
|
+
|
|
866
|
+
Both positional args are optional — anything missing is prompted for.
|
|
867
|
+
${chalk.dim("(<services> is 'all', a single service, or a comma-separated list.)")}
|
|
536
868
|
|
|
537
869
|
${chalk.bold("What it does:")}
|
|
538
|
-
For every selected service,
|
|
870
|
+
For every selected service, deletes both clients:
|
|
539
871
|
- ${chalk.cyan("<project-name>-dev")}
|
|
540
872
|
- ${chalk.cyan("<project-name>-prod")}
|
|
541
|
-
|
|
873
|
+
Also removes the local env cache at
|
|
542
874
|
${chalk.dim("<config-dir>/provisioned/<project-name>.{dev,prod}.env")}.
|
|
543
875
|
|
|
876
|
+
Re-runs are idempotent — missing upstream resources log
|
|
877
|
+
${chalk.dim("already gone")} and keep going.
|
|
878
|
+
|
|
544
879
|
${chalk.bold("Services:")}
|
|
545
|
-
glitchtip
|
|
546
|
-
openpanel
|
|
547
|
-
resend
|
|
880
|
+
glitchtip Deletes the GlitchTip project
|
|
881
|
+
openpanel Deletes the OpenPanel project (and clears cached creds)
|
|
882
|
+
resend Finds API keys by name and deletes them
|
|
883
|
+
|
|
884
|
+
${chalk.bold("Options:")}
|
|
885
|
+
--dry-run Print what would be deleted; hit no APIs, remove no files.
|
|
886
|
+
--yes, -y Skip the interactive confirmation prompt.
|
|
548
887
|
|
|
549
888
|
${chalk.bold("Examples:")}
|
|
550
|
-
hatchkit
|
|
551
|
-
hatchkit
|
|
552
|
-
hatchkit
|
|
553
|
-
|
|
889
|
+
hatchkit remove raptor-runner all
|
|
890
|
+
hatchkit remove raptor-runner glitchtip,resend --dry-run
|
|
891
|
+
hatchkit remove raptor-runner all --yes
|
|
892
|
+
`);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (topic === "rename-domain") {
|
|
896
|
+
console.log(`
|
|
897
|
+
${chalk.bold("hatchkit rename-domain")} — move a project to a new domain
|
|
898
|
+
|
|
899
|
+
${chalk.bold("Usage:")}
|
|
900
|
+
cd <project-dir> && hatchkit rename-domain --to <new-domain>
|
|
901
|
+
hatchkit rename-domain --dir <project-dir> --to <new-domain> --dry-run
|
|
902
|
+
|
|
903
|
+
${chalk.bold("What it rewrites:")}
|
|
904
|
+
${chalk.cyan(".hatchkit.json")} (manifest domain)
|
|
905
|
+
${chalk.cyan("infra/terraform/stacks/<stack>/<name>.tfvars")} (domain + subdomain keys)
|
|
906
|
+
${chalk.cyan("infra/stacks/<name>.env")} (APP_DOMAIN +
|
|
907
|
+
any line that mentions the
|
|
908
|
+
old full domain; skips
|
|
909
|
+
COOLIFY_URL)
|
|
910
|
+
|
|
911
|
+
${chalk.bold("What it does NOT touch (you run these manually):")}
|
|
912
|
+
- ${chalk.dim("terraform apply")} — review the plan before destroying old records.
|
|
913
|
+
- Coolify app FQDN + redeploy — UI or API. New TLS cert: 1-3 min.
|
|
914
|
+
- ${chalk.dim("hatchkit dns link-to-cloudflare")} if NS flip is needed.
|
|
915
|
+
- OAuth redirect URIs, Stripe webhooks, app-code references.
|
|
916
|
+
|
|
917
|
+
${chalk.bold("Options:")}
|
|
918
|
+
--to <domain> Target domain (prompted if omitted).
|
|
919
|
+
--dir <path> Project dir (defaults to cwd).
|
|
920
|
+
--dry-run Show the plan; don't write.
|
|
921
|
+
--yes, -y Skip the confirmation prompt.
|
|
922
|
+
|
|
923
|
+
${chalk.bold("Example:")}
|
|
924
|
+
cd ~/src/my-project
|
|
925
|
+
hatchkit rename-domain --to my-project.com --dry-run
|
|
926
|
+
hatchkit rename-domain --to my-project.com
|
|
554
927
|
`);
|
|
555
928
|
return;
|
|
556
929
|
}
|
|
@@ -559,30 +932,88 @@ function printHelp(topic) {
|
|
|
559
932
|
${chalk.bold("hatchkit config")} — manage provider credentials
|
|
560
933
|
|
|
561
934
|
${chalk.bold("Subcommands:")}
|
|
562
|
-
config Show status of every configured provider
|
|
935
|
+
config Show status of every configured provider (alias: \`status\`)
|
|
563
936
|
config add <p> Configure a provider
|
|
564
|
-
(coolify, hetzner, dns, s3, modal, runpod, hf, replicate
|
|
937
|
+
(coolify, hetzner, dns, s3, modal, runpod, hf, replicate,
|
|
938
|
+
glitchtip, openpanel, resend)
|
|
565
939
|
config reset Clear ALL CLI config (providers, tokens, ML registry, ports)
|
|
940
|
+
`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (topic === "status") {
|
|
944
|
+
console.log(`
|
|
945
|
+
${chalk.bold("hatchkit status")} — show provider status + next-step hint
|
|
946
|
+
|
|
947
|
+
${chalk.bold("Usage:")}
|
|
948
|
+
hatchkit status [--json]
|
|
949
|
+
|
|
950
|
+
${chalk.bold("Output:")}
|
|
951
|
+
Human: ✓/· per provider, next-best-step, config path.
|
|
952
|
+
JSON: full StatusSnapshot — stable shape for agents / scripts.
|
|
953
|
+
`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (topic === "explain") {
|
|
957
|
+
console.log(`
|
|
958
|
+
${chalk.bold("hatchkit explain")} — one-page mental model of the CLI
|
|
959
|
+
|
|
960
|
+
${chalk.bold("Usage:")}
|
|
961
|
+
hatchkit explain [--json]
|
|
962
|
+
|
|
963
|
+
Dumps a plain-text (or JSON) description of concepts, commands, and
|
|
964
|
+
the canonical workflow. Useful for humans with zero context and for
|
|
965
|
+
agents that need to "grok" hatchkit before driving it.
|
|
966
|
+
`);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (topic === "completion") {
|
|
970
|
+
console.log(`
|
|
971
|
+
${chalk.bold("hatchkit completion")} — print a shell-completion script
|
|
972
|
+
|
|
973
|
+
${chalk.bold("Usage:")}
|
|
974
|
+
hatchkit completion <zsh|bash|fish>
|
|
975
|
+
|
|
976
|
+
Pipe into your shell config, e.g.:
|
|
977
|
+
hatchkit completion zsh > ~/.zsh/completions/_hatchkit
|
|
978
|
+
hatchkit completion bash > /usr/local/etc/bash_completion.d/hatchkit
|
|
979
|
+
hatchkit completion fish > ~/.config/fish/completions/hatchkit.fish
|
|
566
980
|
`);
|
|
567
981
|
return;
|
|
568
982
|
}
|
|
569
983
|
console.log(`
|
|
570
984
|
${chalk.bold("Usage:")} hatchkit <command> [options]
|
|
571
985
|
|
|
572
|
-
${chalk.bold("
|
|
573
|
-
|
|
574
|
-
|
|
986
|
+
${chalk.bold("Getting started:")}
|
|
987
|
+
setup One-time onboarding — wires up all credentials (alias: init)
|
|
988
|
+
status Show what's configured and what's next
|
|
989
|
+
doctor Health-check every provider with contextual fix hints
|
|
990
|
+
explain One-page mental model of the CLI
|
|
991
|
+
|
|
992
|
+
${chalk.bold("Projects:")}
|
|
993
|
+
create Scaffold a new project (interactive)
|
|
575
994
|
update Add features to an already-scaffolded project (run in project dir)
|
|
995
|
+
add Create GlitchTip / OpenPanel / Resend clients for an existing project
|
|
996
|
+
remove Delete the -dev/-prod clients created by 'add' (inverse of add)
|
|
997
|
+
rename-domain Move a scaffolded project to a new domain (rewrites tfvars/env/manifest)
|
|
998
|
+
gh-pages Wire GitHub Pages for the current repo (static / Vite / Jekyll — with DNS)
|
|
999
|
+
dns DNS reconciliation helpers (link-to-cloudflare, …)
|
|
576
1000
|
keys show <p> Print the dotenvx private key for a project
|
|
577
1001
|
keys push <p> Push the key onto the project's Coolify app
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
config
|
|
1002
|
+
|
|
1003
|
+
${chalk.bold("Config:")}
|
|
1004
|
+
config Show provider status (same as \`status\`)
|
|
1005
|
+
config add <p> Configure a provider (coolify, hetzner, dns, s3, modal, …)
|
|
581
1006
|
config reset Clear ALL CLI config (providers, tokens, ML registry, ports)
|
|
582
1007
|
|
|
1008
|
+
${chalk.bold("For agents / scripts:")}
|
|
1009
|
+
status --json StatusSnapshot as JSON
|
|
1010
|
+
doctor --json Per-provider health with fix hints as JSON
|
|
1011
|
+
completion <shell> Print a zsh/bash/fish completion script
|
|
1012
|
+
|
|
583
1013
|
${chalk.bold("Options:")}
|
|
584
1014
|
--version, -v Print the CLI version
|
|
585
1015
|
--help, -h Show this help message (pass to a subcommand for detail)
|
|
1016
|
+
--json Machine-readable output (status, doctor, explain)
|
|
586
1017
|
--dry-run (with \`create\`) show what would change without writing
|
|
587
1018
|
--yes, -y (with \`create\`) skip prompts, use defaults / --config values
|
|
588
1019
|
--config <path> (with \`create\`) load JSON overrides for ProjectConfig fields
|
|
@@ -592,8 +1023,8 @@ function printHelp(topic) {
|
|
|
592
1023
|
|
|
593
1024
|
${chalk.bold("Environment:")}
|
|
594
1025
|
HATCHKIT_CONF_DIR Override the config/ports-registry location
|
|
595
|
-
|
|
596
|
-
|
|
1026
|
+
|
|
1027
|
+
${chalk.dim("Run `hatchkit help <command>` for per-command detail.")}
|
|
597
1028
|
`);
|
|
598
1029
|
}
|
|
599
1030
|
// ---------------------------------------------------------------------------
|