Files
chuan f479e94b2d
Release / release (push) Failing after 5m36s
Use Gitea tag deletion endpoint
2026-05-18 01:34:38 +08:00

133 lines
3.6 KiB
TypeScript

import { readdir } from "node:fs/promises";
import { basename, join } from "node:path";
interface Release {
id: number;
tag_name: string;
upload_url?: string;
}
const root = process.cwd();
const packageJson = JSON.parse(await Bun.file(join(root, "package.json")).text()) as { version: string };
const version = packageJson.version;
const tag = `v${version}`;
const repository = process.env.GITEA_REPOSITORY;
const token = process.env.GITEA_TOKEN;
const apiUrl = (process.env.GITEA_API_URL ?? "").replace(/\/$/, "");
const releaseDir = join(root, "release");
const releaseName = `gitea-github-theme-v${version}`;
if (!repository) {
throw new Error("GITEA_REPOSITORY is required");
}
if (!token) {
throw new Error("GITEA_TOKEN is required");
}
if (!apiUrl) {
throw new Error("GITEA_API_URL is required");
}
const [owner, repo] = repository.split("/");
const headers = {
Accept: "application/json",
Authorization: `token ${token}`,
};
function resolveApiUrl(pathOrUrl: string) {
if (/^https?:\/\//.test(pathOrUrl)) {
return pathOrUrl;
}
return `${apiUrl}${pathOrUrl}`;
}
async function request(pathOrUrl: string, init: RequestInit = {}) {
const url = resolveApiUrl(pathOrUrl);
const response = await fetch(url, {
...init,
headers: {
...headers,
...(init.headers ?? {}),
},
});
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error(`${init.method ?? "GET"} ${url} failed: ${response.status} ${await response.text()}`);
}
if (response.status === 204) {
return null;
}
return response.json();
}
function releaseAssetUploadUrl(release: Release, artifact: string) {
if (!release.upload_url) {
return `/repos/${owner}/${repo}/releases/${release.id}/assets?name=${encodeURIComponent(artifact)}`;
}
const baseUrl = release.upload_url.replace(/\{.*\}$/, "");
const separator = baseUrl.includes("?") ? "&" : "?";
return `${baseUrl}${separator}name=${encodeURIComponent(artifact)}`;
}
async function ignoreMissing(path: string, init: RequestInit = {}) {
try {
await request(path, init);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!message.includes("404")) {
throw error;
}
}
}
const currentRelease = (await request(`/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`)) as Release | null;
if (currentRelease) {
await ignoreMissing(`/repos/${owner}/${repo}/releases/${currentRelease.id}`, { method: "DELETE" });
}
await ignoreMissing(`/repos/${owner}/${repo}/tags/${encodeURIComponent(tag)}`, { method: "DELETE" });
const release = (await request(`/repos/${owner}/${repo}/releases`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
tag_name: tag,
target_commitish: process.env.GITEA_SHA,
name: releaseName,
body: [
`Gitea GitHub theme build for ${tag}.`,
"",
"Install by extracting the archive and copying `custom/` into your Gitea custom path.",
].join("\n"),
draft: false,
prerelease: false,
}),
})) as Release;
const artifacts = (await readdir(releaseDir)).filter((file) => file.endsWith(".zip") || file.endsWith(".tar.gz") || file === "checksums.txt");
for (const artifact of artifacts) {
const path = join(releaseDir, artifact);
const file = Bun.file(path);
await request(releaseAssetUploadUrl(release, basename(path)), {
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
},
body: file,
});
}
console.log(`published release ${tag} with ${artifacts.length} assets`);