import { exec } from 'child_process' import os from 'os' import { gte, lt } from 'semver' import { promisify } from 'util' const doExec = promisify(exec) 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 DownloadArchive = { url: string format: ArchiveFormat } // arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch) // return value in [amd64, arm64, arm] const mapArch = (arch: string): string => { const mappings: Record = { x64: 'amd64' } return mappings[arch] || arch } // os in [darwin, linux, win32...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osplatform) // return value in [darwin, linux, windows] const mapOS = (platform: string): string => { const mappings: Record = { win32: 'windows' } return mappings[platform] || platform } const normalizeVersion = (version: string): string => version.replace(/^v/i, '') const resolveLatestVersion = async (): Promise => { const response = await fetch(LATEST_RELEASE_URL) if (!response.ok) { throw new Error( `Failed to resolve latest Supabase CLI release: ${response.statusText}` ) } const release = (await response.json()) as { tag_name?: unknown } if (typeof release.tag_name !== 'string') { throw new Error( 'Failed to resolve latest Supabase CLI release: missing tag name' ) } return normalizeVersion(release.tag_name) } const getArchiveFormat = (version: string, platform: string): ArchiveFormat => { if (platform === 'win32' && gte(version, VERSIONED_ARCHIVE_VERSION)) { return 'zip' } return 'tar' } const getArchiveFilename = ( version: string, platform: string, arch: string ): string => { const archivePlatform = mapOS(platform) const archiveArch = mapArch(arch) if (lt(version, '1.28.0')) { return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz` } if (gte(version, VERSIONED_ARCHIVE_VERSION)) { const extension = platform === 'win32' ? 'zip' : 'tar.gz' return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}` } return `supabase_${archivePlatform}_${archiveArch}.tar.gz` } export const getDownloadArchive = async ( version: string, platform = os.platform(), arch = os.arch() ): Promise => { const resolvedVersion = version.toLowerCase() === 'latest' ? await resolveLatestVersion() : normalizeVersion(version) const filename = getArchiveFilename(resolvedVersion, platform, arch) return { url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`, format: getArchiveFormat(resolvedVersion, platform) } } export const getDownloadUrl = async (version: string): Promise => { const archive = await getDownloadArchive(version) return archive.url } export const determineInstalledVersion = async (): Promise => { const { stdout } = await doExec('supabase --version') const version = stdout.trim() if (!version) { throw new Error('Could not determine installed Supabase CLI version') } return version }