diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index 5baee3d882..d8b2f812bf 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -14,14 +14,12 @@ import InlineEditorButton from 'components/layouts/AppLayout/InlineEditorButton' import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown' import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown' import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils' -import { useCLIReleaseVersionQuery } from 'data/misc/cli-release-version-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgUsageQuery } from 'data/usage/org-usage-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { useShowLayoutHeader } from 'hooks/misc/useShowLayoutHeader' import { IS_PLATFORM } from 'lib/constants' -import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { useAppStateSnapshot } from 'state/app-state' import { Badge, cn } from 'ui' import BreadcrumbsView from './BreadcrumbsView' @@ -71,11 +69,6 @@ const LayoutHeader = ({ const isBranchingEnabled = selectedProject?.is_branch_enabled === true const { setMobileMenuOpen } = useAppStateSnapshot() - const { open: isAiAssistantOpen } = useAiAssistantStateSnapshot() - - const { data } = useCLIReleaseVersionQuery() - const currentCliVersion = data?.current - const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrganization?.slug, }) @@ -200,7 +193,7 @@ const LayoutHeader = ({ ) : ( <> - {!!currentCliVersion && } + )} diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.test.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.test.ts new file mode 100644 index 0000000000..605592f0f2 --- /dev/null +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from 'vitest' +import { semverGte, semverLte } from './LocalVersionPopover.utils' + +describe('LocalVersionPopover.utils:semverLte', () => { + test('1.2.2 should be lower than 2.1.1', () => { + const version = { major: 1, minor: 2, patch: 2 } + const versionCompare = { major: 2, minor: 1, patch: 1 } + const result = semverLte(version, versionCompare) + expect(result).toBe(true) + }) + test('2.1.0 should not be lower than 1.0.0', () => { + const version = { major: 2, minor: 1, patch: 0 } + const versionCompare = { major: 1, minor: 0, patch: 0 } + const result = semverLte(version, versionCompare) + expect(result).toBe(false) + }) + test('2.1.2 should not be lower than 1.99.99', () => { + const version = { major: 2, minor: 1, patch: 2 } + const versionCompare = { major: 1, minor: 99, patch: 99 } + const result = semverLte(version, versionCompare) + expect(result).toBe(false) + }) + test('Same value comparison should return true', () => { + const version = { major: 2, minor: 1, patch: 2 } + const versionCompare = { major: 2, minor: 1, patch: 2 } + const result = semverLte(version, versionCompare) + expect(result).toBe(true) + }) +}) + +describe('LocalVersionPopover.utils:semverGte', () => { + test('2.0.0 should be greater than 1.99.99', () => { + const version = { major: 2, minor: 0, patch: 0 } + const versionCompare = { major: 1, minor: 99, patch: 99 } + const result = semverGte(version, versionCompare) + expect(result).toBe(true) + }) + test('1.99.99 should be not be greater than 2.0.1', () => { + const version = { major: 1, minor: 99, patch: 99 } + const versionCompare = { major: 2, minor: 0, patch: 1 } + const result = semverGte(version, versionCompare) + expect(result).toBe(false) + }) + test('Same value comparison should return true', () => { + const version = { major: 2, minor: 1, patch: 2 } + const versionCompare = { major: 2, minor: 1, patch: 2 } + const result = semverLte(version, versionCompare) + expect(result).toBe(true) + }) +}) diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.tsx index 67babfa173..1fdd245e20 100644 --- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.tsx +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.tsx @@ -23,37 +23,37 @@ import { TabsTrigger_Shadcn_, } from 'ui' import { Admonition } from 'ui-patterns' - -/** - * TODO: Chat with Qiao how we can pass the CLI version into the env var for STUDIO_VERSION - * Ideally we can also mark the beta version via STUDIO_VERSION too - */ +import { getSemver, semverGte, semverLte } from './LocalVersionPopover.utils' export const LocalVersionPopover = () => { const { data, isSuccess } = useCLIReleaseVersionQuery() + const currentCliVersion = data?.current const hasLatestCLIVersion = isSuccess && !!data?.latest - const isLatestVersion = data?.current === data?.latest - // [Joshen] This is just scaffolding - will need to figure out how to identify beta versions - // We can do this separately - const isBeta = false ///(data?.latest ?? '').includes('beta') + const current = getSemver(data?.current) + const latest = getSemver(data?.latest) + + const hasUpdate = + !!current && !!latest ? data?.current !== data?.latest && semverLte(current, latest) : false + const isBeta = + !!current && !!latest && data?.current !== data?.latest && semverGte(current, latest) const approximateNextRelease = !!data?.published_at ? dayjs(data?.published_at).utc().add(14, 'day').format('DD MMM YYYY') : undefined - if (!isSuccess) return null + if (!isSuccess || !currentCliVersion) return null return ( - - {isBeta ? 'Beta' : isLatestVersion ? 'Latest' : 'Update available'} + + {isBeta ? 'Beta' : hasUpdate ? 'Update available' : 'Latest'} {hasLatestCLIVersion ? ( - !isBeta && !isLatestVersion ? ( + !isBeta && hasUpdate ? (

A new version of Supabase CLI is available:

@@ -143,11 +143,16 @@ export const LocalVersionPopover = () => { description="Beta releases are also available between stable releases through the Beta version of the CLI, which might be helpful if you are waiting for a specific fix." >

If you'd like to try, we recommend doing so via npm:

-
+
npm i supabase@beta --save-dev
+ { +

+ Latest Beta version: {data.beta} +

+ } {

Current version:

-

{data.current}

+

{currentCliVersion}

- {hasLatestCLIVersion && !isLatestVersion && !isBeta && ( + {hasLatestCLIVersion && hasUpdate && !isBeta && (

Available version:

{data.latest}

diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.utils.ts b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.utils.ts new file mode 100644 index 0000000000..31a872961e --- /dev/null +++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LocalVersionPopover.utils.ts @@ -0,0 +1,28 @@ +type CLIVersionSemver = { major: number; minor: number; patch: number } + +// [Joshen] Specifically in the syntax of `v0.0.0` +export const getSemver = (version?: string) => { + if (!version) return undefined + const [major, minor, patch] = version.slice(1).split('.') + return { major: Number(major), minor: Number(minor), patch: Number(patch) } +} + +export const semverLte = (a: CLIVersionSemver, b: CLIVersionSemver) => { + if ( + a.major > b.major || + (a.major === b.major && a.minor > b.minor) || + (a.major === b.major && a.minor === b.minor && a.patch > b.patch) + ) + return false + return true +} + +export const semverGte = (a: CLIVersionSemver, b: CLIVersionSemver) => { + if ( + a.major < b.major || + (a.major === b.major && a.minor < b.minor) || + (a.major === b.major && a.minor === b.minor && a.patch < b.patch) + ) + return false + return true +} diff --git a/apps/studio/data/misc/cli-release-version-query.ts b/apps/studio/data/misc/cli-release-version-query.ts index c28f0c0694..3394ca3405 100644 --- a/apps/studio/data/misc/cli-release-version-query.ts +++ b/apps/studio/data/misc/cli-release-version-query.ts @@ -7,7 +7,7 @@ import { miscKeys } from './keys' export async function getCLIReleaseVersion() { try { const data = await fetch(`${BASE_PATH}/api/cli-release-version`).then((res) => res.json()) - return data as { current: string | null; latest: string | null; published_at: string | null } + return data as { current?: string; latest?: string; beta?: string; published_at?: string } } catch (error) { throw error } diff --git a/apps/studio/pages/api/cli-release-version.ts b/apps/studio/pages/api/cli-release-version.ts index c57941fd45..0c67461cb3 100644 --- a/apps/studio/pages/api/cli-release-version.ts +++ b/apps/studio/pages/api/cli-release-version.ts @@ -8,23 +8,25 @@ type GitHubRepositoryRelease = { published_at: string } +const current = process.env.CURRENT_CLI_VERSION + const handler = async (req: NextApiRequest, res: NextApiResponse) => { - // [Joshen] Added under supabase/turbo.json, but not sure why it's still warning - // eslint-disable-next-line turbo/no-undeclared-env-vars - const version = process.env.CURRENT_CLI_VERSION - const fallback = { current: version, latest: null, published_at: null } try { - const data: GitHubRepositoryRelease[] = await fetch( - 'https://api.github.com/repos/supabase/cli/releases?per_page=1' + const { tag_name: latest, published_at }: GitHubRepositoryRelease = await fetch( + 'https://api.github.com/repos/supabase/cli/releases/latest' ).then((res) => res.json()) - if (data.length === 0) return res.status(200).json(fallback) + const data: GitHubRepositoryRelease[] = await fetch( + 'https://api.github.com/repos/supabase/cli/releases?per_page=1' + ) + .then((res) => res.json()) + // Ignore errors fetching beta release version + .catch(() => []) + const beta = data[0]?.tag_name - return res - .status(200) - .json({ current: version, latest: data[0].tag_name, published_at: data[0].published_at }) + return res.status(200).json({ current, latest, beta, published_at }) } catch { - return res.status(200).json(fallback) + return res.status(200).json({ current }) } } diff --git a/turbo.json b/turbo.json index cc99f10f52..d2d085b1a6 100644 --- a/turbo.json +++ b/turbo.json @@ -71,9 +71,9 @@ "FORCE_ASSET_CDN", "ASSET_CDN_S3_ENDPOINT", "SITE_NAME", - "VERCEL_URL", - "CURRENT_CLI_VERSION" + "VERCEL_URL" ], + "passThroughEnv": ["CURRENT_CLI_VERSION"], "outputs": [".next/**", "!.next/cache/**"] }, "www#build": {