133 lines
3.6 KiB
TypeScript
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`);
|