Skip to content

Commit 32b9432

Browse files
committed
fix alpine cli setup
1 parent a4d563a commit 32b9432

3 files changed

Lines changed: 119 additions & 7 deletions

File tree

action.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,55 @@ outputs:
1212
runs:
1313
using: composite
1414
steps:
15+
- id: bun-download
16+
name: Resolve Bun Download URL
17+
shell: sh
18+
working-directory: ${{ github.action_path }}
19+
run: |
20+
set -eu
21+
22+
if [ "${RUNNER_OS}" != "Linux" ]; then
23+
exit 0
24+
fi
25+
26+
# setup-bun does not detect Linux musl yet, so Alpine-like containers need the musl asset explicitly.
27+
is_musl=false
28+
if [ -f /etc/alpine-release ]; then
29+
is_musl=true
30+
elif command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then
31+
is_musl=true
32+
fi
33+
34+
if [ "${is_musl}" != "true" ]; then
35+
exit 0
36+
fi
37+
38+
version="$(cat .bun-version)"
39+
case "$(uname -m)" in
40+
x86_64) arch="x64" ;;
41+
aarch64|arm64) arch="aarch64" ;;
42+
*)
43+
echo "Unsupported Linux musl architecture: $(uname -m)" >&2
44+
exit 1
45+
;;
46+
esac
47+
48+
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${version}/bun-linux-${arch}-musl.zip" >> "$GITHUB_OUTPUT"
49+
1550
- name: Setup Bun
1651
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
1752
with:
1853
bun-version-file: ${{ github.action_path }}/.bun-version
54+
bun-download-url: ${{ steps.bun-download.outputs.url }}
1955

2056
- name: Install Action Dependencies
21-
shell: bash
57+
shell: sh
2258
working-directory: ${{ github.action_path }}
2359
run: bun install --frozen-lockfile --production
2460

2561
- id: setup-cli
2662
name: Setup Supabase CLI
27-
shell: bash
63+
shell: sh
2864
working-directory: ${{ github.action_path }}
2965
env:
3066
INPUT_VERSION: ${{ inputs.version }}

src/main.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,36 @@ test("uses versioned tar archives for Supabase CLI v2.99.0 and later", async ()
188188
});
189189
});
190190

191+
test("uses apk archives for Supabase CLI v2.99.0 and later on Linux musl", async () => {
192+
const { getDownloadArchive } = await getMainModule();
193+
194+
const archive = await getDownloadArchive("2.100.1", "linux", "x64", true);
195+
196+
expect(archive).toEqual({
197+
url: "https://github.com/supabase/cli/releases/download/v2.100.1/supabase_2.100.1_linux_amd64.apk",
198+
format: "apk",
199+
});
200+
});
201+
202+
test("keeps tar archives before Supabase CLI v2.99.0 on Linux musl", async () => {
203+
const { getDownloadArchive } = await getMainModule();
204+
205+
const archive = await getDownloadArchive("2.98.2", "linux", "x64", true);
206+
207+
expect(archive).toEqual({
208+
url: "https://github.com/supabase/cli/releases/download/v2.98.2/supabase_linux_amd64.tar.gz",
209+
format: "tar",
210+
});
211+
});
212+
213+
test("uses usr/bin as the CLI path for apk archives", async () => {
214+
const { getCliPath } = await getMainModule();
215+
216+
expect(getCliPath("/tmp/extracted", "apk")).toBe(path.join("/tmp/extracted", "usr", "bin"));
217+
expect(getCliPath("/tmp/extracted", "tar")).toBe("/tmp/extracted");
218+
expect(getCliPath("/tmp/extracted", "zip")).toBe("/tmp/extracted");
219+
});
220+
191221
test("keeps the unversioned tar archive layout before Supabase CLI v2.99.0", async () => {
192222
const { getDownloadArchive } = await getMainModule();
193223

src/main.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const VERSIONED_ARCHIVE_VERSION = "2.99.0";
1111
const DEFAULT_VERSION = "latest";
1212
const GITHUB_RELEASES_API = "https://api.github.com/repos/supabase/cli/releases/latest";
1313

14-
type ArchiveFormat = "tar" | "zip";
14+
type ArchiveFormat = "apk" | "tar" | "zip";
1515

1616
type DownloadArchive = {
1717
url: string;
@@ -188,7 +188,19 @@ async function resolveLatestVersion(): Promise<string> {
188188
return normalizeVersion(release.tag_name);
189189
}
190190

191-
function getArchiveFormat(version: string, platform: NodeJS.Platform): ArchiveFormat {
191+
function getArchiveFormat(
192+
version: string,
193+
platform: NodeJS.Platform,
194+
isMuslLinux: boolean,
195+
): ArchiveFormat {
196+
if (
197+
platform === "linux" &&
198+
isMuslLinux &&
199+
semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0
200+
) {
201+
return "apk";
202+
}
203+
192204
if (platform === "win32" && semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0) {
193205
return "zip";
194206
}
@@ -200,6 +212,7 @@ function getArchiveFilename(
200212
version: string,
201213
platform: NodeJS.Platform,
202214
arch: NodeJS.Architecture,
215+
archiveFormat: ArchiveFormat,
203216
): string {
204217
const archivePlatform = getArchivePlatform(platform);
205218
const archiveArch = getArchiveArch(arch);
@@ -208,6 +221,10 @@ function getArchiveFilename(
208221
return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`;
209222
}
210223

224+
if (platform === "linux" && archiveFormat === "apk") {
225+
return `supabase_${version}_${archivePlatform}_${archiveArch}.apk`;
226+
}
227+
211228
if (semver.order(version, VERSIONED_ARCHIVE_VERSION) >= 0) {
212229
const extension = platform === "win32" ? "zip" : "tar.gz";
213230
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`;
@@ -220,17 +237,45 @@ export async function getDownloadArchive(
220237
version: string,
221238
platform = process.platform,
222239
arch = process.arch,
240+
isMuslLinux?: boolean,
223241
): Promise<DownloadArchive> {
224242
const resolvedVersion =
225243
version.toLowerCase() === "latest" ? await resolveLatestVersion() : normalizeVersion(version);
226-
const filename = getArchiveFilename(resolvedVersion, platform, arch);
244+
const format = getArchiveFormat(
245+
resolvedVersion,
246+
platform,
247+
isMuslLinux ?? (await detectMuslLinux(platform)),
248+
);
249+
const filename = getArchiveFilename(resolvedVersion, platform, arch, format);
227250

228251
return {
229252
url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`,
230-
format: getArchiveFormat(resolvedVersion, platform),
253+
format,
231254
};
232255
}
233256

257+
async function detectMuslLinux(platform = process.platform): Promise<boolean> {
258+
if (platform !== "linux") {
259+
return false;
260+
}
261+
262+
if (existsSync("/etc/alpine-release")) {
263+
return true;
264+
}
265+
266+
try {
267+
const output = await $`ldd --version`.quiet().text();
268+
return output.toLowerCase().includes("musl");
269+
} catch (error) {
270+
const output = error instanceof Error ? error.message : String(error);
271+
return output.toLowerCase().includes("musl");
272+
}
273+
}
274+
275+
export function getCliPath(extractedPath: string, archiveFormat: ArchiveFormat): string {
276+
return archiveFormat === "apk" ? path.join(extractedPath, "usr", "bin") : extractedPath;
277+
}
278+
234279
function getCliExecutablePath(cliPath: string): string {
235280
if (process.platform !== "win32") {
236281
return path.join(cliPath, "supabase");
@@ -263,10 +308,11 @@ export async function run(): Promise<void> {
263308
const version = resolveVersion(core.getInput("version"));
264309
const archive = await getDownloadArchive(version);
265310
const archivePath = await tc.downloadTool(archive.url);
266-
const cliPath =
311+
const extractedPath =
267312
archive.format === "zip"
268313
? await tc.extractZip(archivePath)
269314
: await tc.extractTar(archivePath);
315+
const cliPath = getCliPath(extractedPath, archive.format);
270316
const installedVersion = await determineInstalledVersion(cliPath);
271317
core.setOutput("version", installedVersion);
272318
core.addPath(cliPath);

0 commit comments

Comments
 (0)