initial commit

This commit is contained in:
Hunter
2026-11-01 13:23:44 -05:00
commit e03ea4f770
53 changed files with 10802 additions and 0 deletions

77
lib/publish.ts Normal file
View File

@@ -0,0 +1,77 @@
import { git, MANAGED_PATHS } from "./git";
/* Publish = commit the managed paths -> push -> ask the live site to
* redeploy via its existing POST /api/deploy route (which drops the flag
* scripts/deploy-watch.sh polls for). Each step is reported, and push /
* deploy are skipped rather than failed when they aren't configured. */
export type PublishStep = {
step: "stage" | "commit" | "push" | "deploy";
status: "ok" | "skipped" | "failed";
output: string;
};
function pushEnabled(): boolean {
return (process.env.GIT_PUSH ?? "true").toLowerCase() !== "false";
}
async function triggerDeploy(): Promise<PublishStep> {
const url = process.env.DEPLOY_URL;
const secret = process.env.DEPLOY_TRIGGER_SECRET;
if (!url || !secret) {
return { step: "deploy", status: "skipped", output: "DEPLOY_URL / DEPLOY_TRIGGER_SECRET not set" };
}
try {
const res = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${secret}` },
signal: AbortSignal.timeout(15_000),
});
const body = await res.text();
return {
step: "deploy",
status: res.ok ? "ok" : "failed",
output: `${res.status} ${res.statusText} ${body}`.trim(),
};
} catch (err) {
return { step: "deploy", status: "failed", output: String(err) };
}
}
export async function publish(message: string): Promise<{ ok: boolean; steps: PublishStep[] }> {
const steps: PublishStep[] = [];
const done = (ok: boolean) => ({ ok, steps });
const add = await git(["add", "-A", "--", ...MANAGED_PATHS]);
steps.push({ step: "stage", status: add.ok ? "ok" : "failed", output: add.stderr.trim() });
if (!add.ok) return done(false);
const staged = await git(["diff", "--cached", "--name-only", "--", ...MANAGED_PATHS]);
const files = staged.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
if (files.length === 0) {
steps.push({ step: "commit", status: "skipped", output: "nothing to commit" });
} else {
// Naming the files commits exactly these, even if other changes are staged.
const commit = await git(["commit", "-m", message, "--", ...files]);
steps.push({
step: "commit",
status: commit.ok ? "ok" : "failed",
output: (commit.stdout + commit.stderr).trim(),
});
if (!commit.ok) return done(false);
}
if (!pushEnabled()) {
steps.push({ step: "push", status: "skipped", output: "GIT_PUSH=false" });
steps.push({ step: "deploy", status: "skipped", output: "push is disabled" });
return done(true);
}
const push = await git(["push"], 120_000);
steps.push({ step: "push", status: push.ok ? "ok" : "failed", output: (push.stdout + push.stderr).trim() });
if (!push.ok) return done(false);
const deploy = await triggerDeploy();
steps.push(deploy);
return done(deploy.status !== "failed");
}