hatchkit 0.1.47 → 0.2.1
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/adopt.d.ts +61 -1
- package/dist/adopt.d.ts.map +1 -1
- package/dist/adopt.js +90 -86
- package/dist/adopt.js.map +1 -1
- package/dist/completion.d.ts.map +1 -1
- package/dist/completion.js +19 -1
- package/dist/completion.js.map +1 -1
- package/dist/config.d.ts +32 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +364 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy/coolify.d.ts +5 -0
- package/dist/deploy/coolify.d.ts.map +1 -1
- package/dist/deploy/coolify.js +67 -4
- package/dist/deploy/coolify.js.map +1 -1
- package/dist/deploy/ghcr.d.ts +1 -0
- package/dist/deploy/ghcr.d.ts.map +1 -1
- package/dist/deploy/ghcr.js +2 -2
- package/dist/deploy/ghcr.js.map +1 -1
- package/dist/deploy/github.d.ts.map +1 -1
- package/dist/deploy/github.js +3 -2
- package/dist/deploy/github.js.map +1 -1
- package/dist/deploy/rollback.d.ts.map +1 -1
- package/dist/deploy/rollback.js +9 -0
- package/dist/deploy/rollback.js.map +1 -1
- package/dist/dev-setup.d.ts +10 -4
- package/dist/dev-setup.d.ts.map +1 -1
- package/dist/dev-setup.js +166 -57
- package/dist/dev-setup.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +65 -1
- package/dist/doctor.js.map +1 -1
- package/dist/email/index.js +5 -5
- package/dist/email/index.js.map +1 -1
- package/dist/email/setup.d.ts +1 -1
- package/dist/email/setup.d.ts.map +1 -1
- package/dist/email/setup.js +3 -3
- package/dist/email/setup.js.map +1 -1
- package/dist/explain.d.ts.map +1 -1
- package/dist/explain.js +8 -7
- package/dist/explain.js.map +1 -1
- package/dist/index.js +277 -60
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +1 -0
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +2 -0
- package/dist/inventory.js.map +1 -1
- package/dist/onboarding/plan.d.ts +54 -0
- package/dist/onboarding/plan.d.ts.map +1 -0
- package/dist/onboarding/plan.js +143 -0
- package/dist/onboarding/plan.js.map +1 -0
- package/dist/onboarding/review.d.ts +27 -0
- package/dist/onboarding/review.d.ts.map +1 -0
- package/dist/onboarding/review.js +55 -0
- package/dist/onboarding/review.js.map +1 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +107 -89
- package/dist/prompts.js.map +1 -1
- package/dist/provision/index.d.ts +21 -3
- package/dist/provision/index.d.ts.map +1 -1
- package/dist/provision/index.js +112 -5
- package/dist/provision/index.js.map +1 -1
- package/dist/provision/plausible.d.ts +10 -0
- package/dist/provision/plausible.d.ts.map +1 -0
- package/dist/provision/plausible.js +103 -0
- package/dist/provision/plausible.js.map +1 -0
- package/dist/provision/search-console.d.ts +17 -0
- package/dist/provision/search-console.d.ts.map +1 -0
- package/dist/provision/search-console.js +142 -0
- package/dist/provision/search-console.js.map +1 -0
- package/dist/scaffold/app.d.ts +1 -0
- package/dist/scaffold/app.d.ts.map +1 -1
- package/dist/scaffold/app.js +4 -1
- package/dist/scaffold/app.js.map +1 -1
- package/dist/scaffold/infra.js +2 -0
- package/dist/scaffold/infra.js.map +1 -1
- package/dist/scaffold/manifest.d.ts +4 -2
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +7 -1
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/server-add.d.ts +21 -0
- package/dist/scaffold/server-add.d.ts.map +1 -0
- package/dist/scaffold/server-add.js +273 -0
- package/dist/scaffold/server-add.js.map +1 -0
- package/dist/scaffold/update.d.ts +1 -0
- package/dist/scaffold/update.d.ts.map +1 -1
- package/dist/scaffold/update.js +8 -5
- package/dist/scaffold/update.js.map +1 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +27 -1
- package/dist/status.js.map +1 -1
- package/dist/templates/base/env.example.hbs +3 -0
- package/dist/utils/cloudflare-api.d.ts +5 -0
- package/dist/utils/cloudflare-api.d.ts.map +1 -1
- package/dist/utils/cloudflare-api.js +19 -0
- package/dist/utils/cloudflare-api.js.map +1 -1
- package/dist/utils/coolify-api.d.ts +3 -2
- package/dist/utils/coolify-api.d.ts.map +1 -1
- package/dist/utils/coolify-api.js +19 -5
- package/dist/utils/coolify-api.js.map +1 -1
- package/dist/utils/flags.d.ts.map +1 -1
- package/dist/utils/flags.js +16 -0
- package/dist/utils/flags.js.map +1 -1
- package/dist/utils/run-ledger.d.ts +3 -0
- package/dist/utils/run-ledger.d.ts.map +1 -1
- package/dist/utils/run-ledger.js.map +1 -1
- package/dist/utils/secrets.d.ts +5 -0
- package/dist/utils/secrets.d.ts.map +1 -1
- package/dist/utils/secrets.js +5 -0
- package/dist/utils/secrets.js.map +1 -1
- package/package.json +24 -3
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { confirm } from "@inquirer/prompts";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { ensureCoolify, ensureGitHub, ensureHetzner, ensureS3, getConfig, getConfigPath, getMlServices, isFirstRun, reconfigureProvider, resetConfig, runOnboarding, } from "./config.js";
|
|
6
|
+
import { ensureCoolify, ensureGitHub, ensureHetzner, ensureS3, getConfig, getConfigPath, getCoolifyConfig, getGhcrConfig, getMlServices, isFirstRun, reconfigureProvider, resetConfig, runOnboarding, } from "./config.js";
|
|
7
7
|
import { runCoolifySetup } from "./deploy/coolify.js";
|
|
8
8
|
import { setupGitHub } from "./deploy/github.js";
|
|
9
9
|
import { deployMlServices } from "./deploy/gpu.js";
|
|
@@ -108,6 +108,11 @@ async function main() {
|
|
|
108
108
|
return printHelp("update");
|
|
109
109
|
await handleUpdate();
|
|
110
110
|
break;
|
|
111
|
+
case "server":
|
|
112
|
+
if (args.includes("--help") && args.length === 2)
|
|
113
|
+
return printHelp("server");
|
|
114
|
+
await handleServer();
|
|
115
|
+
break;
|
|
111
116
|
case "keys":
|
|
112
117
|
if (args.includes("--help") && args.length === 2)
|
|
113
118
|
return printHelp("keys");
|
|
@@ -456,7 +461,15 @@ async function handleAdd() {
|
|
|
456
461
|
const positional = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
457
462
|
let baseName = positional[0];
|
|
458
463
|
const rawService = positional[1];
|
|
459
|
-
const allServices = [
|
|
464
|
+
const allServices = [
|
|
465
|
+
"glitchtip",
|
|
466
|
+
"openpanel",
|
|
467
|
+
"plausible",
|
|
468
|
+
"resend",
|
|
469
|
+
"s3",
|
|
470
|
+
"email",
|
|
471
|
+
"search-console",
|
|
472
|
+
];
|
|
460
473
|
if (!baseName) {
|
|
461
474
|
const { input } = await import("@inquirer/prompts");
|
|
462
475
|
const { validateProjectName } = await import("./utils/validate.js");
|
|
@@ -473,6 +486,7 @@ async function handleAdd() {
|
|
|
473
486
|
choices: [
|
|
474
487
|
{ name: "GlitchTip (error tracking)", value: "glitchtip", checked: true },
|
|
475
488
|
{ name: "OpenPanel (product analytics)", value: "openpanel", checked: true },
|
|
489
|
+
{ name: "Plausible (web analytics)", value: "plausible", checked: false },
|
|
476
490
|
{ name: "Resend (transactional email)", value: "resend", checked: true },
|
|
477
491
|
{
|
|
478
492
|
name: "S3 / R2 (per-bucket scoped credentials from .hatchkit.json)",
|
|
@@ -484,6 +498,11 @@ async function handleAdd() {
|
|
|
484
498
|
value: "email",
|
|
485
499
|
checked: false,
|
|
486
500
|
},
|
|
501
|
+
{
|
|
502
|
+
name: "Google Search Console (DNS verification + domain property)",
|
|
503
|
+
value: "search-console",
|
|
504
|
+
checked: false,
|
|
505
|
+
},
|
|
487
506
|
],
|
|
488
507
|
required: true,
|
|
489
508
|
});
|
|
@@ -503,7 +522,7 @@ async function handleAdd() {
|
|
|
503
522
|
}
|
|
504
523
|
// Flag parsing:
|
|
505
524
|
// --no-write → never write; print a cache summary only
|
|
506
|
-
// --enable-dev-obs → also populate .env.development with
|
|
525
|
+
// --enable-dev-obs → also populate .env.development with observability creds
|
|
507
526
|
// --surfaces=<shared|separate|server-only|client-only>
|
|
508
527
|
// --server-dir <path> → absolute or project-relative env dir for the server
|
|
509
528
|
// --client-dir <path> → same for the client
|
|
@@ -783,7 +802,15 @@ async function handleRemove() {
|
|
|
783
802
|
const skipConfirm = args.includes("--yes") || args.includes("-y");
|
|
784
803
|
let baseName = positional[0];
|
|
785
804
|
const rawService = positional[1];
|
|
786
|
-
const allServices = [
|
|
805
|
+
const allServices = [
|
|
806
|
+
"glitchtip",
|
|
807
|
+
"openpanel",
|
|
808
|
+
"plausible",
|
|
809
|
+
"resend",
|
|
810
|
+
"s3",
|
|
811
|
+
"email",
|
|
812
|
+
"search-console",
|
|
813
|
+
];
|
|
787
814
|
if (!baseName) {
|
|
788
815
|
const { input } = await import("@inquirer/prompts");
|
|
789
816
|
const { validateProjectName } = await import("./utils/validate.js");
|
|
@@ -800,6 +827,7 @@ async function handleRemove() {
|
|
|
800
827
|
choices: [
|
|
801
828
|
{ name: "GlitchTip (deletes the project)", value: "glitchtip", checked: true },
|
|
802
829
|
{ name: "OpenPanel (deletes the project)", value: "openpanel", checked: true },
|
|
830
|
+
{ name: "Plausible (deletes the site)", value: "plausible", checked: false },
|
|
803
831
|
{ name: "Resend (deletes the API key)", value: "resend", checked: true },
|
|
804
832
|
{ name: "S3 / R2 (deletes per-bucket scoped tokens)", value: "s3", checked: false },
|
|
805
833
|
{
|
|
@@ -807,6 +835,11 @@ async function handleRemove() {
|
|
|
807
835
|
value: "email",
|
|
808
836
|
checked: false,
|
|
809
837
|
},
|
|
838
|
+
{
|
|
839
|
+
name: "Google Search Console (removes property; keeps verification token)",
|
|
840
|
+
value: "search-console",
|
|
841
|
+
checked: false,
|
|
842
|
+
},
|
|
810
843
|
],
|
|
811
844
|
required: true,
|
|
812
845
|
});
|
|
@@ -841,7 +874,7 @@ async function handleRemove() {
|
|
|
841
874
|
// project directory if it exists; the s3 unprovision falls back to
|
|
842
875
|
// a keychain sweep when the manifest can't be found.
|
|
843
876
|
let projectDir;
|
|
844
|
-
if (services.includes("s3")) {
|
|
877
|
+
if (services.includes("s3") || services.includes("search-console")) {
|
|
845
878
|
const guess = resolve(baseName);
|
|
846
879
|
if (existsSync(join(guess, ".hatchkit.json"))) {
|
|
847
880
|
projectDir = guess;
|
|
@@ -867,6 +900,52 @@ async function handleDns() {
|
|
|
867
900
|
printHelp("dns");
|
|
868
901
|
}
|
|
869
902
|
}
|
|
903
|
+
async function configureGhcrForCreate(repoUrl, isPrivateRepo, ledger) {
|
|
904
|
+
const { repoSlugFromRemote } = await import("./deploy/gh-actions-secrets.js");
|
|
905
|
+
const slug = repoSlugFromRemote(repoUrl);
|
|
906
|
+
if (!slug) {
|
|
907
|
+
console.log(chalk.dim(" · Couldn't resolve owner/repo from GitHub URL — skipping GHCR pull setup."));
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const coolify = await getCoolifyConfig();
|
|
911
|
+
if (!coolify) {
|
|
912
|
+
console.log(chalk.dim(" · Coolify not configured — skipping GHCR pull setup."));
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const { CoolifyApi } = await import("./utils/coolify-api.js");
|
|
916
|
+
const { makeGhcrPackagePublic, registerGhcrCredsWithCoolify } = await import("./deploy/ghcr.js");
|
|
917
|
+
if (!isPrivateRepo) {
|
|
918
|
+
const result = await makeGhcrPackagePublic({ repoSlug: slug });
|
|
919
|
+
if (result.kind === "public-set")
|
|
920
|
+
return;
|
|
921
|
+
if (result.kind === "skipped" || result.kind === "failed") {
|
|
922
|
+
console.log(chalk.yellow(` GHCR public-image setup skipped: ${result.reason}`));
|
|
923
|
+
console.log(chalk.dim(result.recovery.map((line) => ` ${line}`).join("\n")));
|
|
924
|
+
}
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const ghcrConfig = await getGhcrConfig();
|
|
928
|
+
const api = new CoolifyApi({ url: coolify.url, token: coolify.token });
|
|
929
|
+
const result = await registerGhcrCredsWithCoolify({
|
|
930
|
+
api,
|
|
931
|
+
repoSlug: slug,
|
|
932
|
+
pullToken: ghcrConfig?.pullToken,
|
|
933
|
+
username: ghcrConfig?.username,
|
|
934
|
+
});
|
|
935
|
+
if (result.kind === "private-registered") {
|
|
936
|
+
if (result.created) {
|
|
937
|
+
ledger?.record({ kind: "coolifyPrivateRegistry", uuid: result.registryUuid });
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (result.kind === "skipped" || result.kind === "failed") {
|
|
942
|
+
console.log(chalk.yellow(` GHCR private-image pull setup skipped: ${result.reason}`));
|
|
943
|
+
console.log(chalk.dim(result.recovery.map((line) => ` ${line}`).join("\n")));
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
function isCreatedGithubRepoPrivate(config) {
|
|
947
|
+
return config.createGithubRepo && (config.githubRepoVisibility ?? "private") === "private";
|
|
948
|
+
}
|
|
870
949
|
// ---------------------------------------------------------------------------
|
|
871
950
|
// Commands
|
|
872
951
|
// ---------------------------------------------------------------------------
|
|
@@ -875,20 +954,22 @@ async function handleCreate() {
|
|
|
875
954
|
// the flow non-interactive; otherwise we still prompt for anything
|
|
876
955
|
// not supplied via flags / config file.
|
|
877
956
|
const flags = parseCreateFlags(args);
|
|
878
|
-
const { yes: nonInteractive, dryRun, presets, forceNoGithub, forceNoDeploy, forceNoInstall, } = flags;
|
|
957
|
+
const { yes: nonInteractive, dryRun, presets, forceNoGithub, forceNoDeploy, forceNoInstall, forceNoLocalDev, } = flags;
|
|
879
958
|
// Check if first run (skip onboarding when non-interactive — the
|
|
880
959
|
// onboarding prompts would stall automation).
|
|
881
960
|
if (!nonInteractive && (await isFirstRun())) {
|
|
882
961
|
await runOnboarding();
|
|
883
962
|
}
|
|
884
963
|
// Collect project config via interactive prompts (or presets).
|
|
885
|
-
const config = await collectProjectConfig({ dryRun, presets, nonInteractive });
|
|
964
|
+
const config = await collectProjectConfig({ dryRun, presets, nonInteractive, forceNoLocalDev });
|
|
886
965
|
if (forceNoGithub)
|
|
887
966
|
config.createGithubRepo = false;
|
|
888
967
|
if (forceNoDeploy)
|
|
889
968
|
config.runDeployment = false;
|
|
890
969
|
if (forceNoInstall)
|
|
891
970
|
config.installDeps = false;
|
|
971
|
+
if (forceNoLocalDev)
|
|
972
|
+
config.localDev = undefined;
|
|
892
973
|
// Ensure needed providers are configured (lazy prompting).
|
|
893
974
|
// Coolify + Hetzner only matter for the coolify deployment mode.
|
|
894
975
|
// gh-pages skips them entirely (no server, no Docker registry).
|
|
@@ -917,13 +998,19 @@ async function handleCreate() {
|
|
|
917
998
|
await ensureS3(config.s3Provider);
|
|
918
999
|
}
|
|
919
1000
|
}
|
|
920
|
-
// Pre-flight observability +
|
|
921
|
-
// create` directly (not just `add`): if the user picked
|
|
922
|
-
//
|
|
923
|
-
//
|
|
1001
|
+
// Pre-flight observability + Stripe providers used by `hatchkit
|
|
1002
|
+
// create` directly (not just `add`): if the user picked analytics
|
|
1003
|
+
// providers, make sure they are configured before we can mint
|
|
1004
|
+
// project-scoped resources. Same for Stripe webhook auto-provisioning.
|
|
924
1005
|
if (config.features.includes("analytics")) {
|
|
925
|
-
const
|
|
926
|
-
await
|
|
1006
|
+
const providers = config.analyticsProviders ?? ["glitchtip"];
|
|
1007
|
+
const { ensureGlitchtip, ensureOpenpanel, ensurePlausible } = await import("./config.js");
|
|
1008
|
+
if (providers.includes("glitchtip"))
|
|
1009
|
+
await ensureGlitchtip();
|
|
1010
|
+
if (providers.includes("openpanel"))
|
|
1011
|
+
await ensureOpenpanel();
|
|
1012
|
+
if (providers.includes("plausible"))
|
|
1013
|
+
await ensurePlausible();
|
|
927
1014
|
}
|
|
928
1015
|
if (config.features.includes("stripe")) {
|
|
929
1016
|
const { ensureStripe } = await import("./config.js");
|
|
@@ -954,7 +1041,7 @@ async function handleCreate() {
|
|
|
954
1041
|
console.log(` Features: ${config.features.length > 0 ? config.features.join(", ") : "none"}`);
|
|
955
1042
|
console.log(` ML: ${config.mlServices.length > 0 ? config.mlServices.join(", ") : "none"}`);
|
|
956
1043
|
console.log(` Scaffold: ${config.scaffoldRepo ? "yes" : "no"}`);
|
|
957
|
-
console.log(` GitHub: ${config.createGithubRepo ?
|
|
1044
|
+
console.log(` GitHub: ${config.createGithubRepo ? `yes (${config.githubRepoVisibility ?? "private"})` : "no"}`);
|
|
958
1045
|
console.log(` Install: ${config.installDeps ? "yes (pnpm install)" : "no"}`);
|
|
959
1046
|
console.log(` Deploy now: ${config.runDeployment ? "yes" : "no"}`);
|
|
960
1047
|
if (config.dryRun) {
|
|
@@ -996,28 +1083,47 @@ async function handleCreate() {
|
|
|
996
1083
|
const { printDotenvxSummary } = await import("./scaffold/dotenvx.js");
|
|
997
1084
|
printDotenvxSummary(scaffoldResult.dotenvx, config.name);
|
|
998
1085
|
}
|
|
999
|
-
// Auto-provision
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
if (config.features.includes("analytics") && config.surfaces !== "client-only") {
|
|
1086
|
+
// Auto-provision selected observability/analytics providers
|
|
1087
|
+
// through the same machinery used by `hatchkit add`, so create,
|
|
1088
|
+
// adopt, and existing-project provisioning stay aligned.
|
|
1089
|
+
if (config.features.includes("analytics")) {
|
|
1090
|
+
const analyticsServices = [
|
|
1091
|
+
...(config.analyticsProviders ?? ["glitchtip"]),
|
|
1092
|
+
];
|
|
1007
1093
|
try {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1094
|
+
if (analyticsServices.length > 0) {
|
|
1095
|
+
const provisionMode = config.surfaces === "both"
|
|
1096
|
+
? "shared"
|
|
1097
|
+
: config.surfaces === "server-only"
|
|
1098
|
+
? "server-only"
|
|
1099
|
+
: "client-only";
|
|
1100
|
+
await runProvision({
|
|
1101
|
+
baseName: config.name,
|
|
1102
|
+
services: analyticsServices,
|
|
1103
|
+
domain: config.domain,
|
|
1104
|
+
surfaces: {
|
|
1105
|
+
mode: provisionMode,
|
|
1106
|
+
projectDir: appDir,
|
|
1107
|
+
serverEnvDir: config.surfaces === "client-only" ? undefined : join(appDir, "packages/server"),
|
|
1108
|
+
clientEnvDir: config.surfaces === "server-only" ? undefined : join(appDir, "packages/client"),
|
|
1109
|
+
},
|
|
1110
|
+
onProvisioned: (event) => {
|
|
1111
|
+
if (event.service === "glitchtip") {
|
|
1112
|
+
ledger?.record({ kind: "glitchtip", project: event.project });
|
|
1113
|
+
}
|
|
1114
|
+
else if (event.service === "openpanel") {
|
|
1115
|
+
ledger?.record({ kind: "openpanel", project: event.project });
|
|
1116
|
+
}
|
|
1117
|
+
else if (event.service === "plausible") {
|
|
1118
|
+
ledger?.record({ kind: "plausible", project: event.project });
|
|
1119
|
+
}
|
|
1120
|
+
},
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1017
1123
|
}
|
|
1018
1124
|
catch (err) {
|
|
1019
|
-
console.log(chalk.yellow(` Couldn't auto-provision
|
|
1020
|
-
console.log(chalk.dim(` Run \`hatchkit add ${config.name}
|
|
1125
|
+
console.log(chalk.yellow(` Couldn't auto-provision analytics: ${err.message}`));
|
|
1126
|
+
console.log(chalk.dim(` Run \`hatchkit add ${config.name} ${analyticsServices.join(",")}\` once providers are reachable.`));
|
|
1021
1127
|
}
|
|
1022
1128
|
}
|
|
1023
1129
|
// Stripe: walk the user through pasting per-project keys (sk + pk
|
|
@@ -1170,6 +1276,7 @@ async function handleCreate() {
|
|
|
1170
1276
|
repoUrl: repoUrl ?? undefined,
|
|
1171
1277
|
serverPort: scaffoldResult?.ports.server,
|
|
1172
1278
|
clientPort: scaffoldResult?.ports.client,
|
|
1279
|
+
isPrivateRepo: isCreatedGithubRepoPrivate(config),
|
|
1173
1280
|
});
|
|
1174
1281
|
// Order matters: rollback iterates the ledger in REVERSE, so we
|
|
1175
1282
|
// record parent-before-child (project before app). Otherwise
|
|
@@ -1223,18 +1330,26 @@ async function handleCreate() {
|
|
|
1223
1330
|
if (repoUrl && config.scaffoldRepo) {
|
|
1224
1331
|
try {
|
|
1225
1332
|
const { findCoolifyAppsForProject } = await import("./deploy/coolify-app.js");
|
|
1226
|
-
const { repoSlugFromRemote, setCoolifyDeploySecrets } = await import("./deploy/gh-actions-secrets.js");
|
|
1333
|
+
const { ghSecretExists, repoSlugFromRemote, setCoolifyDeploySecrets } = await import("./deploy/gh-actions-secrets.js");
|
|
1227
1334
|
const slug = repoSlugFromRemote(repoUrl);
|
|
1228
1335
|
const apps = await findCoolifyAppsForProject(config.name);
|
|
1229
|
-
if (slug
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1336
|
+
if (slug) {
|
|
1337
|
+
if (apps.length > 0) {
|
|
1338
|
+
await setCoolifyDeploySecrets({
|
|
1339
|
+
projectDir: appDir,
|
|
1340
|
+
repoSlug: slug,
|
|
1341
|
+
apps,
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
console.log(chalk.dim(` · No Coolify app named "${config.name}" / "${config.name}-server" / "${config.name}-client" / "${config.name}-web" found — skipping Coolify deploy secret push.`));
|
|
1346
|
+
}
|
|
1347
|
+
const secretName = "DOTENV_PRIVATE_KEY_PRODUCTION";
|
|
1348
|
+
const preExisted = await ghSecretExists(appDir, slug, secretName);
|
|
1349
|
+
await pushProjectKeyToGh(config.name, slug);
|
|
1350
|
+
if (!preExisted) {
|
|
1351
|
+
ledger?.record({ kind: "ghActionsSecret", repo: slug, name: secretName });
|
|
1352
|
+
}
|
|
1238
1353
|
}
|
|
1239
1354
|
}
|
|
1240
1355
|
catch (err) {
|
|
@@ -1305,7 +1420,10 @@ async function handleCreate() {
|
|
|
1305
1420
|
// created the repo + `origin` but deliberately skipped the push.
|
|
1306
1421
|
if (config.scaffoldRepo && config.createGithubRepo && repoUrl) {
|
|
1307
1422
|
const { pushInitialBranch } = await import("./deploy/github.js");
|
|
1308
|
-
await pushInitialBranch(appDir);
|
|
1423
|
+
const pushed = await pushInitialBranch(appDir);
|
|
1424
|
+
if (pushed && config.deploymentMode === "coolify") {
|
|
1425
|
+
await configureGhcrForCreate(repoUrl, isCreatedGithubRepoPrivate(config), ledger);
|
|
1426
|
+
}
|
|
1309
1427
|
}
|
|
1310
1428
|
// Step 6.6: optional email forwarding setup (Cloudflare Email
|
|
1311
1429
|
// Routing). Opt-in prompt — most projects want it but a scripted
|
|
@@ -1470,6 +1588,48 @@ async function handleUpdate() {
|
|
|
1470
1588
|
console.log(chalk.yellow(" Run `pnpm install` to pick up @hatchkit/dev-plugin-next, then `hatchkit doctor` to confirm host plumbing."));
|
|
1471
1589
|
}
|
|
1472
1590
|
}
|
|
1591
|
+
async function handleServer() {
|
|
1592
|
+
const sub = args[1];
|
|
1593
|
+
if (sub !== "add") {
|
|
1594
|
+
console.log("Usage: hatchkit server add [--yes] [--dry-run] [--server-dir <path>]");
|
|
1595
|
+
console.log("Run `hatchkit help server` for details.");
|
|
1596
|
+
process.exit(1);
|
|
1597
|
+
}
|
|
1598
|
+
const { runServerAdd } = await import("./scaffold/server-add.js");
|
|
1599
|
+
const result = await runServerAdd(resolve("."), {
|
|
1600
|
+
yes: args.includes("--yes") || args.includes("-y"),
|
|
1601
|
+
dryRun: args.includes("--dry-run"),
|
|
1602
|
+
serverDir: flagValue("--server-dir"),
|
|
1603
|
+
sharedDir: flagValue("--shared-dir"),
|
|
1604
|
+
});
|
|
1605
|
+
if (args.includes("--json")) {
|
|
1606
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (result.dryRun) {
|
|
1610
|
+
console.log(chalk.yellow(" --dry-run — no files were changed."));
|
|
1611
|
+
}
|
|
1612
|
+
if (result.created.length > 0) {
|
|
1613
|
+
console.log(chalk.green(` ✓ Created: ${result.created.join(", ")}`));
|
|
1614
|
+
}
|
|
1615
|
+
if (result.updated.length > 0) {
|
|
1616
|
+
console.log(chalk.green(` ✓ Updated: ${result.updated.join(", ")}`));
|
|
1617
|
+
}
|
|
1618
|
+
if (result.reused.length > 0) {
|
|
1619
|
+
console.log(chalk.dim(` · Reused existing: ${result.reused.join(", ")}`));
|
|
1620
|
+
}
|
|
1621
|
+
for (const warning of result.warnings) {
|
|
1622
|
+
console.log(chalk.yellow(` ! ${warning}`));
|
|
1623
|
+
}
|
|
1624
|
+
if (result.skipped.length > 0 && !result.changed) {
|
|
1625
|
+
console.log(chalk.dim(` · ${result.skipped.join(", ")}`));
|
|
1626
|
+
}
|
|
1627
|
+
if (result.nextSteps.length > 0) {
|
|
1628
|
+
console.log(chalk.bold("\n Next:"));
|
|
1629
|
+
for (const step of result.nextSteps)
|
|
1630
|
+
console.log(` ${step}`);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1473
1633
|
async function handleConfig() {
|
|
1474
1634
|
const subcommand = args[1];
|
|
1475
1635
|
switch (subcommand) {
|
|
@@ -1477,7 +1637,7 @@ async function handleConfig() {
|
|
|
1477
1637
|
const provider = args[2];
|
|
1478
1638
|
if (!provider) {
|
|
1479
1639
|
console.log("Usage: hatchkit config add <provider>");
|
|
1480
|
-
console.log("Providers: coolify, ghcr, hetzner, dns, s3, modal, runpod, hf, replicate, glitchtip, openpanel, resend, stripe");
|
|
1640
|
+
console.log("Providers: coolify, ghcr, hetzner, dns, s3, modal, runpod, hf, replicate, glitchtip, openpanel, plausible, resend, search-console, stripe");
|
|
1481
1641
|
return;
|
|
1482
1642
|
}
|
|
1483
1643
|
// Handle provider setup based on name
|
|
@@ -1489,7 +1649,9 @@ async function handleConfig() {
|
|
|
1489
1649
|
case "dns":
|
|
1490
1650
|
case "glitchtip":
|
|
1491
1651
|
case "openpanel":
|
|
1652
|
+
case "plausible":
|
|
1492
1653
|
case "resend":
|
|
1654
|
+
case "search-console":
|
|
1493
1655
|
case "stripe":
|
|
1494
1656
|
case "ghcr":
|
|
1495
1657
|
await reconfigureProvider(provider);
|
|
@@ -1528,7 +1690,7 @@ async function handleConfig() {
|
|
|
1528
1690
|
default:
|
|
1529
1691
|
if (!isGpuPlatform(provider)) {
|
|
1530
1692
|
console.log(chalk.red(` Unknown provider: ${provider}`));
|
|
1531
|
-
console.log(chalk.dim(" Valid: coolify, ghcr, hetzner, dns, s3, modal, runpod, hf, replicate, glitchtip, openpanel, resend, stripe"));
|
|
1693
|
+
console.log(chalk.dim(" Valid: coolify, ghcr, hetzner, dns, s3, modal, runpod, hf, replicate, glitchtip, openpanel, plausible, resend, search-console, stripe"));
|
|
1532
1694
|
return;
|
|
1533
1695
|
}
|
|
1534
1696
|
await reconfigureProvider(`gpu.${provider}`);
|
|
@@ -1673,6 +1835,37 @@ function printHelp(topic) {
|
|
|
1673
1835
|
|
|
1674
1836
|
${chalk.bold("Removal is not supported.")} Removing features could delete
|
|
1675
1837
|
user code — remove manually + edit the manifest.
|
|
1838
|
+
`);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (topic === "server") {
|
|
1842
|
+
console.log(`
|
|
1843
|
+
${chalk.bold("hatchkit server add")} — retrofit a server into a client-only project
|
|
1844
|
+
|
|
1845
|
+
${chalk.bold("Usage:")}
|
|
1846
|
+
cd <project-dir> && hatchkit server add
|
|
1847
|
+
cd <project-dir> && hatchkit server add --yes
|
|
1848
|
+
|
|
1849
|
+
${chalk.bold("What it does:")}
|
|
1850
|
+
Reads .hatchkit.json, copies the Hatchkit server package from the
|
|
1851
|
+
starter, restores shared server types, updates root scripts/workspace
|
|
1852
|
+
files, flips manifest surfaces from ${chalk.cyan("client-only")} to
|
|
1853
|
+
${chalk.cyan("both")}, and switches gh-pages projects back to coolify.
|
|
1854
|
+
|
|
1855
|
+
${chalk.bold("What it does not do:")}
|
|
1856
|
+
No provider calls. No Coolify, DNS, GitHub, keychain, or Terraform
|
|
1857
|
+
mutation. To wire deploy infra after the local scaffold:
|
|
1858
|
+
|
|
1859
|
+
hatchkit adopt --resume --regenerate-pipeline
|
|
1860
|
+
|
|
1861
|
+
${chalk.bold("Options:")}
|
|
1862
|
+
--server-dir <path> Destination for the server package. Default:
|
|
1863
|
+
${chalk.dim("packages/server")}.
|
|
1864
|
+
--shared-dir <path> Destination for the shared package. Default:
|
|
1865
|
+
${chalk.dim("packages/shared")}.
|
|
1866
|
+
--yes, -y Skip confirmation.
|
|
1867
|
+
--dry-run Show planned local changes without writing.
|
|
1868
|
+
--json Machine-readable result.
|
|
1676
1869
|
`);
|
|
1677
1870
|
return;
|
|
1678
1871
|
}
|
|
@@ -1792,20 +1985,22 @@ function printHelp(topic) {
|
|
|
1792
1985
|
}
|
|
1793
1986
|
if (topic === "dev-setup") {
|
|
1794
1987
|
console.log(`
|
|
1795
|
-
${chalk.bold("hatchkit dev-setup")} —
|
|
1988
|
+
${chalk.bold("hatchkit dev-setup")} — Tailscale-served dev URLs
|
|
1796
1989
|
|
|
1797
1990
|
Wires up the host-wide plumbing that makes every scaffolded project
|
|
1798
1991
|
reachable from any Tailscale peer at:
|
|
1799
1992
|
|
|
1800
|
-
${chalk.cyan("https://<slug>.local
|
|
1993
|
+
${chalk.cyan("https://<slug>.local.<your-domain>/")}
|
|
1801
1994
|
|
|
1802
1995
|
…without per-project DNS, port juggling, or framework basePath config.
|
|
1803
1996
|
|
|
1804
1997
|
${chalk.bold("Host-wide subcommands (run once per machine):")}
|
|
1805
1998
|
dev-setup init [--force] Auto-write ~/.config/dev/Caddyfile, register
|
|
1806
1999
|
a launchd job to run Caddy on a free port
|
|
1807
|
-
(default 9443, auto-bumps on collision),
|
|
1808
|
-
register a tailscale serve TCP=443 bridge
|
|
2000
|
+
(default 9443, auto-bumps on collision),
|
|
2001
|
+
register a tailscale serve TCP=443 bridge,
|
|
2002
|
+
and auto-upsert the wildcard DNS A record
|
|
2003
|
+
when Cloudflare credentials are available.
|
|
1809
2004
|
Idempotent — safe to re-run.
|
|
1810
2005
|
dev-setup status Print the same Local-dev rows that
|
|
1811
2006
|
${chalk.cyan("hatchkit doctor")} would show.
|
|
@@ -1825,8 +2020,11 @@ function printHelp(topic) {
|
|
|
1825
2020
|
next.config + dep in place (they're inert
|
|
1826
2021
|
without the fragment).
|
|
1827
2022
|
|
|
1828
|
-
${chalk.bold("
|
|
1829
|
-
|
|
2023
|
+
${chalk.bold("DNS:")}
|
|
2024
|
+
${chalk.cyan("dev-setup init")} auto-upserts a DNS-only A record:
|
|
2025
|
+
*.local.<your-domain> A <your-tailnet-ip> (DNS-only)
|
|
2026
|
+
|
|
2027
|
+
If Cloudflare credentials are unavailable, add that record manually.
|
|
1830
2028
|
|
|
1831
2029
|
This feature is fully optional: until you run ${chalk.cyan("dev-setup init")},
|
|
1832
2030
|
${chalk.cyan("hatchkit doctor")} surfaces zero Local-dev rows. Within a project,
|
|
@@ -1864,7 +2062,7 @@ function printHelp(topic) {
|
|
|
1864
2062
|
· App fqdn references an apex with no Cloudflare zone
|
|
1865
2063
|
· R2 bucket follows the \`<project>-<role>\` convention but has no
|
|
1866
2064
|
matching Coolify app (orphan from a destroyed project)
|
|
1867
|
-
· GlitchTip / OpenPanel project with no Coolify app counterpart
|
|
2065
|
+
· GlitchTip / OpenPanel / Plausible project/site with no Coolify app counterpart
|
|
1868
2066
|
· Cloudflare zone with no Coolify app pointing into it
|
|
1869
2067
|
|
|
1870
2068
|
${chalk.bold("Flags:")}
|
|
@@ -1944,10 +2142,14 @@ function printHelp(topic) {
|
|
|
1944
2142
|
${chalk.bold("What it does:")}
|
|
1945
2143
|
· GlitchTip / OpenPanel: ${chalk.bold("one project per product")}, events tagged by
|
|
1946
2144
|
\`environment\` so dev / staging / prod share the same dashboard.
|
|
1947
|
-
|
|
2145
|
+
· Plausible: one site for the public project domain, with browser tracker env.
|
|
2146
|
+
Observability values are written to ${chalk.cyan(".env.production")} only — dev noise pollutes real metrics.
|
|
1948
2147
|
Pass ${chalk.cyan("--enable-dev-obs")} to populate ${chalk.cyan(".env.development")} too.
|
|
1949
2148
|
· Resend: separate ${chalk.cyan("-dev")} and ${chalk.cyan("-prod")} API keys (audience
|
|
1950
2149
|
safety). Written to the server's dev + prod env respectively.
|
|
2150
|
+
· Search Console: verifies the project domain via Cloudflare DNS TXT,
|
|
2151
|
+
then adds the ${chalk.cyan("sc-domain:<domain>")} property to your Google account.
|
|
2152
|
+
No runtime env is written.
|
|
1951
2153
|
· ${chalk.cyan(".env.production")} is dotenvx-encrypted — commit-safe.
|
|
1952
2154
|
${chalk.cyan(".env.development")} is plaintext — gitignored, not encrypted.
|
|
1953
2155
|
· A 0600 cache of every value is saved under
|
|
@@ -1967,7 +2169,10 @@ function printHelp(topic) {
|
|
|
1967
2169
|
${chalk.bold("Services:")}
|
|
1968
2170
|
glitchtip GLITCHTIP_DSN (server) / PUBLIC_GLITCHTIP_DSN (client)
|
|
1969
2171
|
openpanel OPENPANEL_* (server) / PUBLIC_OPENPANEL_* (client)
|
|
2172
|
+
plausible NEXT_PUBLIC_PLAUSIBLE_DOMAIN / *_SCRIPT_URL (client only)
|
|
1970
2173
|
resend RESEND_API_KEY (server only)
|
|
2174
|
+
search-console
|
|
2175
|
+
Google Search Console domain property (DNS verification; no env)
|
|
1971
2176
|
s3 R2_<BUCKET>_ACCESS_KEY_ID / *_SECRET_ACCESS_KEY / *_BUCKET / R2_ENDPOINT
|
|
1972
2177
|
— mints a per-bucket scoped Cloudflare R2 API token for every
|
|
1973
2178
|
bucket declared in .hatchkit.json (s3Buckets). Single-bucket
|
|
@@ -2016,8 +2221,10 @@ function printHelp(topic) {
|
|
|
2016
2221
|
· Writes \`.hatchkit.json\` so \`update\`, \`add\`, \`keys\` recognise
|
|
2017
2222
|
the project.
|
|
2018
2223
|
· ${chalk.cyan("GitHub remote")} — \`git init\` (if needed),
|
|
2019
|
-
commit, \`gh repo create --private --source=. --push\`.
|
|
2020
|
-
|
|
2224
|
+
commit, \`gh repo create --private|--public --source=. --push\`.
|
|
2225
|
+
Visibility is prompted (default private) or set with
|
|
2226
|
+
\`--github-visibility private|public\`. Skipped when an \`origin\`
|
|
2227
|
+
is already set.
|
|
2021
2228
|
· ${chalk.cyan("Coolify + DNS")} — direct REST-API calls into the
|
|
2022
2229
|
Coolify and Cloudflare you already configured (no Terraform,
|
|
2023
2230
|
no submodule). Finds or creates the Coolify project, picks
|
|
@@ -2027,7 +2234,7 @@ function printHelp(topic) {
|
|
|
2027
2234
|
(DOTENV_PRIVATE_KEY_PRODUCTION + GITHUB_REPO_URL), upserts an
|
|
2028
2235
|
A record \`<domain> → <server-ip>\` on Cloudflare, and triggers
|
|
2029
2236
|
the first deploy. Defaults ON when no matching app exists.
|
|
2030
|
-
· Optionally provisions GlitchTip / OpenPanel / Resend clients
|
|
2237
|
+
· Optionally provisions GlitchTip / OpenPanel / Plausible / Resend clients
|
|
2031
2238
|
(same machinery as \`hatchkit add\`).
|
|
2032
2239
|
· Optionally pushes the dotenvx private key to Coolify
|
|
2033
2240
|
(redundant when the Coolify+DNS step ran — it already does).
|
|
@@ -2086,7 +2293,11 @@ function printHelp(topic) {
|
|
|
2086
2293
|
${chalk.bold("Services:")}
|
|
2087
2294
|
glitchtip Deletes the GlitchTip project
|
|
2088
2295
|
openpanel Deletes the OpenPanel project (and clears cached creds)
|
|
2296
|
+
plausible Deletes the Plausible site cached for this project
|
|
2089
2297
|
resend Finds API keys by name and deletes them
|
|
2298
|
+
search-console
|
|
2299
|
+
Removes the Search Console property from your Google account
|
|
2300
|
+
(keeps DNS verification token / ownership state)
|
|
2090
2301
|
s3 Deletes per-bucket scoped Cloudflare R2 API tokens
|
|
2091
2302
|
(clears the keychain entries and DELETEs upstream)
|
|
2092
2303
|
|
|
@@ -2124,7 +2335,7 @@ function printHelp(topic) {
|
|
|
2124
2335
|
create + adopt:
|
|
2125
2336
|
- GitHub repo ${chalk.dim("gh repo delete")}
|
|
2126
2337
|
- dotenvx private key in keychain ${chalk.dim("keytar deletePassword")}
|
|
2127
|
-
- GlitchTip / OpenPanel / Resend
|
|
2338
|
+
- GlitchTip / OpenPanel / Plausible / Resend ${chalk.dim("DELETE")} per-vendor
|
|
2128
2339
|
- Coolify app / project / database ${chalk.dim("DELETE /api/v1/...")}
|
|
2129
2340
|
|
|
2130
2341
|
adopt-only (fine-grained, never wider than what adopt itself wrote):
|
|
@@ -2269,7 +2480,7 @@ function printHelp(topic) {
|
|
|
2269
2480
|
config Show status of every configured provider (alias: \`status\`)
|
|
2270
2481
|
config add <p> Configure a provider
|
|
2271
2482
|
(coolify, ghcr, hetzner, dns, s3, modal, runpod, hf, replicate,
|
|
2272
|
-
glitchtip, openpanel, resend, stripe)
|
|
2483
|
+
glitchtip, openpanel, plausible, resend, search-console, stripe)
|
|
2273
2484
|
config reset Clear ALL CLI config (providers, tokens, ML registry, ports)
|
|
2274
2485
|
`);
|
|
2275
2486
|
return;
|
|
@@ -2374,7 +2585,8 @@ function printHelp(topic) {
|
|
|
2374
2585
|
create Scaffold a new project (interactive)
|
|
2375
2586
|
adopt Bring an existing project under hatchkit management (run in project dir)
|
|
2376
2587
|
update Add features to an already-scaffolded project (run in project dir)
|
|
2377
|
-
add
|
|
2588
|
+
server add Retrofit a server into a client-only project
|
|
2589
|
+
add Create GlitchTip / OpenPanel / Plausible / Resend clients for an existing project
|
|
2378
2590
|
assets Move bytes between local MinIO and prod buckets (seed/push/pull/migrate)
|
|
2379
2591
|
remove Delete the -dev/-prod clients created by 'add' (inverse of add)
|
|
2380
2592
|
destroy Roll back everything ${chalk.cyan("hatchkit create")} did for a project
|
|
@@ -2408,7 +2620,12 @@ function printHelp(topic) {
|
|
|
2408
2620
|
--yes, -y (with \`create\`) skip prompts, use defaults / --config values
|
|
2409
2621
|
--config <path> (with \`create\`) load JSON overrides for ProjectConfig fields
|
|
2410
2622
|
--name <name> (with \`create\`) set project name without prompting
|
|
2623
|
+
--local-dev[=<slug>] (with \`create\`) enable Tailscale dev URL, optionally with slug
|
|
2624
|
+
--no-local-dev (with \`create\`) skip local-dev wiring
|
|
2411
2625
|
--no-github (with \`create\`) skip GitHub repo creation
|
|
2626
|
+
--github-visibility {private|public}
|
|
2627
|
+
(with \`create\`) visibility for a newly-created GitHub repo.
|
|
2628
|
+
Default: private. Shorthands: \`--private\`, \`--public\`.
|
|
2412
2629
|
--no-deploy (with \`create\`) skip Terraform/Coolify/ML deployment
|
|
2413
2630
|
|
|
2414
2631
|
${chalk.bold("Environment:")}
|