diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ab606e..3fdb33b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ && sudo apt update \ && sudo apt install gh -y - + - run: | gh cache delete --all || true env: @@ -51,6 +51,7 @@ jobs: - "1" - "> 1.0.0" - "< 2" + - "08a9b6a98b14d3c23b58080bea31513546246c50" # Disable support for now. This is because Github Artifacts # expire after 90 days, and we don't have another source of truth yet. # - "822a00c4d508b54f650933a73ca5f4a3af9a7983" # 1.0.0 commit diff --git a/src/action.ts b/src/action.ts index 0d0e3dd..71bf816 100644 --- a/src/action.ts +++ b/src/action.ts @@ -13,8 +13,8 @@ import { downloadTool, extractZip } from "@actions/tool-cache"; import { getExecOutput } from "@actions/exec"; import { writeBunfig } from "./bunfig"; import { saveState } from "@actions/core"; -import { addExtension, getArchitecture, getPlatform, request } from "./utils"; -import { compareVersions, satisfies, validate } from "compare-versions"; +import { addExtension } from "./utils"; +import { DownloadMeta, getDownloadMeta } from "./download-url"; export type Input = { customUrl?: string; @@ -48,7 +48,9 @@ export default async (options: Input): Promise => { const bunfigPath = join(process.cwd(), "bunfig.toml"); writeBunfig(bunfigPath, options); - const url = await getDownloadUrl(options); + const downloadMeta = await getDownloadMeta(options); + const url = downloadMeta.url; + const cacheEnabled = isCacheEnabled(options); const binPath = join(homedir(), ".bun", "bin"); @@ -91,7 +93,7 @@ export default async (options: Input): Promise => { if (!cacheHit) { info(`Downloading a new version of Bun: ${url}`); - revision = await downloadBun(url, bunPath); + revision = await downloadBun(downloadMeta, bunPath); } if (!revision) { @@ -121,11 +123,18 @@ export default async (options: Input): Promise => { }; async function downloadBun( - url: string, + downloadMeta: DownloadMeta, bunPath: string ): Promise { // Workaround for https://github.com/oven-sh/setup-bun/issues/79 and https://github.com/actions/toolkit/issues/1179 - const zipPath = addExtension(await downloadTool(url), ".zip"); + const zipPath = addExtension( + await downloadTool( + downloadMeta.url, + undefined, + downloadMeta.auth ?? undefined + ), + ".zip" + ); const extractedZipPath = await extractZip(zipPath); const extractedBunPath = await extractBun(extractedZipPath); try { @@ -153,52 +162,6 @@ function isCacheEnabled(options: Input): boolean { return isFeatureAvailable(); } -async function getDownloadUrl(options: Input): Promise { - const { customUrl } = options; - if (customUrl) { - return customUrl; - } - - const res = (await ( - await request("https://api.github.com/repos/oven-sh/bun/git/refs/tags", { - headers: { - "Authorization": `Bearer ${options.token}`, - }, - }) - ).json()) as { ref: string }[]; - let tags = res - .filter( - (tag) => - tag.ref.startsWith("refs/tags/bun-v") || tag.ref === "refs/tags/canary" - ) - .map((item) => item.ref.replace(/refs\/tags\/(bun-v)?/g, "")); - - const { version, os, arch, avx2, profile } = options; - - let tag = tags.find((t) => t === version); - if (!tag) { - tags = tags.filter((t) => validate(t)).sort(compareVersions); - - if (version === "latest") tag = `bun-v${tags.at(-1)}`; - else tag = `bun-v${tags.filter((t) => satisfies(t, version)).at(-1)}`; - } else if (validate(tag)) { - tag = `bun-v${tag}`; - } - - const eversion = encodeURIComponent(tag ?? version); - const eos = encodeURIComponent(os ?? getPlatform()); - const earch = encodeURIComponent(arch ?? getArchitecture()); - const eavx2 = encodeURIComponent(avx2 ? "-baseline" : ""); - const eprofile = encodeURIComponent(profile ? "-profile" : ""); - - const { href } = new URL( - `${eversion}/bun-${eos}-${earch}${eavx2}${eprofile}.zip`, - "https://github.com/oven-sh/bun/releases/download/" - ); - - return href; -} - async function extractBun(path: string): Promise { for (const entry of readdirSync(path, { withFileTypes: true })) { const { name } = entry; diff --git a/src/download-url.ts b/src/download-url.ts new file mode 100644 index 0000000..be95dcf --- /dev/null +++ b/src/download-url.ts @@ -0,0 +1,113 @@ +import { compareVersions, satisfies, validate } from "compare-versions"; +import { Input } from "./action"; +import { getArchitecture, getPlatform, request } from "./utils"; + +export interface DownloadMeta { + url: string; + auth?: string; +} + +export async function getDownloadMeta(options: Input): Promise { + const { customUrl } = options; + if (customUrl) { + return { + url: customUrl, + }; + } + + if (options.version && /^[0-9a-f]{40}$/i.test(options.version)) { + return await getShaDownloadMeta(options); + } + + return await getSemverDownloadMeta(options); +} + +interface Run { + id: string; + head_sha: string; +} + +interface Runs { + workflow_runs: Run[]; +} + +async function getShaDownloadMeta(options: Input): Promise { + let res: Runs; + let page = 1; + let run: Run; + while ( + (res = (await ( + await request( + `https://api.github.com/repos/oven-sh/bun/actions/workflows/ci.yml/runs?per_page=100&page=${page}`, + {} + ) + ).json()) as Runs) + ) { + run = res.workflow_runs.find((item) => item.head_sha === options.version); + if (run) break; + + page++; + } + + const artifacts = (await ( + await request( + `https://api.github.com/repos/oven-sh/bun/actions/runs/${run.id}/artifacts`, + {} + ) + ).json()) as { artifacts: { name: string; archive_download_url: string }[] }; + + const { os, arch, avx2, profile, token } = options; + + const name = `bun-${os ?? getPlatform()}-${arch ?? getArchitecture()}${ + avx2 ? "-baseline" : "" + }${profile ? "-profile" : ""}`; + + return { + url: artifacts.artifacts.find((item) => item.name === name) + .archive_download_url, + auth: token, + }; +} + +async function getSemverDownloadMeta(options: Input): Promise { + const res = (await ( + await request("https://api.github.com/repos/oven-sh/bun/git/refs/tags", { + headers: { + "Authorization": `Bearer ${options.token}`, + }, + }) + ).json()) as { ref: string }[]; + let tags = res + .filter( + (tag) => + tag.ref.startsWith("refs/tags/bun-v") || tag.ref === "refs/tags/canary" + ) + .map((item) => item.ref.replace(/refs\/tags\/(bun-v)?/g, "")); + + const { version, os, arch, avx2, profile } = options; + + let tag = tags.find((t) => t === version); + if (!tag) { + tags = tags.filter((t) => validate(t)).sort(compareVersions); + + if (version === "latest") tag = `bun-v${tags.at(-1)}`; + else tag = `bun-v${tags.filter((t) => satisfies(t, version)).at(-1)}`; + } else if (validate(tag)) { + tag = `bun-v${tag}`; + } + + const eversion = encodeURIComponent(tag ?? version); + const eos = encodeURIComponent(os ?? getPlatform()); + const earch = encodeURIComponent(arch ?? getArchitecture()); + const eavx2 = encodeURIComponent(avx2 ? "-baseline" : ""); + const eprofile = encodeURIComponent(profile ? "-profile" : ""); + + const { href } = new URL( + `${eversion}/bun-${eos}-${earch}${eavx2}${eprofile}.zip`, + "https://github.com/oven-sh/bun/releases/download/" + ); + + return { + url: href, + }; +}