Files
dmg 23964f7695
Release / release (push) Successful in 3m26s
feat(build): bootstrap Purpur Carry plugin
2026-09-22 21:32:51 -04:00

100 lines
3.2 KiB
TypeScript

export interface ReleaseInput {
credential: string;
version: string;
commit: string;
notes: string;
jar: Uint8Array;
}
export type Request = (url: string, init: RequestInit) => Promise<Response>;
const releases = "https://git.garvis.dev/api/v1/repos/dmg/purpur-carry/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) {
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<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>;
}