Compare commits

..

1 Commits

Author SHA1 Message Date
Julien Goux
cd9b0fd6c9 fix: handle Supabase CLI v2.99 archives on v1 2026-05-18 13:19:23 +02:00
5 changed files with 167 additions and 30 deletions

View File

@@ -1,4 +1,4 @@
import { getDownloadUrl } from '../src/utils' import { getDownloadArchive, getDownloadUrl } from '../src/utils'
import { CLI_CONFIG_REGISTRY } from '../src/main' import { CLI_CONFIG_REGISTRY } from '../src/main'
import * as os from 'os' import * as os from 'os'
import * as process from 'process' import * as process from 'process'
@@ -7,7 +7,11 @@ import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as url from 'url' import * as url from 'url'
import { expect, test } from '@jest/globals' import { afterEach, expect, jest, test } from '@jest/globals'
afterEach(() => {
jest.restoreAllMocks()
})
test('gets download url to binary', async () => { test('gets download url to binary', async () => {
const url = await getDownloadUrl('1.28.0') const url = await getDownloadUrl('1.28.0')
@@ -31,10 +35,42 @@ test('gets legacy download url to binary', async () => {
}) })
test('gets download url to latest version', async () => { test('gets download url to latest version', async () => {
const url = await getDownloadUrl('latest') jest.spyOn(globalThis, 'fetch').mockResolvedValue(
expect(url).toMatch( new Response(JSON.stringify({ tag_name: 'v2.99.0' }), {
'https://github.com/supabase/cli/releases/latest/download/' status: 200,
statusText: 'OK'
})
) )
const url = await getDownloadUrl('latest')
expect(url).toContain('/download/v2.99.0/supabase_2.99.0_')
expect(url).toMatch(/\.tar\.gz$|\.zip$/)
})
test('gets versioned archive url to binary from Supabase CLI v2.99.0', async () => {
const archive = await getDownloadArchive('2.99.0', 'linux', 'x64')
expect(archive).toEqual({
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_linux_amd64.tar.gz',
format: 'tar'
})
})
test('gets versioned zip archive url on Windows from Supabase CLI v2.99.0', async () => {
const archive = await getDownloadArchive('2.99.0', 'win32', 'x64')
expect(archive).toEqual({
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_windows_amd64.zip',
format: 'zip'
})
})
test('keeps unversioned archive url to binary before Supabase CLI v2.99.0', async () => {
const url = await getDownloadUrl('2.98.2')
expect(url).toContain('/download/v2.98.2/supabase_')
expect(url).not.toContain('supabase_2.98.2_')
expect(url).toMatch(/\.tar\.gz$/)
}) })
// shows how the runner will run a javascript action with env / stdout protocol // shows how the runner will run a javascript action with env / stdout protocol

54
dist/index.js generated vendored
View File

@@ -32518,6 +32518,8 @@ function requireSemver () {
var semverExports = requireSemver(); var semverExports = requireSemver();
const doExec = promisify(exec$1); const doExec = promisify(exec$1);
const VERSIONED_ARCHIVE_VERSION = '2.99.0';
const LATEST_RELEASE_URL = 'https://api.github.com/repos/supabase/cli/releases/latest';
// arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch) // arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch)
// return value in [amd64, arm64, arm] // return value in [amd64, arm64, arm]
const mapArch = (arch) => { const mapArch = (arch) => {
@@ -32534,17 +32536,45 @@ const mapOS = (platform) => {
}; };
return mappings[platform] || platform; return mappings[platform] || platform;
}; };
const getDownloadUrl = async (version) => { const normalizeVersion = (version) => version.replace(/^v/i, '');
const platform = mapOS(require$$0.platform()); const resolveLatestVersion = async () => {
const arch = mapArch(require$$0.arch()); const response = await fetch(LATEST_RELEASE_URL);
const filename = `supabase_${platform}_${arch}.tar.gz`; if (!response.ok) {
if (version.toLowerCase() === 'latest') { throw new Error(`Failed to resolve latest Supabase CLI release: ${response.statusText}`);
return `https://github.com/supabase/cli/releases/latest/download/${filename}`;
} }
const release = (await response.json());
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, platform) => {
if (platform === 'win32' && semverExports.gte(version, VERSIONED_ARCHIVE_VERSION)) {
return 'zip';
}
return 'tar';
};
const getArchiveFilename = (version, platform, arch) => {
const archivePlatform = mapOS(platform);
const archiveArch = mapArch(arch);
if (semverExports.lt(version, '1.28.0')) { if (semverExports.lt(version, '1.28.0')) {
return `https://github.com/supabase/cli/releases/download/v${version}/supabase_${version}_${platform}_${arch}.tar.gz`; return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`;
} }
return `https://github.com/supabase/cli/releases/download/v${version}/${filename}`; if (semverExports.gte(version, VERSIONED_ARCHIVE_VERSION)) {
const extension = platform === 'win32' ? 'zip' : 'tar.gz';
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`;
}
return `supabase_${archivePlatform}_${archiveArch}.tar.gz`;
};
const getDownloadArchive = async (version, platform = require$$0.platform(), arch = require$$0.arch()) => {
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)
};
}; };
const determineInstalledVersion = async () => { const determineInstalledVersion = async () => {
const { stdout } = await doExec('supabase --version'); const { stdout } = await doExec('supabase --version');
@@ -32566,10 +32596,12 @@ async function run() {
// Get version of tool to be installed // Get version of tool to be installed
const version = coreExports.getInput('version'); const version = coreExports.getInput('version');
// Download the specific version of the tool, e.g. as a tarball/zipball // Download the specific version of the tool, e.g. as a tarball/zipball
const download = await getDownloadUrl(version); const download = await getDownloadArchive(version);
const pathToTarball = await toolCacheExports.downloadTool(download); const pathToArchive = await toolCacheExports.downloadTool(download.url);
// Extract the tarball/zipball onto host runner // Extract the tarball/zipball onto host runner
const pathToCLI = await toolCacheExports.extractTar(pathToTarball); const pathToCLI = download.format === 'zip'
? await toolCacheExports.extractZip(pathToArchive)
: await toolCacheExports.extractTar(pathToArchive);
// Expose the tool by adding it to the PATH // Expose the tool by adding it to the PATH
coreExports.addPath(pathToCLI); coreExports.addPath(pathToCLI);
// Expose installed tool version // Expose installed tool version

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as tc from '@actions/tool-cache' import * as tc from '@actions/tool-cache'
import { gte } from 'semver' import { gte } from 'semver'
import { getDownloadUrl, determineInstalledVersion } from './utils.js' import { getDownloadArchive, determineInstalledVersion } from './utils.js'
export const CLI_CONFIG_REGISTRY = 'SUPABASE_INTERNAL_IMAGE_REGISTRY' export const CLI_CONFIG_REGISTRY = 'SUPABASE_INTERNAL_IMAGE_REGISTRY'
@@ -16,11 +16,14 @@ export async function run(): Promise<void> {
const version = core.getInput('version') const version = core.getInput('version')
// Download the specific version of the tool, e.g. as a tarball/zipball // Download the specific version of the tool, e.g. as a tarball/zipball
const download = await getDownloadUrl(version) const download = await getDownloadArchive(version)
const pathToTarball = await tc.downloadTool(download) const pathToArchive = await tc.downloadTool(download.url)
// Extract the tarball/zipball onto host runner // Extract the tarball/zipball onto host runner
const pathToCLI = await tc.extractTar(pathToTarball) const pathToCLI =
download.format === 'zip'
? await tc.extractZip(pathToArchive)
: await tc.extractTar(pathToArchive)
// Expose the tool by adding it to the PATH // Expose the tool by adding it to the PATH
core.addPath(pathToCLI) core.addPath(pathToCLI)

View File

@@ -1,9 +1,19 @@
import { exec } from 'child_process' import { exec } from 'child_process'
import os from 'os' import os from 'os'
import { lt } from 'semver' import { gte, lt } from 'semver'
import { promisify } from 'util' import { promisify } from 'util'
const doExec = promisify(exec) 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) // arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch)
// return value in [amd64, arm64, arm] // return value in [amd64, arm64, arm]
@@ -23,17 +33,73 @@ const mapOS = (platform: string): string => {
return mappings[platform] || platform return mappings[platform] || platform
} }
export const getDownloadUrl = async (version: string): Promise<string> => { const normalizeVersion = (version: string): string => version.replace(/^v/i, '')
const platform = mapOS(os.platform())
const arch = mapArch(os.arch()) const resolveLatestVersion = async (): Promise<string> => {
const filename = `supabase_${platform}_${arch}.tar.gz` const response = await fetch(LATEST_RELEASE_URL)
if (version.toLowerCase() === 'latest') { if (!response.ok) {
return `https://github.com/supabase/cli/releases/latest/download/${filename}` 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')) { if (lt(version, '1.28.0')) {
return `https://github.com/supabase/cli/releases/download/v${version}/supabase_${version}_${platform}_${arch}.tar.gz` return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`
} }
return `https://github.com/supabase/cli/releases/download/v${version}/${filename}`
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<DownloadArchive> => {
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<string> => {
const archive = await getDownloadArchive(version)
return archive.url
} }
export const determineInstalledVersion = async (): Promise<string> => { export const determineInstalledVersion = async (): Promise<string> => {