From e2c9e5d30d59d0a6ca81fdad470b147472d2157c Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Wed, 20 May 2026 12:16:21 +0200 Subject: [PATCH] fix: authenticate latest release lookup --- .github/workflows/ci.yml | 1 + README.md | 9 ++++++--- action.yml | 4 ++++ src/main.test.ts | 24 ++++++++++++++++++++++++ src/main.ts | 13 ++++++++++++- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94dcf0f..6f37cb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,7 @@ jobs: - uses: ./ with: version: ${{ matrix.version }} + github-token: ${{ github.token }} - run: supabase -h ci: diff --git a/README.md b/README.md index 37fb88c..f71b28f 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ steps: - uses: supabase/setup-cli@v2 with: version: latest + github-token: ${{ github.token }} - run: supabase init - run: supabase db start ``` @@ -58,9 +59,10 @@ on Windows and macOS runners. The action supports the following inputs: -| Name | Type | Description | Default | Required | -| --------- | ------ | ---------------------------------- | --------------------------------- | -------- | -| `version` | String | Supabase CLI version (or `latest`) | Root lockfile version or `latest` | false | +| Name | Type | Description | Default | Required | +| -------------- | ------ | -------------------------------------------------------------------------- | --------------------------------- | -------- | +| `version` | String | Supabase CLI version (or `latest`) | Root lockfile version or `latest` | false | +| `github-token` | String | GitHub token used to resolve `latest` without unauthenticated API limiting | | false | ## Advanced Usage @@ -162,6 +164,7 @@ steps: - uses: ./ with: version: latest + github-token: ${{ github.token }} ``` The CI workflow provides fast smoke coverage across GitHub-hosted runners, and diff --git a/action.yml b/action.yml index cbf4c66..d0ed65d 100644 --- a/action.yml +++ b/action.yml @@ -5,6 +5,9 @@ inputs: version: description: Version of Supabase CLI to install. If omitted, detect from the root lockfile and otherwise use latest. required: false + github-token: + description: GitHub token used to resolve the latest Supabase CLI release without hitting unauthenticated API limits. + required: false outputs: version: description: Version of installed Supabase CLI @@ -28,4 +31,5 @@ runs: working-directory: ${{ github.action_path }} env: INPUT_VERSION: ${{ inputs.version }} + SUPABASE_CLI_GITHUB_TOKEN: ${{ inputs.github-token }} run: bun src/main.ts diff --git a/src/main.test.ts b/src/main.test.ts index 46627c6..6637f5c 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -10,13 +10,21 @@ import * as tc from "@actions/tool-cache"; const repo = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const defaultEntrypoint = fileURLToPath(new URL("./main.ts", import.meta.url)); const CLI_CONFIG_REGISTRY = "SUPABASE_INTERNAL_IMAGE_REGISTRY"; +const GITHUB_RELEASES_API = "https://api.github.com/repos/supabase/cli/releases/latest"; +const GITHUB_TOKEN_ENV = "SUPABASE_CLI_GITHUB_TOKEN"; const originalWorkspace = process.env.GITHUB_WORKSPACE; +const originalGithubToken = process.env[GITHUB_TOKEN_ENV]; const tempDirs = new Set(); let mainModule: typeof import("./main.ts") | null = null; afterEach(() => { mock.restore(); process.env.GITHUB_WORKSPACE = originalWorkspace; + if (originalGithubToken === undefined) { + delete process.env[GITHUB_TOKEN_ENV]; + } else { + process.env[GITHUB_TOKEN_ENV] = originalGithubToken; + } for (const dir of tempDirs) { rmSync(dir, { force: true, recursive: true }); @@ -222,6 +230,22 @@ test("resolves latest before choosing a versioned Supabase CLI archive", async ( }); }); +test("authenticates latest release lookup when a GitHub token is provided", async () => { + process.env[GITHUB_TOKEN_ENV] = "ghs_test-token"; + const fetch = mockLatestRelease("v2.99.0"); + const { getDownloadArchive } = await getMainModule(); + + await getDownloadArchive("latest", "darwin", "arm64"); + + expect(fetch).toHaveBeenCalledWith(GITHUB_RELEASES_API, { + headers: expect.objectContaining({ + Accept: "application/vnd.github+json", + Authorization: "Bearer ghs_test-token", + "X-GitHub-Api-Version": "2022-11-28", + }), + }); +}); + test("awaits the action entrypoint with omitted version and latest fallback", async () => { process.env.GITHUB_WORKSPACE = repo; mockLatestRelease(); diff --git a/src/main.ts b/src/main.ts index 68ca9ba..08f2c56 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ const REGISTRY_VERSION = "1.28.0"; const VERSIONED_ARCHIVE_VERSION = "2.99.0"; const DEFAULT_VERSION = "latest"; const GITHUB_RELEASES_API = "https://api.github.com/repos/supabase/cli/releases/latest"; +const GITHUB_TOKEN_ENV = "SUPABASE_CLI_GITHUB_TOKEN"; type ArchiveFormat = "tar" | "zip"; @@ -175,7 +176,17 @@ function resolveVersion(inputVersion: string): string { } async function resolveLatestVersion(): Promise { - const response = await fetch(GITHUB_RELEASES_API); + const headers: Record = { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }; + const githubToken = process.env[GITHUB_TOKEN_ENV]?.trim(); + + if (githubToken) { + headers.Authorization = `Bearer ${githubToken}`; + } + + const response = await fetch(GITHUB_RELEASES_API, { headers }); if (!response.ok) { throw new Error(`Failed to resolve latest Supabase CLI release: ${response.statusText}`); }