Files
dmg 0fcb1e7e9c
Release / release (push) Failing after 8m32s
feat(presentation): hide held character sheets
2026-09-20 15:13:54 -04:00

85 lines
3.7 KiB
TypeScript

export interface ReleaseInput {
credential: string;
version: string;
commit: string;
notes: string;
jar: Uint8Array;
resourcePack: Uint8Array;
}
export type Request = (url: string, init: RequestInit) => Promise<Response>;
const releases = "https://git.garvis.dev/api/v1/repos/dmg/purpur-fantasy-stick/releases";
export async function publishRelease(input: ReleaseInput, request: Request): Promise<number> {
if (!/^[\x21-\x7e]+$/.test(input.credential)
|| !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(input.version)
|| !/^[a-f0-9]{40}$/.test(input.commit) || input.jar.length === 0 || input.resourcePack.length === 0) {
throw new Error("Invalid release input.");
}
const send: Request = async (url, init) => {
let response: Response;
try {
response = await request(url, { ...init, signal: AbortSignal.timeout(60_000) });
} catch {
throw new Error("Release API request failed.");
}
if (!response.ok) throw new Error(`Release API request failed (HTTP ${response.status}).`);
return response;
};
const response = await send(releases, {
method: "POST",
redirect: "error",
headers: { authorization: `token ${input.credential}`, "content-type": "application/json" },
body: JSON.stringify({
tag_name: `v${input.version}`, target_commitish: input.commit, name: `v${input.version}`,
body: input.notes, draft: true, prerelease: false,
}),
});
const release = await readObject(response, "Invalid draft release response.");
if (typeof release.id !== "number" || !Number.isSafeInteger(release.id) || release.id <= 0
|| release.draft !== true || release.tag_name !== `v${input.version}`) {
throw new Error("Invalid draft release response.");
}
const assetResponse = await send(`${releases}/${release.id}/assets?name=purpur-fantasy-stick-${input.version}.jar`, {
method: "POST", redirect: "error",
headers: { authorization: `token ${input.credential}`, "content-type": "application/octet-stream" },
body: new Uint8Array(input.jar).buffer,
});
const asset = await readObject(assetResponse, "Invalid release asset response.");
if (asset.name !== `purpur-fantasy-stick-${input.version}.jar` || asset.size !== input.jar.length) {
throw new Error("Invalid release asset response.");
}
const packName = `purpur-fantasy-stick-${input.version}-resource-pack.zip`;
const packResponse = await send(`${releases}/${release.id}/assets?name=${packName}`, {
method: "POST", redirect: "error",
headers: { authorization: `token ${input.credential}`, "content-type": "application/octet-stream" },
body: new Uint8Array(input.resourcePack).buffer,
});
const pack = await readObject(packResponse, "Invalid release asset response.");
if (pack.name !== packName || pack.size !== input.resourcePack.length) {
throw new Error("Invalid release asset response.");
}
const publishedResponse = await send(`${releases}/${release.id}`, {
method: "PATCH", redirect: "error",
headers: { authorization: `token ${input.credential}`, "content-type": "application/json" },
body: JSON.stringify({ draft: false }),
});
const published = await readObject(publishedResponse, "Invalid published release response.");
if (published.id !== release.id || published.draft !== false || published.tag_name !== `v${input.version}`) {
throw new Error("Invalid published release response.");
}
return release.id;
}
async function readObject(response: Response, message: string): Promise<Record<string, unknown>> {
let value: unknown;
try {
value = await response.json();
} catch {
throw new Error(message);
}
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(message);
return value as Record<string, unknown>;
}