hatchkit 0.1.39 → 0.1.41
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.map +1 -1
- package/dist/adopt.js +311 -77
- package/dist/adopt.js.map +1 -1
- package/dist/deploy/coolify-app.d.ts +9 -9
- package/dist/deploy/coolify-app.d.ts.map +1 -1
- package/dist/deploy/coolify-app.js +14 -19
- package/dist/deploy/coolify-app.js.map +1 -1
- package/dist/deploy/coolify.d.ts.map +1 -1
- package/dist/deploy/coolify.js +6 -2
- package/dist/deploy/coolify.js.map +1 -1
- package/dist/deploy/keys.d.ts +7 -2
- package/dist/deploy/keys.d.ts.map +1 -1
- package/dist/deploy/keys.js +27 -7
- package/dist/deploy/keys.js.map +1 -1
- package/dist/deploy/pages.d.ts +41 -0
- package/dist/deploy/pages.d.ts.map +1 -1
- package/dist/deploy/pages.js +360 -13
- package/dist/deploy/pages.js.map +1 -1
- package/dist/deploy/regen-infra.js +4 -0
- package/dist/deploy/regen-infra.js.map +1 -1
- package/dist/deploy/rollback.d.ts.map +1 -1
- package/dist/deploy/rollback.js +94 -22
- package/dist/deploy/rollback.js.map +1 -1
- package/dist/deploy/sync.d.ts +10 -7
- package/dist/deploy/sync.d.ts.map +1 -1
- package/dist/deploy/sync.js +13 -9
- package/dist/deploy/sync.js.map +1 -1
- package/dist/index.js +269 -23
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +144 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/inventory.js +1980 -0
- package/dist/inventory.js.map +1 -0
- package/dist/overview.d.ts +101 -0
- package/dist/overview.d.ts.map +1 -0
- package/dist/overview.js +852 -0
- package/dist/overview.js.map +1 -0
- package/dist/prompts.d.ts +22 -7
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +240 -40
- package/dist/prompts.js.map +1 -1
- package/dist/scaffold/app.js +1 -1
- package/dist/scaffold/app.js.map +1 -1
- package/dist/scaffold/infra.d.ts.map +1 -1
- package/dist/scaffold/infra.js +8 -2
- package/dist/scaffold/infra.js.map +1 -1
- package/dist/scaffold/manifest.d.ts +6 -0
- package/dist/scaffold/manifest.d.ts.map +1 -1
- package/dist/scaffold/manifest.js +1 -0
- package/dist/scaffold/manifest.js.map +1 -1
- package/dist/scaffold/pages-heuristics.d.ts +17 -0
- package/dist/scaffold/pages-heuristics.d.ts.map +1 -0
- package/dist/scaffold/pages-heuristics.js +344 -0
- package/dist/scaffold/pages-heuristics.js.map +1 -0
- package/dist/scaffold/pages-mode.d.ts +10 -0
- package/dist/scaffold/pages-mode.d.ts.map +1 -0
- package/dist/scaffold/pages-mode.js +109 -0
- package/dist/scaffold/pages-mode.js.map +1 -0
- package/dist/scaffold/surfaces.d.ts.map +1 -1
- package/dist/scaffold/surfaces.js +12 -1
- package/dist/scaffold/surfaces.js.map +1 -1
- package/dist/utils/cloudflare-api.d.ts +19 -0
- package/dist/utils/cloudflare-api.d.ts.map +1 -1
- package/dist/utils/cloudflare-api.js +16 -0
- package/dist/utils/cloudflare-api.js.map +1 -1
- package/dist/utils/coolify-api.d.ts +20 -0
- package/dist/utils/coolify-api.d.ts.map +1 -1
- package/dist/utils/coolify-api.js +51 -0
- package/dist/utils/coolify-api.js.map +1 -1
- package/dist/utils/coolify-server-ips.d.ts +6 -12
- package/dist/utils/coolify-server-ips.d.ts.map +1 -1
- package/dist/utils/coolify-server-ips.js +26 -81
- package/dist/utils/coolify-server-ips.js.map +1 -1
- package/dist/utils/run-ledger.d.ts +20 -0
- package/dist/utils/run-ledger.d.ts.map +1 -1
- package/dist/utils/run-ledger.js.map +1 -1
- package/package.json +1 -1
package/dist/adopt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../src/adopt.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../src/adopt.ts"],"names":[],"mappings":"AA4LA,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5D,OAAO,CAAC,IAAI,CAAC,CAuLf"}
|
package/dist/adopt.js
CHANGED
|
@@ -100,9 +100,19 @@ export async function runAdopt(cwd, opts = {}) {
|
|
|
100
100
|
: state.clientDir
|
|
101
101
|
? "client-only"
|
|
102
102
|
: "client-only");
|
|
103
|
+
// Auto-suggest gh-pages when (a) the manifest already recorded it,
|
|
104
|
+
// or (b) the manifest is silent AND the project looks client-only
|
|
105
|
+
// AND there's no Coolify app already wired up. Otherwise default
|
|
106
|
+
// to coolify (the existing behaviour).
|
|
107
|
+
const inferredDeploymentMode = m?.deploymentMode === "gh-pages"
|
|
108
|
+
? "gh-pages"
|
|
109
|
+
: m?.deploymentMode === "scaffold-only"
|
|
110
|
+
? "scaffold-only"
|
|
111
|
+
: "coolify";
|
|
103
112
|
let plan = {
|
|
104
113
|
name: m?.name ?? state.packageName ?? "",
|
|
105
114
|
domain: m?.domain ?? "",
|
|
115
|
+
deploymentMode: inferredDeploymentMode,
|
|
106
116
|
// Description resolution order:
|
|
107
117
|
// 1. Persisted manifest value (`--resume` recovery — a previous
|
|
108
118
|
// run already settled this).
|
|
@@ -147,7 +157,50 @@ export async function runAdopt(cwd, opts = {}) {
|
|
|
147
157
|
// redundant in that branch.
|
|
148
158
|
pushKey: !!state.coolifyAppMatch,
|
|
149
159
|
};
|
|
160
|
+
// When the plan starts in gh-pages mode (from a `--resume` of an
|
|
161
|
+
// earlier adopt, or a manifest the user committed by hand), run
|
|
162
|
+
// the heuristics once up front. Block before even entering the
|
|
163
|
+
// review loop on findings that would make Pages refuse to build —
|
|
164
|
+
// the user needs to either fix the project or switch back to
|
|
165
|
+
// coolify. The edit-step handler runs the same check when the
|
|
166
|
+
// user *switches into* gh-pages from inside the loop, so this
|
|
167
|
+
// covers the gap where they never edit the deploymentMode row.
|
|
168
|
+
if (plan.deploymentMode === "gh-pages") {
|
|
169
|
+
const { detectPagesIncompatibilities, hasBlockingFinding } = await import("./scaffold/pages-heuristics.js");
|
|
170
|
+
const findings = detectPagesIncompatibilities(state.projectDir);
|
|
171
|
+
if (findings.length > 0) {
|
|
172
|
+
console.log(chalk.bold("\n Pages compatibility findings:\n"));
|
|
173
|
+
for (const f of findings) {
|
|
174
|
+
const tag = f.level === "block"
|
|
175
|
+
? chalk.red("✗ block")
|
|
176
|
+
: f.level === "warn"
|
|
177
|
+
? chalk.yellow("! warn")
|
|
178
|
+
: chalk.dim("· info");
|
|
179
|
+
console.log(` ${tag} ${chalk.bold(f.title)}`);
|
|
180
|
+
console.log(chalk.dim(` ${f.detail}`));
|
|
181
|
+
for (const ev of f.evidence) {
|
|
182
|
+
console.log(chalk.dim(` → ${ev}`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
console.log();
|
|
186
|
+
if (hasBlockingFinding(findings)) {
|
|
187
|
+
console.log(chalk.red(" Blocking findings — Pages can't host this project as-is. Fix the issues above\n" +
|
|
188
|
+
" or pick a different deployment mode in the review screen."));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
150
192
|
plan = await reviewLoop(state, plan);
|
|
193
|
+
// Re-check after the review loop in case the user kept (or switched
|
|
194
|
+
// back to) gh-pages despite blockers being present. The edit handler
|
|
195
|
+
// refuses the switch into gh-pages over blockers, but it can't catch
|
|
196
|
+
// the case where blockers exist on entry AND the user stays put.
|
|
197
|
+
if (plan.deploymentMode === "gh-pages") {
|
|
198
|
+
const { detectPagesIncompatibilities, hasBlockingFinding } = await import("./scaffold/pages-heuristics.js");
|
|
199
|
+
const findings = detectPagesIncompatibilities(state.projectDir);
|
|
200
|
+
if (hasBlockingFinding(findings)) {
|
|
201
|
+
throw new Error("Pages compatibility blockers still present — refusing to adopt with gh-pages mode. Fix the issues listed above or re-run adopt and pick coolify/scaffold-only.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
151
204
|
await executePlan(state, plan, {
|
|
152
205
|
resume: !!opts.resume,
|
|
153
206
|
regeneratePipeline: !!opts.regeneratePipeline,
|
|
@@ -232,7 +285,12 @@ async function detectProject(projectDir) {
|
|
|
232
285
|
]);
|
|
233
286
|
coolifyGithubSourceCount = sources.length;
|
|
234
287
|
if (packageName) {
|
|
235
|
-
const wanted = [
|
|
288
|
+
const wanted = [
|
|
289
|
+
packageName,
|
|
290
|
+
`${packageName}-server`,
|
|
291
|
+
`${packageName}-client`,
|
|
292
|
+
`${packageName}-web`,
|
|
293
|
+
];
|
|
236
294
|
const match = apps.find((a) => wanted.includes(a.name));
|
|
237
295
|
if (match)
|
|
238
296
|
coolifyAppMatch = { uuid: match.uuid, name: match.name };
|
|
@@ -607,70 +665,85 @@ function buildAdoptGroups(state, plan) {
|
|
|
607
665
|
},
|
|
608
666
|
],
|
|
609
667
|
},
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
668
|
+
// The Docker + GH Actions pipeline is Coolify-targeted (it
|
|
669
|
+
// configures the redeploy webhook + builds an image). gh-pages
|
|
670
|
+
// ships its own workflow that uploads to Pages instead, so we
|
|
671
|
+
// hide this group when the user picks Pages.
|
|
672
|
+
...(plan.deploymentMode === "coolify"
|
|
673
|
+
? [
|
|
613
674
|
{
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
675
|
+
title: "Build pipeline",
|
|
676
|
+
steps: [
|
|
677
|
+
{
|
|
678
|
+
key: "scaffoldBuildPipeline",
|
|
679
|
+
label: "Docker + GH Actions",
|
|
680
|
+
set: true,
|
|
681
|
+
summary: renderBuildPipelineSummary(state, plan),
|
|
682
|
+
},
|
|
683
|
+
],
|
|
618
684
|
},
|
|
619
|
-
]
|
|
620
|
-
|
|
685
|
+
]
|
|
686
|
+
: []),
|
|
621
687
|
{
|
|
622
688
|
title: "Deploy",
|
|
623
689
|
steps: [
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
690
|
+
// Top-line choice: where this project deploys. Hides the
|
|
691
|
+
// Coolify-specific rows when set to gh-pages.
|
|
692
|
+
{
|
|
693
|
+
key: "deploymentMode",
|
|
694
|
+
label: "Deployment mode",
|
|
695
|
+
set: true,
|
|
696
|
+
summary: renderAdoptDeploymentModeSummary(plan.deploymentMode, plan.surfaces),
|
|
697
|
+
},
|
|
698
|
+
// Coolify-specific rows — only shown when actually deploying
|
|
699
|
+
// to Coolify. gh-pages skips this branch entirely.
|
|
700
|
+
...(plan.deploymentMode === "coolify"
|
|
701
|
+
? [
|
|
702
|
+
(() => {
|
|
703
|
+
// Visibility row. Picking the wrong path here is the #1
|
|
704
|
+
// cause of "Permission denied (publickey)" deploy failures
|
|
705
|
+
// (Coolify tries SSH against a repo whose GitHub App isn't
|
|
706
|
+
// installed). Mark `set: false` when we couldn't auto-detect
|
|
707
|
+
// visibility from `gh repo view`, so the cursor parks on it.
|
|
708
|
+
const detected = state.gitRemoteIsPrivate;
|
|
709
|
+
const summaryBase = plan.isPrivate
|
|
710
|
+
? "private — Coolify clones via GitHub App SSH key"
|
|
711
|
+
: "public — Coolify clones via HTTPS";
|
|
712
|
+
const detectedHint = detected === undefined && state.gitRemoteUrl
|
|
713
|
+
? chalk.dim(" (couldn't auto-detect — confirm)")
|
|
714
|
+
: detected !== undefined && detected !== plan.isPrivate
|
|
715
|
+
? chalk.yellow(` (gh says ${detected ? "private" : "public"} — overridden)`)
|
|
716
|
+
: "";
|
|
717
|
+
return {
|
|
718
|
+
key: "isPrivate",
|
|
719
|
+
label: "Repo visibility",
|
|
720
|
+
set: !(detected === undefined && !!state.gitRemoteUrl),
|
|
721
|
+
summary: `${summaryBase}${detectedHint}`,
|
|
722
|
+
};
|
|
723
|
+
})(),
|
|
724
|
+
(() => {
|
|
725
|
+
const missingSource = plan.wireCoolify &&
|
|
726
|
+
plan.isPrivate &&
|
|
727
|
+
state.coolifyConfigured &&
|
|
728
|
+
state.coolifyGithubSourceCount === 0;
|
|
729
|
+
const baseSummary = plan.wireCoolify
|
|
730
|
+
? state.coolifyAppMatch
|
|
731
|
+
? chalk.dim(`existing app "${state.coolifyAppMatch.name}" — will reconcile build pack`)
|
|
732
|
+
: `yes — create app + upsert DNS (port ${plan.appPort})`
|
|
733
|
+
: state.coolifyAppMatch
|
|
734
|
+
? chalk.dim(`already exists: ${state.coolifyAppMatch.name}`)
|
|
735
|
+
: chalk.dim("no");
|
|
736
|
+
return {
|
|
737
|
+
key: "wireCoolify",
|
|
738
|
+
label: "Coolify + DNS",
|
|
739
|
+
set: !missingSource,
|
|
740
|
+
summary: missingSource
|
|
741
|
+
? `${baseSummary} ${chalk.yellow("(needs Coolify GitHub App — install one or set visibility to public)")}`
|
|
742
|
+
: baseSummary,
|
|
743
|
+
};
|
|
744
|
+
})(),
|
|
745
|
+
]
|
|
746
|
+
: []),
|
|
674
747
|
],
|
|
675
748
|
},
|
|
676
749
|
{
|
|
@@ -682,20 +755,39 @@ function buildAdoptGroups(state, plan) {
|
|
|
682
755
|
set: true,
|
|
683
756
|
summary: plan.services.length > 0 ? plan.services.join(", ") : chalk.dim("skip provisioning"),
|
|
684
757
|
},
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
: "
|
|
693
|
-
|
|
694
|
-
|
|
758
|
+
// `pushKey` only matters when a Coolify app is the deploy
|
|
759
|
+
// target — Pages reads no secrets from a Coolify env, so the
|
|
760
|
+
// row would just be noise on the gh-pages path.
|
|
761
|
+
...(plan.deploymentMode === "coolify"
|
|
762
|
+
? [
|
|
763
|
+
{
|
|
764
|
+
key: "pushKey",
|
|
765
|
+
label: "Push key to Coolify",
|
|
766
|
+
set: true,
|
|
767
|
+
summary: plan.pushKey
|
|
768
|
+
? state.coolifyAppMatch
|
|
769
|
+
? `yes (${state.coolifyAppMatch.name})`
|
|
770
|
+
: "yes — Coolify app must exist by name"
|
|
771
|
+
: chalk.dim("no"),
|
|
772
|
+
},
|
|
773
|
+
]
|
|
774
|
+
: []),
|
|
695
775
|
],
|
|
696
776
|
},
|
|
697
777
|
];
|
|
698
778
|
}
|
|
779
|
+
function renderAdoptDeploymentModeSummary(mode, surfaces) {
|
|
780
|
+
switch (mode) {
|
|
781
|
+
case "coolify":
|
|
782
|
+
return "Coolify (full-stack on Hetzner)";
|
|
783
|
+
case "gh-pages":
|
|
784
|
+
return surfaces === "client-only"
|
|
785
|
+
? "GitHub Pages (static)"
|
|
786
|
+
: chalk.yellow("GitHub Pages — needs surfaces=client-only");
|
|
787
|
+
case "scaffold-only":
|
|
788
|
+
return "scaffold only (no deploy)";
|
|
789
|
+
}
|
|
790
|
+
}
|
|
699
791
|
async function editAdoptStep(state, plan, step) {
|
|
700
792
|
if (step === "name") {
|
|
701
793
|
const name = (await input({
|
|
@@ -739,9 +831,17 @@ async function editAdoptStep(state, plan, step) {
|
|
|
739
831
|
// Adjust the dir fields when the surface changes — dropping
|
|
740
832
|
// server/client dirs that are no longer relevant, and setting
|
|
741
833
|
// sane defaults for newly-relevant ones.
|
|
834
|
+
// Also: switching away from client-only invalidates gh-pages
|
|
835
|
+
// (Pages can't host a backend). Snap deploymentMode back to
|
|
836
|
+
// coolify in that case so the user doesn't keep an invalid combo.
|
|
837
|
+
const nextDeploymentMode = plan.deploymentMode === "gh-pages" && next !== "client-only" ? "coolify" : plan.deploymentMode;
|
|
838
|
+
if (plan.deploymentMode === "gh-pages" && next !== "client-only") {
|
|
839
|
+
console.log(chalk.yellow(" ⚠ gh-pages requires client-only surfaces — switched deployment mode back to coolify."));
|
|
840
|
+
}
|
|
742
841
|
return {
|
|
743
842
|
...plan,
|
|
744
843
|
surfaces: next,
|
|
844
|
+
deploymentMode: nextDeploymentMode,
|
|
745
845
|
serverDir: next === "client-only"
|
|
746
846
|
? undefined
|
|
747
847
|
: (plan.serverDir ?? state.serverDir ?? state.projectDir),
|
|
@@ -750,6 +850,58 @@ async function editAdoptStep(state, plan, step) {
|
|
|
750
850
|
: (plan.clientDir ?? state.clientDir ?? state.projectDir),
|
|
751
851
|
};
|
|
752
852
|
}
|
|
853
|
+
if (step === "deploymentMode") {
|
|
854
|
+
const choices = [
|
|
855
|
+
{ name: "Coolify (full-stack on Hetzner)", value: "coolify" },
|
|
856
|
+
];
|
|
857
|
+
if (plan.surfaces === "client-only") {
|
|
858
|
+
choices.push({ name: "GitHub Pages (static)", value: "gh-pages" });
|
|
859
|
+
}
|
|
860
|
+
choices.push({ name: "Scaffold only — don't deploy", value: "scaffold-only" });
|
|
861
|
+
const next = await select({
|
|
862
|
+
message: "Where do you want to deploy?",
|
|
863
|
+
choices,
|
|
864
|
+
default: plan.deploymentMode,
|
|
865
|
+
});
|
|
866
|
+
// When switching INTO gh-pages, run the static-site sanity checks
|
|
867
|
+
// and surface any blockers before letting the user proceed. They
|
|
868
|
+
// can still pick gh-pages over a "warn" finding, but "block"
|
|
869
|
+
// (e.g. Next without `output: "export"`) requires they either fix
|
|
870
|
+
// the project first or step back to coolify.
|
|
871
|
+
if (next === "gh-pages" && plan.deploymentMode !== "gh-pages") {
|
|
872
|
+
const { detectPagesIncompatibilities, hasBlockingFinding } = await import("./scaffold/pages-heuristics.js");
|
|
873
|
+
const findings = detectPagesIncompatibilities(state.projectDir);
|
|
874
|
+
if (findings.length > 0) {
|
|
875
|
+
console.log(chalk.bold("\n Pages compatibility findings:\n"));
|
|
876
|
+
for (const f of findings) {
|
|
877
|
+
const tag = f.level === "block"
|
|
878
|
+
? chalk.red("✗ block")
|
|
879
|
+
: f.level === "warn"
|
|
880
|
+
? chalk.yellow("! warn")
|
|
881
|
+
: chalk.dim("· info");
|
|
882
|
+
console.log(` ${tag} ${chalk.bold(f.title)}`);
|
|
883
|
+
console.log(chalk.dim(` ${f.detail}`));
|
|
884
|
+
for (const ev of f.evidence) {
|
|
885
|
+
console.log(chalk.dim(` → ${ev}`));
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
console.log();
|
|
889
|
+
if (hasBlockingFinding(findings)) {
|
|
890
|
+
console.log(chalk.red(" Blocking findings present — Pages won't be able to host this project as-is."));
|
|
891
|
+
console.log(chalk.dim(" Fix the issues above (or stay on coolify) and re-pick."));
|
|
892
|
+
// Don't switch — leave plan.deploymentMode unchanged.
|
|
893
|
+
return plan;
|
|
894
|
+
}
|
|
895
|
+
const proceed = await confirm({
|
|
896
|
+
message: "Proceed with gh-pages despite the warnings?",
|
|
897
|
+
default: false,
|
|
898
|
+
});
|
|
899
|
+
if (!proceed)
|
|
900
|
+
return plan;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return { ...plan, deploymentMode: next };
|
|
904
|
+
}
|
|
753
905
|
if (step === "serverDir") {
|
|
754
906
|
const picked = (await input({
|
|
755
907
|
message: "Server env directory (relative to project root):",
|
|
@@ -971,7 +1123,11 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
971
1123
|
// anything that already exists, so this is idempotent across re-runs.
|
|
972
1124
|
// Must run BEFORE Coolify wiring so the docker-compose.yml exists
|
|
973
1125
|
// by the time Coolify clones the repo for the first deploy.
|
|
974
|
-
|
|
1126
|
+
//
|
|
1127
|
+
// Gated on coolify mode — the Coolify-targeted Dockerfile + deploy
|
|
1128
|
+
// webhook workflow aren't useful for gh-pages, which uses its own
|
|
1129
|
+
// `gh-pages.yml` workflow written later in step 3c-pages.
|
|
1130
|
+
if (plan.scaffoldBuildPipeline && plan.deploymentMode === "coolify") {
|
|
975
1131
|
const pipeResult = await scaffoldBuildPipelineNow(state, plan, remoteUrl, {
|
|
976
1132
|
force: !!opts.regeneratePipeline,
|
|
977
1133
|
});
|
|
@@ -990,7 +1146,9 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
990
1146
|
// DNS-provider REST endpoints with credentials we already have in
|
|
991
1147
|
// keychain. Idempotent on the DNS side (upsert); not yet on the
|
|
992
1148
|
// app-create side (Coolify accepts duplicate app names).
|
|
993
|
-
|
|
1149
|
+
//
|
|
1150
|
+
// Skipped for gh-pages — Pages handles its own DNS in step 3c-pages.
|
|
1151
|
+
if (plan.wireCoolify && plan.deploymentMode === "coolify" && remoteUrl) {
|
|
994
1152
|
try {
|
|
995
1153
|
const { wireProjectIntoCoolify } = await import("./deploy/coolify-app.js");
|
|
996
1154
|
coolifyResult = await wireProjectIntoCoolify({
|
|
@@ -1097,8 +1255,12 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
1097
1255
|
// created" and "app already existed before adopt" branches. Need
|
|
1098
1256
|
// an app uuid in either case. Run BEFORE the initial git push
|
|
1099
1257
|
// (below) so the workflow's first run has the secrets in place.
|
|
1258
|
+
//
|
|
1259
|
+
// Skipped for gh-pages — there's no Coolify webhook to hit.
|
|
1100
1260
|
const appUuidForSecrets = coolifyResult?.appUuid ?? state.coolifyAppMatch?.uuid;
|
|
1101
|
-
if (plan.scaffoldBuildPipeline &&
|
|
1261
|
+
if (plan.scaffoldBuildPipeline &&
|
|
1262
|
+
plan.deploymentMode === "coolify" &&
|
|
1263
|
+
appUuidForSecrets) {
|
|
1102
1264
|
const slug = repoSlugFromRemote(remoteUrl);
|
|
1103
1265
|
if (slug) {
|
|
1104
1266
|
await setCoolifyDeploySecrets({
|
|
@@ -1122,7 +1284,10 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
1122
1284
|
// a Coolify one) — gates only on having the build pipeline + a
|
|
1123
1285
|
// resolvable repo slug. Best-effort: failure surfaces as a caveat
|
|
1124
1286
|
// with a copy-pasteable manual recipe so adopt finishes cleanly.
|
|
1125
|
-
|
|
1287
|
+
//
|
|
1288
|
+
// gh-pages doesn't need this secret — the Pages workflow builds
|
|
1289
|
+
// the client without consuming server-side env.
|
|
1290
|
+
if (plan.scaffoldBuildPipeline && plan.deploymentMode === "coolify") {
|
|
1126
1291
|
const slug = repoSlugFromRemote(remoteUrl);
|
|
1127
1292
|
if (slug) {
|
|
1128
1293
|
const secretName = "DOTENV_PRIVATE_KEY_PRODUCTION";
|
|
@@ -1152,6 +1317,74 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
1152
1317
|
console.log(chalk.dim(" · Couldn't resolve owner/repo from git remote — push DOTENV_PRIVATE_KEY_PRODUCTION to Actions manually."));
|
|
1153
1318
|
}
|
|
1154
1319
|
}
|
|
1320
|
+
// Step 3c-pages: GitHub Pages setup (gh-pages mode only).
|
|
1321
|
+
// Writes .github/workflows/gh-pages.yml + CNAME locally and
|
|
1322
|
+
// configures the remote side (enable Pages, register cname,
|
|
1323
|
+
// wire DNS, poll for the Let's Encrypt cert, flip
|
|
1324
|
+
// https_enforced). Must happen BEFORE the push step below so
|
|
1325
|
+
// the new files land in the same push and the workflow runs.
|
|
1326
|
+
//
|
|
1327
|
+
// Auto-detect heuristic: for a client-only project we deploy
|
|
1328
|
+
// the client dir (typically `packages/client/`). The detected
|
|
1329
|
+
// shape mirrors what gh-pages's own pickSite would have chosen
|
|
1330
|
+
// — node-build, pnpm, root-level build script.
|
|
1331
|
+
if (plan.deploymentMode === "gh-pages" && remoteUrl) {
|
|
1332
|
+
try {
|
|
1333
|
+
const { runPagesSetupProgrammatic } = await import("./deploy/pages.js");
|
|
1334
|
+
const { exec: bashExec } = await import("./utils/exec.js");
|
|
1335
|
+
const slug = repoSlugFromRemote(remoteUrl) ?? plan.name;
|
|
1336
|
+
// For adopt we don't know the exact build layout of the
|
|
1337
|
+
// user's project. Best-guess for a client-only Next.js
|
|
1338
|
+
// app: `packages/client/out` (post-`output: export` build).
|
|
1339
|
+
// If the user has a different layout they can re-run
|
|
1340
|
+
// `hatchkit gh-pages` from the project dir to override.
|
|
1341
|
+
const clientDir = plan.clientDir
|
|
1342
|
+
? relative(state.projectDir, plan.clientDir)
|
|
1343
|
+
: "packages/client";
|
|
1344
|
+
const detected = {
|
|
1345
|
+
kind: "node-build",
|
|
1346
|
+
publishDir: clientDir ? `${clientDir}/out` : "out",
|
|
1347
|
+
packageManager: "pnpm",
|
|
1348
|
+
buildScript: "build",
|
|
1349
|
+
workDir: "",
|
|
1350
|
+
};
|
|
1351
|
+
const { pageUrl } = await runPagesSetupProgrammatic(state.projectDir, {
|
|
1352
|
+
detected,
|
|
1353
|
+
domain: plan.domain || null,
|
|
1354
|
+
});
|
|
1355
|
+
ledger.record({
|
|
1356
|
+
kind: "ghPages",
|
|
1357
|
+
repo: slug,
|
|
1358
|
+
projectDir: state.projectDir,
|
|
1359
|
+
cname: plan.domain || undefined,
|
|
1360
|
+
});
|
|
1361
|
+
// Stage and commit so the next push picks up the workflow
|
|
1362
|
+
// + CNAME file. If nothing changed (idempotent re-run), the
|
|
1363
|
+
// status check skips the commit entirely.
|
|
1364
|
+
await bashExec("git", ["add", "-A"], { cwd: state.projectDir, silent: true });
|
|
1365
|
+
const status = await bashExec("git", ["status", "--porcelain"], {
|
|
1366
|
+
cwd: state.projectDir,
|
|
1367
|
+
silent: true,
|
|
1368
|
+
});
|
|
1369
|
+
if (status.stdout.trim()) {
|
|
1370
|
+
await bashExec("git", ["commit", "-m", "ci: GitHub Pages setup"], {
|
|
1371
|
+
cwd: state.projectDir,
|
|
1372
|
+
silent: true,
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
console.log(chalk.green(` ✓ GitHub Pages will publish at ${pageUrl}`));
|
|
1376
|
+
}
|
|
1377
|
+
catch (err) {
|
|
1378
|
+
caveats.push({
|
|
1379
|
+
title: "GitHub Pages not wired",
|
|
1380
|
+
reason: err.message,
|
|
1381
|
+
recovery: [
|
|
1382
|
+
`Re-run from the project dir: hatchkit gh-pages`,
|
|
1383
|
+
`(it'll pick up where this left off and is idempotent).`,
|
|
1384
|
+
],
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1155
1388
|
// Step 3d: push the working branch to origin. Done AFTER secrets
|
|
1156
1389
|
// are set so the workflow's first run can hit the Coolify webhook
|
|
1157
1390
|
// without falling through to the "secret not set" branch. Skipped
|
|
@@ -1525,9 +1758,6 @@ async function executePlan(state, plan, opts = { resume: false }) {
|
|
|
1525
1758
|
.filter(Boolean)
|
|
1526
1759
|
.join(" / ");
|
|
1527
1760
|
console.log(` Coolify: ${chalk.cyan(coolifyResult.appUuid)} ${chalk.dim(`@ ${ipDisplay || "?"}`)}`);
|
|
1528
|
-
if (coolifyResult.ipMismatchWarning) {
|
|
1529
|
-
console.log(` ${chalk.yellow("⚠")} ${chalk.dim(coolifyResult.ipMismatchWarning)}`);
|
|
1530
|
-
}
|
|
1531
1761
|
const records = [];
|
|
1532
1762
|
if (coolifyResult.dnsRecordId)
|
|
1533
1763
|
records.push(`A ${plan.domain} → ${coolifyResult.serverIpv4}`);
|
|
@@ -1826,6 +2056,10 @@ function writeAdoptManifest(projectDir, plan) {
|
|
|
1826
2056
|
mlServices: [],
|
|
1827
2057
|
s3Provider: (() => (plan.features.includes("s3") ? "existing" : "none"))(),
|
|
1828
2058
|
deployTarget: "existing",
|
|
2059
|
+
// Persist deployment mode so `--resume` recovers the gh-pages
|
|
2060
|
+
// path without re-asking the user. Same back-compat invariant
|
|
2061
|
+
// as `surfaces` — readers without this field fall back to coolify.
|
|
2062
|
+
deploymentMode: plan.deploymentMode,
|
|
1829
2063
|
ports: { server: 3000, client: 3001 },
|
|
1830
2064
|
// Persist the surface choice so `--resume` doesn't re-infer
|
|
1831
2065
|
// "server-only" just because there's no client/ directory in the
|