mirror of
https://github.com/supabase/setup-cli.git
synced 2026-06-27 17:36:57 +00:00
fix: v1 setup on Linux musl (#432)
## Summary - Detect Linux musl runners in the v1 action and download the Supabase CLI `.apk` asset for CLI versions `>= 2.99.0`. - Add the extracted `usr/bin` directory to `PATH` for `.apk` archives. - Backport the optional `github-token` input for authenticated `latest` release lookup, because the test matrix hit unauthenticated GitHub API rate limits. - Rebuild `dist/index.js` for the Node action. ## Validation - `npm run format:check` - `npm run lint` - `npm test` - `npm run package` - Local Docker smoke test in `node:20-alpine` with `INPUT_VERSION=2.100.1` - setup-cli-testing workflow: https://github.com/jgoux/setup-cli-testing/actions/runs/26165593808 The external workflow passed Alpine `2.100.1`, Alpine `latest`, and Ubuntu/macOS/Windows with both `2.100.1` and `latest`.
This commit is contained in:
18
src/main.ts
18
src/main.ts
@@ -1,7 +1,11 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as tc from '@actions/tool-cache'
|
||||
import { gte } from 'semver'
|
||||
import { getDownloadArchive, determineInstalledVersion } from './utils.js'
|
||||
import {
|
||||
getDownloadArchive,
|
||||
determineInstalledVersion,
|
||||
getCliPath
|
||||
} from './utils.js'
|
||||
|
||||
export const CLI_CONFIG_REGISTRY = 'SUPABASE_INTERNAL_IMAGE_REGISTRY'
|
||||
|
||||
@@ -14,16 +18,24 @@ export async function run(): Promise<void> {
|
||||
try {
|
||||
// Get version of tool to be installed
|
||||
const version = core.getInput('version')
|
||||
const githubToken = core.getInput('github-token')
|
||||
|
||||
// Download the specific version of the tool, e.g. as a tarball/zipball
|
||||
const download = await getDownloadArchive(version)
|
||||
const download = await getDownloadArchive(
|
||||
version,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
githubToken
|
||||
)
|
||||
const pathToArchive = await tc.downloadTool(download.url)
|
||||
|
||||
// Extract the tarball/zipball onto host runner
|
||||
const pathToCLI =
|
||||
const extractedPath =
|
||||
download.format === 'zip'
|
||||
? await tc.extractZip(pathToArchive)
|
||||
: await tc.extractTar(pathToArchive)
|
||||
const pathToCLI = getCliPath(extractedPath, download.format)
|
||||
|
||||
// Expose the tool by adding it to the PATH
|
||||
core.addPath(pathToCLI)
|
||||
|
||||
91
src/utils.ts
91
src/utils.ts
@@ -1,4 +1,5 @@
|
||||
import { exec } from 'child_process'
|
||||
import { existsSync } from 'fs'
|
||||
import os from 'os'
|
||||
import { gte, lt } from 'semver'
|
||||
import { promisify } from 'util'
|
||||
@@ -8,7 +9,7 @@ const VERSIONED_ARCHIVE_VERSION = '2.99.0'
|
||||
const LATEST_RELEASE_URL =
|
||||
'https://api.github.com/repos/supabase/cli/releases/latest'
|
||||
|
||||
export type ArchiveFormat = 'tar' | 'zip'
|
||||
export type ArchiveFormat = 'apk' | 'tar' | 'zip'
|
||||
|
||||
export type DownloadArchive = {
|
||||
url: string
|
||||
@@ -35,8 +36,18 @@ const mapOS = (platform: string): string => {
|
||||
|
||||
const normalizeVersion = (version: string): string => version.replace(/^v/i, '')
|
||||
|
||||
const resolveLatestVersion = async (): Promise<string> => {
|
||||
const response = await fetch(LATEST_RELEASE_URL)
|
||||
const resolveLatestVersion = async (githubToken?: string): Promise<string> => {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: 'application/vnd.github+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
}
|
||||
const token = githubToken?.trim()
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const response = await fetch(LATEST_RELEASE_URL, { headers })
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to resolve latest Supabase CLI release: ${response.statusText}`
|
||||
@@ -53,7 +64,37 @@ const resolveLatestVersion = async (): Promise<string> => {
|
||||
return normalizeVersion(release.tag_name)
|
||||
}
|
||||
|
||||
const getArchiveFormat = (version: string, platform: string): ArchiveFormat => {
|
||||
const detectMuslLinux = async (platform = os.platform()): Promise<boolean> => {
|
||||
if (platform !== 'linux') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (existsSync('/etc/alpine-release')) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await doExec('ldd --version')
|
||||
return `${stdout}\n${stderr}`.toLowerCase().includes('musl')
|
||||
} catch (error) {
|
||||
const output = error instanceof Error ? error.message : String(error)
|
||||
return output.toLowerCase().includes('musl')
|
||||
}
|
||||
}
|
||||
|
||||
const getArchiveFormat = (
|
||||
version: string,
|
||||
platform: string,
|
||||
isMuslLinux: boolean
|
||||
): ArchiveFormat => {
|
||||
if (
|
||||
platform === 'linux' &&
|
||||
isMuslLinux &&
|
||||
gte(version, VERSIONED_ARCHIVE_VERSION)
|
||||
) {
|
||||
return 'apk'
|
||||
}
|
||||
|
||||
if (platform === 'win32' && gte(version, VERSIONED_ARCHIVE_VERSION)) {
|
||||
return 'zip'
|
||||
}
|
||||
@@ -64,7 +105,8 @@ const getArchiveFormat = (version: string, platform: string): ArchiveFormat => {
|
||||
const getArchiveFilename = (
|
||||
version: string,
|
||||
platform: string,
|
||||
arch: string
|
||||
arch: string,
|
||||
format: ArchiveFormat
|
||||
): string => {
|
||||
const archivePlatform = mapOS(platform)
|
||||
const archiveArch = mapArch(arch)
|
||||
@@ -72,6 +114,10 @@ const getArchiveFilename = (
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`
|
||||
}
|
||||
|
||||
if (platform === 'linux' && format === 'apk') {
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.apk`
|
||||
}
|
||||
|
||||
if (gte(version, VERSIONED_ARCHIVE_VERSION)) {
|
||||
const extension = platform === 'win32' ? 'zip' : 'tar.gz'
|
||||
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`
|
||||
@@ -83,22 +129,45 @@ const getArchiveFilename = (
|
||||
export const getDownloadArchive = async (
|
||||
version: string,
|
||||
platform = os.platform(),
|
||||
arch = os.arch()
|
||||
arch = os.arch(),
|
||||
isMuslLinux?: boolean,
|
||||
githubToken?: string
|
||||
): Promise<DownloadArchive> => {
|
||||
const resolvedVersion =
|
||||
version.toLowerCase() === 'latest'
|
||||
? await resolveLatestVersion()
|
||||
? await resolveLatestVersion(githubToken)
|
||||
: 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
|
||||
}
|
||||
}
|
||||
|
||||
export const getDownloadUrl = async (version: string): Promise<string> => {
|
||||
const archive = await getDownloadArchive(version)
|
||||
export const getCliPath = (
|
||||
extractedPath: string,
|
||||
archiveFormat: ArchiveFormat
|
||||
): string => {
|
||||
return archiveFormat === 'apk' ? `${extractedPath}/usr/bin` : extractedPath
|
||||
}
|
||||
|
||||
export const getDownloadUrl = async (
|
||||
version: string,
|
||||
githubToken?: string
|
||||
): Promise<string> => {
|
||||
const archive = await getDownloadArchive(
|
||||
version,
|
||||
os.platform(),
|
||||
os.arch(),
|
||||
undefined,
|
||||
githubToken
|
||||
)
|
||||
return archive.url
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user