import { Vault, requestUrl, normalizePath } from "obsidian"; import MetadataStore, { Metadata } from "../metadata-store"; export type RepoContent = { files: { [key: string]: GetTreeResponseItem }; sha: string; }; /** * Represents a single item in a tree response from the GitHub API. */ export type GetTreeResponseItem = { path: string; mode: string; type: string; sha: string; size: number; url: string; }; export type NewTreeRequestItem = { path: string; mode: string; type: string; sha?: string | null; content?: string; }; /** * Represents a git blob response from the GitHub API. */ export type BlobFile = { sha: string; node_id: string; size: number; url: string; content: string; encoding: string; }; export default class GithubClient { constructor( private token: string, private owner: string, private repo: string, private branch: string, private repoContentDir: string, private configDir: string, ) {} headers() { return { Accept: "application/vnd.github+json", Authorization: `Bearer ${this.token}`, "X-GitHub-Api-Version": "2022-11-28", }; } /** * Gets the content of the repo. * * @returns Array of files in the directory in the remote repo */ async getRepoContent(): Promise { const res = await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/git/trees/${this.branch}?recursive=1`, headers: this.headers(), }); const files = res.json.tree .filter((file: GetTreeResponseItem) => file.type === "blob") .reduce( ( acc: { [key: string]: GetTreeResponseItem }, file: GetTreeResponseItem, ) => ({ ...acc, [file.path]: file }), {}, ); return { files, sha: res.json.sha }; } async createTree(tree: { tree: NewTreeRequestItem[]; base_tree: string }) { const res = await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/git/trees`, headers: this.headers(), method: "POST", body: JSON.stringify(tree), }); return res.json.sha; } async createCommit(message: string, treeSha: string, parent: string) { const res = await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/git/commits`, headers: this.headers(), method: "POST", body: JSON.stringify({ message: message, tree: treeSha, parents: [parent], }), }); return res.json.sha; } async getBranchHeadSha() { const res = await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/git/refs/heads/${this.branch}`, headers: this.headers(), }); return res.json.object.sha; } async updateBranchHead(sha: string) { await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/git/refs/heads/${this.branch}`, headers: this.headers(), method: "PATCH", body: JSON.stringify({ sha: sha, }), }); } /** * Gets a blob from a blob url * @param url blob url */ async getBlob(url: string): Promise { const res = await requestUrl({ url: url, headers: this.headers(), }); return res.json; } /** * Create or edit a remote file to GitHub. * * @param remoteFilePath Path to remote file * @param fileContent Content of the file * @param sha SHA of the file */ async uploadFile( remoteFilePath: string, fileContent: ArrayBuffer, sha: string | null, ) { await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}`, method: "PUT", headers: this.headers(), body: JSON.stringify({ message: `Edit ${remoteFilePath}`, branch: this.branch, content: Buffer.from(fileContent).toString("base64"), sha: sha, }), }); } /** * Gets the SHA of a file in the remote repo. * * @param remoteFilePath Path to remote file * @returns sha of the file as string */ async getFileSha(remoteFilePath: string) { const res = await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}?ref=${this.branch}`, headers: this.headers(), }); return res.json.sha; } /** * Delete a single file from GitHub. * * @param remoteFilePath Path to remote file * @param sha SHA of the file */ async deleteFile(remoteFilePath: string, sha: string) { await requestUrl({ url: `https://api.github.com/repos/${this.owner}/${this.repo}/contents/${remoteFilePath}`, method: "DELETE", headers: this.headers(), body: JSON.stringify({ message: `Delete ${remoteFilePath}`, branch: this.branch, sha: sha, }), }); } }