export interface ReleaseInput { credential: string; version: string; commit: string; notes: string; jar: Uint8Array; } export type Request = (url: string, init: RequestInit) => Promise; const releases = "https://git.garvis.dev/api/v1/repos/dmg/purpur-carry/releases"; export async function publishRelease(input: ReleaseInput, request: Request): Promise { 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) { 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 assetName = `purpur-carry-${input.version}.jar`; const assetResponse = await send(`${releases}/${release.id}/assets?name=${assetName}`, { 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 !== assetName || asset.size !== input.jar.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> { 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; }