import { appendFileSync } from "node:fs"; import { execFileSync } from "node:child_process"; import { pathToFileURL } from "node:url"; /** Keep manual test builds out of the production channel. */ export function imagePublication({ event, ref, inputTag, revision }) { if (!/^[0-9a-f]{40}$/.test(revision)) throw new Error("Expected the full SHA-1 of the checked-out commit."); let tag, channel; if (event === "workflow_dispatch") { tag = inputTag || `dev-${revision.slice(0, 7)}`; const commit = /^dev-([0-9a-f]{7,40})$/.exec(tag)?.[1]; if (!commit || tag !== tag.trim()) throw new Error( "Manual image tags must be dev- (7-40 lowercase hex characters).", ); if (!revision.startsWith(commit)) throw new Error( "The dev tag does not match the checked-out commit. Select the matching source branch or Git tag.", ); channel = "dev"; } else if (event === "push" && ref?.startsWith("refs/tags/")) { tag = ref.slice("refs/tags/".length); if ( !/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(tag) || tag.length > 128 || tag !== tag.trim() ) throw new Error( "Production requires a stable Git version tag: vMAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH.", ); channel = "prod"; } else { throw new Error( "Image publication requires a version tag push or a manual dev build.", ); } return { tag, channel, revision }; } if ( process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href ) { const env = process.env; const revision = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8", }).trim(); const publication = imagePublication({ event: env.EVENT_NAME, ref: env.GIT_REF, inputTag: env.INPUT_TAG, revision, }); const registry = new URL(env.SERVER_URL).host; const image = `${registry}/${env.REPOSITORY.toLowerCase()}`; const source = `${env.SERVER_URL.replace(/\/$/, "")}/${env.REPOSITORY}`; const outputs = { registry, image, source, ...publication }; appendFileSync( env.GITHUB_OUTPUT, Object.entries(outputs) .map(([key, value]) => `${key}=${value}\n`) .join(""), ); console.log( `Publishing ${image}:${publication.tag} and :${publication.channel} from ${revision}`, ); }