const BASE = "https://api.github.com";
const OWNER = process.env.GITHUB_OWNER;
const REPO = process.env.GITHUB_REPO;
const TOKEN = process.env.GITHUB_TOKEN;
function headers() {
return {
Authorization: `Bearer ${TOKEN}`,
Accept: "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28",
};
}
export async function getContents(path = "") {
const url = `${BASE}/repos/${OWNER}/${REPO}/contents/${encodeURIPath(path)}`;
const res = await fetch(url, {
headers: headers(),
next: { revalidate: 30 },
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message || `GitHub ${res.status}`);
}
return res.json();
}
export async function getFile(path) {
const data = await getContents(path);
if (Array.isArray(data)) throw new Error("Path is a directory, not a file");
return {
sha: data.sha,
size: data.size,
name: data.name,
download_url: data.download_url,
encoding: data.encoding,
content: data.content
? Buffer.from(data.content, "base64").toString("utf-8")
: null,
};
}
export async function putFile(path, textContent, sha, message) {
const url = `${BASE}/repos/${OWNER}/${REPO}/contents/${encodeURIPath(path)}`;
const body = {
message: message || `Update ${path}`,
content: Buffer.from(textContent, "utf-8").toString("base64"),
...(sha ? { sha } : {}),
};
const res = await fetch(url, {
method: "PUT",
headers: { ...headers(), "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message || `GitHub ${res.status}`);
}
return res.json();
}
export async function putBinaryFile(path, base64Content, sha, message) {
const url = `${BASE}/repos/${OWNER}/${REPO}/contents/${encodeURIPath(path)}`;
const body = {
message: message || `Upload ${path}`,
content: base64Content,
...(sha ? { sha } : {}),
};
const res = await fetch(url, {
method: "PUT",
headers: { ...headers(), "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message || `GitHub ${res.status}`);
}
return res.json();
}
export async function deleteFile(path, sha, message) {
const url = `${BASE}/repos/${OWNER}/${REPO}/contents/${encodeURIPath(path)}`;
const body = {
message: message || `Delete ${path}`,
sha,
};
const res = await fetch(url, {
method: "DELETE",
headers: { ...headers(), "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.message || `GitHub ${res.status}`);
}
return res.json();
}
function encodeURIPath(path) {
return path.split("/").map(encodeURIComponent).join("/");
}