Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,55 @@ 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: bash
shell: sh
working-directory: ${{ github.action_path }}
run: bun install --frozen-lockfile --production

- id: setup-cli
name: Setup Supabase CLI
shell: bash
shell: sh
working-directory: ${{ github.action_path }}
env:
INPUT_VERSION: ${{ inputs.version }}
Expand Down
30 changes: 30 additions & 0 deletions src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,36 @@ 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",
Comment thread
jgoux marked this conversation as resolved.
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();

Expand Down
56 changes: 51 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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";
type ArchiveFormat = "apk" | "tar" | "zip";

type DownloadArchive = {
url: string;
Expand Down Expand Up @@ -199,7 +199,19 @@ async function resolveLatestVersion(): Promise<string> {
return normalizeVersion(release.tag_name);
}

function getArchiveFormat(version: string, platform: NodeJS.Platform): ArchiveFormat {
function getArchiveFormat(
version: string,
platform: NodeJS.Platform,
isMuslLinux: boolean,
): ArchiveFormat {
if (
platform === "linux" &&
isMuslLinux &&
semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0
) {
return "apk";
}

if (platform === "win32" && semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0) {
return "zip";
}
Expand All @@ -211,6 +223,7 @@ function getArchiveFilename(
version: string,
platform: NodeJS.Platform,
arch: NodeJS.Architecture,
archiveFormat: ArchiveFormat,
): string {
const archivePlatform = getArchivePlatform(platform);
const archiveArch = getArchiveArch(arch);
Expand All @@ -219,6 +232,10 @@ 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}`;
Expand All @@ -231,17 +248,45 @@ 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 filename = getArchiveFilename(resolvedVersion, platform, arch);
const format = getArchiveFormat(
resolvedVersion,
platform,
isMuslLinux ?? (await detectMuslLinux(platform)),
);
const filename = getArchiveFilename(resolvedVersion, platform, arch, format);

return {
url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`,
format: getArchiveFormat(resolvedVersion, platform),
format,
};
}

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");
Expand Down Expand Up @@ -274,10 +319,11 @@ 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 cliPath =
const extractedPath =
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);
Expand Down
Loading