- {!connection?.metadata?.framework ? (
+ {!frameworkIconUrl ? (
) : (
-

+

)}
{type === 'GitHub' ? (
{
const getForeignProjectIcon = useCallback(
(_project: ForeignProject) => {
const project = vercelProjectsById[_project.id]
+ const frameworkIconUrl = getFrameworkIconUrl(project?.framework)
- return !project?.framework ? (
+ return !frameworkIconUrl ? (
vercelIcon
) : (
-
+
)
},
[vercelProjectsById]
diff --git a/apps/studio/data/integrations/integrations.types.ts b/apps/studio/data/integrations/integrations.types.ts
index 4e2d69fe60c..cc3344b1a46 100644
--- a/apps/studio/data/integrations/integrations.types.ts
+++ b/apps/studio/data/integrations/integrations.types.ts
@@ -1,50 +1,3 @@
-export type VercelFramework =
- | (
- | 'blitzjs'
- | 'nextjs'
- | 'gatsby'
- | 'remix'
- | 'astro'
- | 'hexo'
- | 'eleventy'
- | 'docusaurus-2'
- | 'docusaurus'
- | 'preact'
- | 'solidstart'
- | 'dojo'
- | 'ember'
- | 'vue'
- | 'scully'
- | 'ionic-angular'
- | 'angular'
- | 'polymer'
- | 'svelte'
- | 'sveltekit'
- | 'sveltekit-1'
- | 'ionic-react'
- | 'create-react-app'
- | 'gridsome'
- | 'umijs'
- | 'sapper'
- | 'saber'
- | 'stencil'
- | 'nuxtjs'
- | 'redwoodjs'
- | 'hugo'
- | 'jekyll'
- | 'brunch'
- | 'middleman'
- | 'zola'
- | 'hydrogen'
- | 'vite'
- | 'vitepress'
- | 'vuepress'
- | 'parcel'
- | 'sanity'
- | 'storybook'
- )
- | null
-
export type VercelGitLink =
| {
/**
@@ -142,7 +95,7 @@ export type Imetadata = {
}
link?: VercelGitLink
name: string
- framework: VercelFramework
+ framework: string | null
}
export type IntegrationProjectConnection = {
diff --git a/apps/studio/lib/integration-utils.test.ts b/apps/studio/lib/integration-utils.test.ts
index 070ab28e471..68767eba83a 100644
--- a/apps/studio/lib/integration-utils.test.ts
+++ b/apps/studio/lib/integration-utils.test.ts
@@ -1,6 +1,10 @@
+import { readdirSync } from 'node:fs'
+import { join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import {
+ FRAMEWORK_ICON_SLUGS,
+ getFrameworkIconUrl,
getInitialMigrationSQLFromGitHubRepo,
getIntegrationConfigurationUrl,
} from './integration-utils'
@@ -10,6 +14,7 @@ import type {
VercelAccount,
VercelTeamAccount,
} from '@/data/integrations/integrations.types'
+import { BASE_PATH } from '@/lib/constants'
vi.mock('@/data/fetchers', () => ({
fetchHandler: vi.fn(),
@@ -255,4 +260,23 @@ describe('integration-utils', () => {
expect(result).toBe('')
})
})
+
+ describe('getFrameworkIconUrl', () => {
+ it('returns undefined for missing or unshipped frameworks and a URL for shipped ones', () => {
+ expect(getFrameworkIconUrl('not-a-framework')).toBeUndefined()
+ expect(getFrameworkIconUrl(null)).toBeUndefined()
+ expect(getFrameworkIconUrl(undefined)).toBeUndefined()
+ expect(getFrameworkIconUrl('')).toBeUndefined()
+ expect(getFrameworkIconUrl('nextjs')).toBe(`${BASE_PATH}/img/icons/frameworks/nextjs.svg`)
+ })
+
+ it('stays in sync with public/img/icons/frameworks/*.svg', () => {
+ const slugsOnDisk = readdirSync(join(__dirname, '../public/img/icons/frameworks'))
+ .filter((file) => file.endsWith('.svg'))
+ .map((file) => file.replace(/\.svg$/, ''))
+ .sort()
+
+ expect([...FRAMEWORK_ICON_SLUGS].sort()).toEqual(slugsOnDisk)
+ })
+ })
})
diff --git a/apps/studio/lib/integration-utils.ts b/apps/studio/lib/integration-utils.ts
index 3a17c746580..80e42858fee 100644
--- a/apps/studio/lib/integration-utils.ts
+++ b/apps/studio/lib/integration-utils.ts
@@ -3,6 +3,7 @@ import { getCreateMigrationsTableSQL, getInsertMigrationSQL } from '@supabase/pg
import { isResponseOk } from './api/apiWrapper'
import { fetchHandler } from '@/data/fetchers'
import type { Integration } from '@/data/integrations/integrations.types'
+import { BASE_PATH } from '@/lib/constants'
import { ResponseError, type SupaResponse } from '@/types'
async function fetchGitHub(url: string, responseJson = true): Promise> {
@@ -132,3 +133,53 @@ function getGitHubConfigurationUrl(integration: GitHubIntegration) {
: ''
}settings/installations/${integration.metadata?.installation_id}`
}
+
+export const FRAMEWORK_ICON_SLUGS: ReadonlySet = new Set([
+ 'angular',
+ 'astro',
+ 'blitzjs',
+ 'brunch',
+ 'create-react-app',
+ 'docusaurus',
+ 'docusaurus-2',
+ 'dojo',
+ 'eleventy',
+ 'ember',
+ 'gatsby',
+ 'gridsome',
+ 'hexo',
+ 'hugo',
+ 'hydrogen',
+ 'ionic-angular',
+ 'ionic-react',
+ 'jekyll',
+ 'middleman',
+ 'nextjs',
+ 'nuxtjs',
+ 'parcel',
+ 'polymer',
+ 'preact',
+ 'redwoodjs',
+ 'remix',
+ 'saber',
+ 'sanity',
+ 'sapper',
+ 'scully',
+ 'solidstart',
+ 'stencil',
+ 'storybook',
+ 'svelte',
+ 'sveltekit',
+ 'sveltekit-1',
+ 'umijs',
+ 'vite',
+ 'vitepress',
+ 'vue',
+ 'vuepress',
+ 'zola',
+])
+
+export function getFrameworkIconUrl(framework?: string | null): string | undefined {
+ if (!framework || !FRAMEWORK_ICON_SLUGS.has(framework)) return undefined
+ return `${BASE_PATH}/img/icons/frameworks/${framework}.svg`
+}
diff --git a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
index ccd04468424..08c9592f422 100644
--- a/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
+++ b/apps/studio/pages/integrations/vercel/[slug]/marketplace/choose-project.tsx
@@ -25,8 +25,8 @@ import { useIntegrationVercelConnectionsCreateMutation } from '@/data/integratio
import { useVercelProjectsQuery } from '@/data/integrations/integrations-vercel-projects-query'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { withAuth } from '@/hooks/misc/withAuth'
-import { BASE_PATH } from '@/lib/constants'
import { getErrorMessage } from '@/lib/get-error-message'
+import { getFrameworkIconUrl } from '@/lib/integration-utils'
import { hasVercelDeployButtonSignals } from '@/lib/integrations/vercel-install.utils'
import { buildStudioPageTitle } from '@/lib/page-title'
import { useProfileNameAndPicture } from '@/lib/profile'
@@ -83,16 +83,12 @@ const VercelChooseProjectPage: NextPageWithLayout = () => {
const getForeignProjectIcon = useCallback(
(_project: ForeignProject) => {
const project = vercelProjectsById[_project.id]
+ const frameworkIconUrl = getFrameworkIconUrl(project?.framework)
- return !project?.framework ? (
+ return !frameworkIconUrl ? (
vercelIcon
) : (
-
+
)
},
[vercelProjectsById]