Skip to content

Commit cd9b0fd

Browse files
committed
fix: handle Supabase CLI v2.99 archives on v1
1 parent b60b589 commit cd9b0fd

5 files changed

Lines changed: 167 additions & 30 deletions

File tree

__tests__/main.test.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getDownloadUrl } from '../src/utils'
1+
import { getDownloadArchive, getDownloadUrl } from '../src/utils'
22
import { CLI_CONFIG_REGISTRY } from '../src/main'
33
import * as os from 'os'
44
import * as process from 'process'
@@ -7,7 +7,11 @@ import * as path from 'path'
77
import * as fs from 'fs'
88
import * as yaml from 'js-yaml'
99
import * as url from 'url'
10-
import { expect, test } from '@jest/globals'
10+
import { afterEach, expect, jest, test } from '@jest/globals'
11+
12+
afterEach(() => {
13+
jest.restoreAllMocks()
14+
})
1115

1216
test('gets download url to binary', async () => {
1317
const url = await getDownloadUrl('1.28.0')
@@ -31,10 +35,42 @@ test('gets legacy download url to binary', async () => {
3135
})
3236

3337
test('gets download url to latest version', async () => {
34-
const url = await getDownloadUrl('latest')
35-
expect(url).toMatch(
36-
'https://github.com/supabase/cli/releases/latest/download/'
38+
jest.spyOn(globalThis, 'fetch').mockResolvedValue(
39+
new Response(JSON.stringify({ tag_name: 'v2.99.0' }), {
40+
status: 200,
41+
statusText: 'OK'
42+
})
3743
)
44+
45+
const url = await getDownloadUrl('latest')
46+
expect(url).toContain('/download/v2.99.0/supabase_2.99.0_')
47+
expect(url).toMatch(/\.tar\.gz$|\.zip$/)
48+
})
49+
50+
test('gets versioned archive url to binary from Supabase CLI v2.99.0', async () => {
51+
const archive = await getDownloadArchive('2.99.0', 'linux', 'x64')
52+
53+
expect(archive).toEqual({
54+
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_linux_amd64.tar.gz',
55+
format: 'tar'
56+
})
57+
})
58+
59+
test('gets versioned zip archive url on Windows from Supabase CLI v2.99.0', async () => {
60+
const archive = await getDownloadArchive('2.99.0', 'win32', 'x64')
61+
62+
expect(archive).toEqual({
63+
url: 'https://github.com/supabase/cli/releases/download/v2.99.0/supabase_2.99.0_windows_amd64.zip',
64+
format: 'zip'
65+
})
66+
})
67+
68+
test('keeps unversioned archive url to binary before Supabase CLI v2.99.0', async () => {
69+
const url = await getDownloadUrl('2.98.2')
70+
71+
expect(url).toContain('/download/v2.98.2/supabase_')
72+
expect(url).not.toContain('supabase_2.98.2_')
73+
expect(url).toMatch(/\.tar\.gz$/)
3874
})
3975

4076
// shows how the runner will run a javascript action with env / stdout protocol

dist/index.js

Lines changed: 43 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as core from '@actions/core'
22
import * as tc from '@actions/tool-cache'
33
import { gte } from 'semver'
4-
import { getDownloadUrl, determineInstalledVersion } from './utils.js'
4+
import { getDownloadArchive, determineInstalledVersion } from './utils.js'
55

66
export const CLI_CONFIG_REGISTRY = 'SUPABASE_INTERNAL_IMAGE_REGISTRY'
77

@@ -16,11 +16,14 @@ export async function run(): Promise<void> {
1616
const version = core.getInput('version')
1717

1818
// Download the specific version of the tool, e.g. as a tarball/zipball
19-
const download = await getDownloadUrl(version)
20-
const pathToTarball = await tc.downloadTool(download)
19+
const download = await getDownloadArchive(version)
20+
const pathToArchive = await tc.downloadTool(download.url)
2121

2222
// Extract the tarball/zipball onto host runner
23-
const pathToCLI = await tc.extractTar(pathToTarball)
23+
const pathToCLI =
24+
download.format === 'zip'
25+
? await tc.extractZip(pathToArchive)
26+
: await tc.extractTar(pathToArchive)
2427

2528
// Expose the tool by adding it to the PATH
2629
core.addPath(pathToCLI)

src/utils.ts

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { exec } from 'child_process'
22
import os from 'os'
3-
import { lt } from 'semver'
3+
import { gte, lt } from 'semver'
44
import { promisify } from 'util'
55

66
const doExec = promisify(exec)
7+
const VERSIONED_ARCHIVE_VERSION = '2.99.0'
8+
const LATEST_RELEASE_URL =
9+
'https://api.github.com/repos/supabase/cli/releases/latest'
10+
11+
export type ArchiveFormat = 'tar' | 'zip'
12+
13+
export type DownloadArchive = {
14+
url: string
15+
format: ArchiveFormat
16+
}
717

818
// arch in [arm, arm64, x64...] (https://nodejs.org/docs/latest-v16.x/api/os.html#osarch)
919
// return value in [amd64, arm64, arm]
@@ -23,17 +33,73 @@ const mapOS = (platform: string): string => {
2333
return mappings[platform] || platform
2434
}
2535

26-
export const getDownloadUrl = async (version: string): Promise<string> => {
27-
const platform = mapOS(os.platform())
28-
const arch = mapArch(os.arch())
29-
const filename = `supabase_${platform}_${arch}.tar.gz`
30-
if (version.toLowerCase() === 'latest') {
31-
return `https://github.com/supabase/cli/releases/latest/download/${filename}`
36+
const normalizeVersion = (version: string): string => version.replace(/^v/i, '')
37+
38+
const resolveLatestVersion = async (): Promise<string> => {
39+
const response = await fetch(LATEST_RELEASE_URL)
40+
if (!response.ok) {
41+
throw new Error(
42+
`Failed to resolve latest Supabase CLI release: ${response.statusText}`
43+
)
44+
}
45+
46+
const release = (await response.json()) as { tag_name?: unknown }
47+
if (typeof release.tag_name !== 'string') {
48+
throw new Error(
49+
'Failed to resolve latest Supabase CLI release: missing tag name'
50+
)
51+
}
52+
53+
return normalizeVersion(release.tag_name)
54+
}
55+
56+
const getArchiveFormat = (version: string, platform: string): ArchiveFormat => {
57+
if (platform === 'win32' && gte(version, VERSIONED_ARCHIVE_VERSION)) {
58+
return 'zip'
3259
}
60+
61+
return 'tar'
62+
}
63+
64+
const getArchiveFilename = (
65+
version: string,
66+
platform: string,
67+
arch: string
68+
): string => {
69+
const archivePlatform = mapOS(platform)
70+
const archiveArch = mapArch(arch)
3371
if (lt(version, '1.28.0')) {
34-
return `https://github.com/supabase/cli/releases/download/v${version}/supabase_${version}_${platform}_${arch}.tar.gz`
72+
return `supabase_${version}_${archivePlatform}_${archiveArch}.tar.gz`
73+
}
74+
75+
if (gte(version, VERSIONED_ARCHIVE_VERSION)) {
76+
const extension = platform === 'win32' ? 'zip' : 'tar.gz'
77+
return `supabase_${version}_${archivePlatform}_${archiveArch}.${extension}`
78+
}
79+
80+
return `supabase_${archivePlatform}_${archiveArch}.tar.gz`
81+
}
82+
83+
export const getDownloadArchive = async (
84+
version: string,
85+
platform = os.platform(),
86+
arch = os.arch()
87+
): Promise<DownloadArchive> => {
88+
const resolvedVersion =
89+
version.toLowerCase() === 'latest'
90+
? await resolveLatestVersion()
91+
: normalizeVersion(version)
92+
const filename = getArchiveFilename(resolvedVersion, platform, arch)
93+
94+
return {
95+
url: `https://github.com/supabase/cli/releases/download/v${resolvedVersion}/${filename}`,
96+
format: getArchiveFormat(resolvedVersion, platform)
3597
}
36-
return `https://github.com/supabase/cli/releases/download/v${version}/${filename}`
98+
}
99+
100+
export const getDownloadUrl = async (version: string): Promise<string> => {
101+
const archive = await getDownloadArchive(version)
102+
return archive.url
37103
}
38104

39105
export const determineInstalledVersion = async (): Promise<string> => {

0 commit comments

Comments
 (0)