mirror of
https://github.com/supabase/setup-cli.git
synced 2026-06-28 01:46:58 +00:00
Compare commits
1 Commits
v2.1.0
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
688c6655dc |
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -52,7 +52,6 @@ jobs:
|
||||
- uses: ./
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
github-token: ${{ github.token }}
|
||||
- run: supabase -h
|
||||
|
||||
ci:
|
||||
|
||||
67
.github/workflows/e2e.yml
vendored
67
.github/workflows/e2e.yml
vendored
@@ -16,9 +16,22 @@ on:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: "30 1,9 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
cli_version:
|
||||
description: Specific Supabase CLI version to test. When set, the matrix runs only this version across all supported Postgres majors. Leave empty to run the full version matrix.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
# Triggered from supabase/cli after a successful beta release so the
|
||||
# symmetric e2e runs before a stale archive layout (or any other
|
||||
# packaging change) reaches the stable channel. The dispatcher sends
|
||||
# `client_payload.version` (e.g. "2.99.0") and `client_payload.channel`.
|
||||
repository_dispatch:
|
||||
types:
|
||||
- cli-released
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.cli_version || github.event.client_payload.version }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -29,24 +42,50 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
e2e: # make sure the action works on a clean machine without building
|
||||
plan:
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
matrix: ${{ steps.compute.outputs.matrix }}
|
||||
cli_version: ${{ steps.compute.outputs.cli_version }}
|
||||
source: ${{ steps.compute.outputs.source }}
|
||||
steps:
|
||||
- id: compute
|
||||
env:
|
||||
DISPATCH_VERSION: ${{ github.event.inputs.cli_version }}
|
||||
PAYLOAD_VERSION: ${{ github.event.client_payload.version }}
|
||||
PAYLOAD_CHANNEL: ${{ github.event.client_payload.channel }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version=""
|
||||
source="default"
|
||||
if [[ "$EVENT_NAME" == "repository_dispatch" && -n "$PAYLOAD_VERSION" ]]; then
|
||||
version="${PAYLOAD_VERSION#v}"
|
||||
source="cli-${PAYLOAD_CHANNEL:-release}"
|
||||
elif [[ -n "$DISPATCH_VERSION" ]]; then
|
||||
version="${DISPATCH_VERSION#v}"
|
||||
source="workflow_dispatch"
|
||||
fi
|
||||
if [[ -n "$version" ]]; then
|
||||
matrix='{"version":["'"$version"'"],"pg_major":[14,15,17]}'
|
||||
else
|
||||
matrix='{"version":["1.178.2","2.33.0","latest"],"pg_major":[14,15,17],"exclude":[{"version":"1.178.2","pg_major":17}]}'
|
||||
fi
|
||||
{
|
||||
echo "cli_version=$version"
|
||||
echo "source=$source"
|
||||
echo "matrix=$matrix"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
e2e: # make sure the action works on a clean machine without building
|
||||
needs: plan
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- 1.178.2
|
||||
- 2.33.0
|
||||
- latest
|
||||
pg_major:
|
||||
- 14
|
||||
- 15
|
||||
- 17
|
||||
exclude:
|
||||
- version: 1.178.2
|
||||
pg_major: 17
|
||||
matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
|
||||
@@ -47,7 +47,6 @@ steps:
|
||||
- uses: supabase/setup-cli@v2
|
||||
with:
|
||||
version: latest
|
||||
github-token: ${{ github.token }}
|
||||
- run: supabase init
|
||||
- run: supabase db start
|
||||
```
|
||||
@@ -60,9 +59,8 @@ 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 |
|
||||
| `github-token` | String | GitHub token used to resolve `latest` without unauthenticated API limiting | | false |
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
@@ -164,7 +162,6 @@ steps:
|
||||
- uses: ./
|
||||
with:
|
||||
version: latest
|
||||
github-token: ${{ github.token }}
|
||||
```
|
||||
|
||||
The CI workflow provides fast smoke coverage across GitHub-hosted runners, and
|
||||
|
||||
44
action.yml
44
action.yml
@@ -5,9 +5,6 @@ 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
|
||||
@@ -15,57 +12,20 @@ outputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: bun-download
|
||||
name: Resolve Bun Download URL
|
||||
shell: sh
|
||||
working-directory: ${{ github.action_path }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
if [ "${RUNNER_OS}" != "Linux" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# setup-bun does not detect Linux musl yet, so Alpine-like containers need the musl asset explicitly.
|
||||
is_musl=false
|
||||
if [ -f /etc/alpine-release ]; then
|
||||
is_musl=true
|
||||
elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
|
||||
is_musl=true
|
||||
fi
|
||||
|
||||
if [ "${is_musl}" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
version="$(cat .bun-version)"
|
||||
case "$(uname -m)" in
|
||||
x86_64) arch="x64" ;;
|
||||
aarch64|arm64) arch="aarch64" ;;
|
||||
*)
|
||||
echo "Unsupported Linux musl architecture: $(uname -m)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${version}/bun-linux-${arch}-musl.zip" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version-file: ${{ github.action_path }}/.bun-version
|
||||
bun-download-url: ${{ steps.bun-download.outputs.url }}
|
||||
|
||||
- name: Install Action Dependencies
|
||||
shell: sh
|
||||
shell: bash
|
||||
working-directory: ${{ github.action_path }}
|
||||
run: bun install --frozen-lockfile --production
|
||||
|
||||
- id: setup-cli
|
||||
name: Setup Supabase CLI
|
||||
shell: sh
|
||||
shell: bash
|
||||
working-directory: ${{ github.action_path }}
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
SUPABASE_CLI_GITHUB_TOKEN: ${{ inputs.github-token }}
|
||||
run: bun src/main.ts
|
||||
|
||||
@@ -10,21 +10,13 @@ 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<string>();
|
||||
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 });
|
||||
@@ -196,36 +188,6 @@ test("uses versioned tar archives for Supabase CLI v2.99.0 and later", async ()
|
||||
});
|
||||
});
|
||||
|
||||
test("uses apk archives for Supabase CLI v2.99.0 and later on Linux musl", async () => {
|
||||
const { getDownloadArchive } = await getMainModule();
|
||||
|
||||
const archive = await getDownloadArchive("2.100.1", "linux", "x64", true);
|
||||
|
||||
expect(archive).toEqual({
|
||||
url: "https://github.com/supabase/cli/releases/download/v2.100.1/supabase_2.100.1_linux_amd64.apk",
|
||||
format: "apk",
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps tar archives before Supabase CLI v2.99.0 on Linux musl", async () => {
|
||||
const { getDownloadArchive } = await getMainModule();
|
||||
|
||||
const archive = await getDownloadArchive("2.98.2", "linux", "x64", true);
|
||||
|
||||
expect(archive).toEqual({
|
||||
url: "https://github.com/supabase/cli/releases/download/v2.98.2/supabase_linux_amd64.tar.gz",
|
||||
format: "tar",
|
||||
});
|
||||
});
|
||||
|
||||
test("uses usr/bin as the CLI path for apk archives", async () => {
|
||||
const { getCliPath } = await getMainModule();
|
||||
|
||||
expect(getCliPath("/tmp/extracted", "apk")).toBe(path.join("/tmp/extracted", "usr", "bin"));
|
||||
expect(getCliPath("/tmp/extracted", "tar")).toBe("/tmp/extracted");
|
||||
expect(getCliPath("/tmp/extracted", "zip")).toBe("/tmp/extracted");
|
||||
});
|
||||
|
||||
test("keeps the unversioned tar archive layout before Supabase CLI v2.99.0", async () => {
|
||||
const { getDownloadArchive } = await getMainModule();
|
||||
|
||||
@@ -260,22 +222,6 @@ 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();
|
||||
|
||||
69
src/main.ts
69
src/main.ts
@@ -10,9 +10,8 @@ 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 = "apk" | "tar" | "zip";
|
||||
type ArchiveFormat = "tar" | "zip";
|
||||
|
||||
type DownloadArchive = {
|
||||
url: string;
|
||||
@@ -176,17 +175,7 @@ function resolveVersion(inputVersion: string): string {
|
||||
}
|
||||
|
||||
async function resolveLatestVersion(): Promise<string> {
|
||||
const headers: Record<string, string> = {
|
||||
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 });
|
||||
const response = await fetch(GITHUB_RELEASES_API);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to resolve latest Supabase CLI release: ${response.statusText}`);
|
||||
}
|
||||
@@ -199,19 +188,7 @@ async function resolveLatestVersion(): Promise<string> {
|
||||
return normalizeVersion(release.tag_name);
|
||||
}
|
||||
|
||||
function getArchiveFormat(
|
||||
version: string,
|
||||
platform: NodeJS.Platform,
|
||||
isMuslLinux: boolean,
|
||||
): ArchiveFormat {
|
||||
if (
|
||||
platform === "linux" &&
|
||||
isMuslLinux &&
|
||||
semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0
|
||||
) {
|
||||
return "apk";
|
||||
}
|
||||
|
||||
function getArchiveFormat(version: string, platform: NodeJS.Platform): ArchiveFormat {
|
||||
if (platform === "win32" && semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0) {
|
||||
return "zip";
|
||||
}
|
||||
@@ -223,7 +200,6 @@ function getArchiveFilename(
|
||||
version: string,
|
||||
platform: NodeJS.Platform,
|
||||
arch: NodeJS.Architecture,
|
||||
archiveFormat: ArchiveFormat,
|
||||
): string {
|
||||
const archivePlatform = getArchivePlatform(platform);
|
||||
const archiveArch = getArchiveArch(arch);
|
||||
@@ -232,10 +208,6 @@ function getArchiveFilename(
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`;
|
||||
}
|
||||
|
||||
if (platform === "linux" && archiveFormat === "apk") {
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.apk`;
|
||||
}
|
||||
|
||||
if (semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0) {
|
||||
const extension = platform === "win32" ? "zip" : "tar.gz";
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`;
|
||||
@@ -248,45 +220,17 @@ export async function getDownloadArchive(
|
||||
version: string,
|
||||
platform = process.platform,
|
||||
arch = process.arch,
|
||||
isMuslLinux?: boolean,
|
||||
): Promise<DownloadArchive> {
|
||||
const resolvedVersion =
|
||||
version.toLowerCase() === "latest" ? await resolveLatestVersion() : normalizeVersion(version);
|
||||
const format = getArchiveFormat(
|
||||
resolvedVersion,
|
||||
platform,
|
||||
isMuslLinux ?? (await detectMuslLinux(platform)),
|
||||
);
|
||||
const filename = getArchiveFilename(resolvedVersion, platform, arch, format);
|
||||
const filename = getArchiveFilename(resolvedVersion, platform, arch);
|
||||
|
||||
return {
|
||||
url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`,
|
||||
format,
|
||||
format: getArchiveFormat(resolvedVersion, platform),
|
||||
};
|
||||
}
|
||||
|
||||
async function detectMuslLinux(platform = process.platform): Promise<boolean> {
|
||||
if (platform !== "linux") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existsSync("/etc/alpine-release")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await $`ldd --version`.quiet().text();
|
||||
return output.toLowerCase().includes("musl");
|
||||
} catch (error) {
|
||||
const output = error instanceof Error ? error.message : String(error);
|
||||
return output.toLowerCase().includes("musl");
|
||||
}
|
||||
}
|
||||
|
||||
export function getCliPath(extractedPath: string, archiveFormat: ArchiveFormat): string {
|
||||
return archiveFormat === "apk" ? path.join(extractedPath, "usr", "bin") : extractedPath;
|
||||
}
|
||||
|
||||
function getCliExecutablePath(cliPath: string): string {
|
||||
if (process.platform !== "win32") {
|
||||
return path.join(cliPath, "supabase");
|
||||
@@ -319,11 +263,10 @@ export async function run(): Promise<void> {
|
||||
const version = resolveVersion(core.getInput("version"));
|
||||
const archive = await getDownloadArchive(version);
|
||||
const archivePath = await tc.downloadTool(archive.url);
|
||||
const extractedPath =
|
||||
const cliPath =
|
||||
archive.format === "zip"
|
||||
? await tc.extractZip(archivePath)
|
||||
: await tc.extractTar(archivePath);
|
||||
const cliPath = getCliPath(extractedPath, archive.format);
|
||||
const installedVersion = await determineInstalledVersion(cliPath);
|
||||
core.setOutput("version", installedVersion);
|
||||
core.addPath(cliPath);
|
||||
|
||||
Reference in New Issue
Block a user