Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/sxzz/actionspack. Lockfile-first GitHub Actions workflow packer
actions github workflow
Something went wrong. Try again.
3.4 kB · 130 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131import { Buffer } from 'node:buffer'import { mkdir, readFile, writeFile } from 'node:fs/promises'import { tmpdir } from 'node:os'import path from 'node:path'import process from 'node:process'
export interface GitHubClient { resolveRef: (owner: string, repo: string, ref: string) => Promise<string> readFile: ( owner: string, repo: string, ref: string, path: string, ) => Promise<string | undefined>}
interface GitHubRefResponse { object?: { sha?: string }}
interface GitHubContentResponse { content?: string encoding?: string}
export class HttpGitHubClient implements GitHubClient { readonly #cacheDir: string readonly #token: string | undefined
constructor( token: string | undefined = process.env.GITHUB_TOKEN, cacheDir: string = path.join(tmpdir(), 'actionspack-github-cache'), ) { this.#cacheDir = cacheDir this.#token = token }
async resolveRef(owner: string, repo: string, ref: string): Promise<string> { if (/^[a-f0-9]{40}$/iu.test(ref)) { return ref }
const candidates = [`heads/${ref}`, `tags/${ref}`, ref] for (const candidate of candidates) { const response = await this.#request<GitHubRefResponse>( `/repos/${owner}/${repo}/git/ref/${candidate}`, true, ) const sha = response?.object?.sha if (sha) { return sha } }
throw new Error(`Unable to resolve ${owner}/${repo}@${ref}`) }
async readFile( owner: string, repo: string, ref: string, filePath: string, ): Promise<string | undefined> { const cacheFile = this.#cacheFile(owner, repo, ref, filePath) const cached = await readFile(cacheFile, 'utf8').catch(() => undefined) if (cached !== undefined) { return cached }
const encodedPath = filePath.split('/').map(encodeURIComponent).join('/') const response = await this.#request<GitHubContentResponse>( `/repos/${owner}/${repo}/contents/${encodedPath}?ref=${ref}`, true, )
if (!response) { return } if ( response.encoding !== 'base64' || typeof response.content !== 'string' ) { throw new Error( `Unexpected GitHub content response for ${owner}/${repo}/${filePath}@${ref}`, ) } const content = Buffer.from(response.content, 'base64').toString('utf8') await mkdir(path.dirname(cacheFile), { recursive: true }) await writeFile(cacheFile, content, 'utf8') return content }
#cacheFile( owner: string, repo: string, ref: string, filePath: string, ): string { return path.join(this.#cacheDir, owner, repo, ref, ...filePath.split('/')) }
async #request<T>( pathname: string, allowNotFound = false, ): Promise<T | undefined> { const headers: Record<string, string> = { accept: 'application/vnd.github+json', 'user-agent': 'actionspack', 'x-github-api-version': '2022-11-28', } if (this.#token) { headers.authorization = `Bearer ${this.#token}` }
const response = await fetch(`https://api.github.com${pathname}`, { headers, }) if (response.status === 404 && allowNotFound) { return } if (!response.ok) { throw new Error( `GitHub API request failed: ${response.status} ${response.statusText}`, ) } return (await response.json()) as T }}