/*
RFC-0896: customdomain.register command handler — registers a proxied A record
and Workers route for a site's apex domain via the Cloudflare API. Idempotent:
skips existing correct records, errors on mismatched A records. Coexists with
non-A records (MX, TXT, etc.) for the same domain name — only A records are
managed by this command.
- Do not auto-update mismatched A DNS records or Workers routes — operator must fix manually.
- Do not modify or delete non-A records (MX, TXT, etc.) — those are managed by dns.record.upsert.
- Do not handle DNS propagation waiting — that is out of scope.
- Do not handle www subdomain — that is redirect.register's responsibility.
- RFC-0896: initial customdomain.register command handler.
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
- RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt
*/
import type {
KernelCommandInput,
KernelCommandResult,
KernelRuntimeContext,
} from "@warpgogol/werkstatt-engine/kernel";
import { flagSite } from "../leitstand/deploy-helpers.ts";
import {
listDnsRecords,
createDnsRecord,
listWorkersRoutes,
createWorkersRoute,
} from "../leitstand/adapters/cloudflare-api.ts";
import {
resolveCustomDomainConfig,
resolveCustomDomainEnv,
buildApexDnsRecord,
buildApexRoutePattern,
} from "./customdomain-helpers.ts";
export interface CustomDomainRegisterResult {
command: "customdomain.register";
systemId: string;
domain: string;
dnsRecord: {
id: string;
type: "A";
name: string;
content: string;
proxied: true;
created: boolean;
};
workersRoute: {
id: string;
pattern: string;
script: string;
created: boolean;
};
state: "registered" | "already-registered" | "failed";
}
export async function runCustomdomainRegister(
input: KernelCommandInput,
context: KernelRuntimeContext,
): Promise> {
const { workspaceRoot } = context;
const systemId = flagSite(input);
if (!systemId) throw new Error("[customdomain.register] --site is required");
const { zoneId, apexDomain, workerName } = await resolveCustomDomainConfig(
workspaceRoot,
systemId,
);
const env = await resolveCustomDomainEnv();
const apiToken = env["CLOUDFLARE_API_TOKEN"];
const expectedDnsRecord = buildApexDnsRecord(apexDomain);
const expectedRoutePattern = buildApexRoutePattern(apexDomain);
const existingDnsRecords = await listDnsRecords(zoneId, apiToken, apexDomain);
// Only look for A records — MX, TXT, and other record types for the same
// domain name can coexist with the proxied A record and are not managed here.
const matchingDns = existingDnsRecords.find((r) => r.name === apexDomain && r.type === "A");
let dnsResult: CustomDomainRegisterResult["dnsRecord"];
let dnsCreated = false;
if (matchingDns) {
if (matchingDns.proxied === true) {
dnsResult = {
id: matchingDns.id,
type: "A",
name: matchingDns.name,
content: matchingDns.content,
proxied: true,
created: false,
};
} else {
throw new Error(
`[customdomain.register] A record for '${apexDomain}' exists but is not proxied. ` +
`Current: proxied=${matchingDns.proxied}. ` +
`Expected: proxied=true. ` +
`Enable Cloudflare proxy on the A record manually before re-running.`,
);
}
} else {
try {
const created = await createDnsRecord(zoneId, apiToken, expectedDnsRecord);
dnsCreated = true;
dnsResult = {
id: created.id,
type: "A",
name: created.name,
content: created.content,
proxied: true,
created: true,
};
} catch (err) {
// Cloudflare error 81062: "A DNS record managed by Workers already exists on that host."
// listDnsRecords does not return Workers-managed records — treat as idempotent success.
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes("81062")) {
dnsResult = {
id: "workers-managed",
type: "A",
name: apexDomain,
content: expectedDnsRecord.content,
proxied: true,
created: false,
};
} else {
throw err;
}
}
}
const existingRoutes = await listWorkersRoutes(zoneId, apiToken);
const matchingRoute = existingRoutes.find((r) => r.pattern === expectedRoutePattern);
let routeResult: CustomDomainRegisterResult["workersRoute"];
let routeCreated = false;
if (matchingRoute) {
if (matchingRoute.script === workerName) {
routeResult = {
id: matchingRoute.id,
pattern: matchingRoute.pattern,
script: matchingRoute.script ?? workerName,
created: false,
};
} else {
throw new Error(
`[customdomain.register] Workers route for '${expectedRoutePattern}' exists but points to wrong script. ` +
`Current: script=${matchingRoute.script}. Expected: script=${workerName}. ` +
`Delete or fix the route manually before re-running.`,
);
}
} else {
const created = await createWorkersRoute(zoneId, apiToken, {
pattern: expectedRoutePattern,
script: workerName,
});
routeCreated = true;
routeResult = {
id: created.id,
pattern: created.pattern,
script: created.script ?? workerName,
created: true,
};
}
const state: CustomDomainRegisterResult["state"] =
dnsCreated || routeCreated ? "registered" : "already-registered";
return {
data: {
command: "customdomain.register",
systemId,
domain: apexDomain,
dnsRecord: dnsResult,
workersRoute: routeResult,
state,
},
exitCode: 0,
summary: `[customdomain.register] ${apexDomain}: ${state} (dns: ${dnsCreated ? "created" : "exists"}, route: ${routeCreated ? "created" : "exists"})`,
nextSteps: [
{
action: `Verify the domain is serving: curl -I https://${apexDomain}`,
kind: "optional",
},
],
};
}