From 593cbb987b6568a1c12852196b8f53246fb6fc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 19 Aug 2026 17:58:44 +0200 Subject: [PATCH 1/7] chore(deps): bump @appwrite.io/console to 56957ff Brings in the console variables that report per-provider VCS capabilities (_APP_VCS_PROVIDERS_WITH_REPOSITORY_CREATION and _APP_VCS_PROVIDERS_WITH_PUBLIC_REPOSITORIES). No breaking changes against the previous ed09983 pin. Co-Authored-By: Claude Fable 5 --- bun.lock | 5 ++--- package.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index 798c00a88a..ea221f77a8 100644 --- a/bun.lock +++ b/bun.lock @@ -1,12 +1,11 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@56957ff", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", @@ -130,7 +129,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@56957ff", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index fd0ebc590d..e54f0e19d7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@56957ff", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", From 8d69c12a1abc532406c15803835ace5282bfa0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 18 Aug 2026 16:48:21 +0200 Subject: [PATCH 2/7] feat: add Origin (Cursor) as a connectable git provider Adds Origin, Cursor's git hosting, alongside GitHub and Gitea: - IconOrigin.svelte with the Cursor cube mark, currentcolor like the other provider icons - connectOrigin() plus provider URL helpers in the git store, and a Connect to Origin button (second, after GitHub) in the shared connect empty state, gated on _APP_VCS_PROVIDERS containing 'origin' - Provider-aware settings page: Origin icon and cursor.com/codebase links for installations, per-row Configure follows the row's provider instead of always GitHub, unknown providers fall back to a git icon - Repository cards, git-deployment modals and the deployment source popover no longer assume GitHub for icons and repository links Co-Authored-By: Claude Fable 5 --- src/lib/components/git/IconOrigin.svelte | 13 ++++++++++ src/lib/components/git/connectGit.svelte | 14 +++++++++- .../components/git/deploymentSource.svelte | 21 ++++++++++++--- src/lib/components/git/repositoryCard.svelte | 12 +++++++-- src/lib/stores/git.ts | 26 ++++++++++++++++++- .../(modals)/createGit.svelte | 11 ++++++-- .../settings/updateInstallations.svelte | 18 +++++++++---- .../createGitDeploymentModal.svelte | 11 ++++++-- 8 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 src/lib/components/git/IconOrigin.svelte diff --git a/src/lib/components/git/IconOrigin.svelte b/src/lib/components/git/IconOrigin.svelte new file mode 100644 index 0000000000..184073d564 --- /dev/null +++ b/src/lib/components/git/IconOrigin.svelte @@ -0,0 +1,13 @@ + + + + diff --git a/src/lib/components/git/connectGit.svelte b/src/lib/components/git/connectGit.svelte index 6473f5f6d1..6f856561a1 100644 --- a/src/lib/components/git/connectGit.svelte +++ b/src/lib/components/git/connectGit.svelte @@ -1,11 +1,12 @@ @@ -49,6 +52,15 @@ Connect to GitHub + {#if isOriginEnabled} + + {/if} {#if isGiteaEnabled} (showInstallationDropdown[i] = false)}> Configure @@ -192,7 +200,7 @@ Connect to GitHub diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte index 6e98419d04..55af1b71b0 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte @@ -10,6 +10,8 @@ import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { installation, repository } from '$lib/stores/vcs'; + import { getProviderRepositoryUrl } from '$lib/stores/git'; + import IconOrigin from '$lib/components/git/IconOrigin.svelte'; import { Adapter, BuildRuntime, @@ -160,10 +162,15 @@ alignItems="center" gap="xs"> - + + href={getProviderRepositoryUrl( + $installation?.provider ?? 'github', + $repository?.organization, + $repository?.name + )}> {$repository?.organization}/{$repository?.name} From 1ad9dd36b14b0d8c559f9b033d065041eddf2dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 18 Aug 2026 17:58:28 +0200 Subject: [PATCH 3/7] feat: choose between git providers across the console Builds on the Origin groundwork with a central provider registry in the git store (icons, labels, provider URLs, installation-settings URLs and enablement gates), the same shape the new console uses. Everything that assumed GitHub now reads from it: - Connect empty states (shared connectGit, settings, both template wizards) render one button per enabled provider - Settings' Add installation becomes a provider chooser when more than one provider is enabled, and each row's Configure follows the row's provider - The repository picker labels installations with their provider icon and offers per-provider 'Add installation' entries - 'Missing a repository?' permission links follow the installation's provider (GitHub installation settings vs Cursor's app settings) - Wizard asides and repository cards show the connected provider's icon - The create-repository modal explains that Origin apps cannot create repositories and points at cursor.com instead Co-Authored-By: Claude Fable 5 --- src/lib/components/git/connectGit.svelte | 36 ++------ .../components/git/connectRepoModal.svelte | 54 +++++++++-- src/lib/components/git/repositories.svelte | 31 +++++-- src/lib/stores/git.ts | 84 +++++++++++++++-- .../create-function/(components)/aside.svelte | 9 +- .../functions/create-function/+page.svelte | 7 +- .../repository-[repository]/repoCard.svelte | 7 +- .../template-[template]/+page.svelte | 18 ++-- .../template-[template]/repoCard.svelte | 6 +- .../settings/updateInstallations.svelte | 92 +++++++++++-------- .../sites/create-site/aside.svelte | 9 +- .../create-site/repositories/+page.svelte | 16 ++-- .../repository-[repository]/+page.svelte | 6 +- .../template-[template]/+page.svelte | 17 +++- 14 files changed, 274 insertions(+), 118 deletions(-) diff --git a/src/lib/components/git/connectGit.svelte b/src/lib/components/git/connectGit.svelte index 6f856561a1..823b3929e7 100644 --- a/src/lib/components/git/connectGit.svelte +++ b/src/lib/components/git/connectGit.svelte @@ -1,12 +1,9 @@ @@ -45,31 +39,15 @@ description="Add an installation to connect repositories"> - - {#if isOriginEnabled} - - {/if} - {#if isGiteaEnabled} + {#each providers as provider (provider.id)} - {/if} + {/each} diff --git a/src/lib/components/git/connectRepoModal.svelte b/src/lib/components/git/connectRepoModal.svelte index faca63d917..6ccc1d204f 100644 --- a/src/lib/components/git/connectRepoModal.svelte +++ b/src/lib/components/git/connectRepoModal.svelte @@ -12,8 +12,10 @@ import { addNotification } from '$lib/stores/notifications'; import { Click, trackEvent } from '$lib/actions/analytics'; import RepositoryBehaviour from '$lib/components/git/repositoryBehaviour.svelte'; + import { Alert } from '@appwrite.io/pink-svelte'; import { page } from '$app/state'; - import { connectGitHub } from '$lib/stores/git'; + + import { connectVcsProvider, getVcsProvider } from '$lib/stores/git'; let { show = $bindable(false), @@ -37,6 +39,17 @@ let selectedInstallationId = $state(''); let selectedRepository = $state(''); let installations = $state({ installations: [], total: 0 }); + const selectedProvider = $derived( + getVcsProvider( + installations?.installations?.find((entry) => entry.$id === selectedInstallationId) + ?.provider ?? + $installation?.provider ?? + 'github' + ) + ); + // Origin's partner API has no repository creation for apps; repositories + // made on cursor.com appear in the picker automatically. + const creationUnsupported = $derived(selectedProvider.id === 'origin'); let error = $state(''); onMount(async () => { @@ -105,6 +118,14 @@ bind:repositoryPrivate bind:selectedInstallationId {installations} /> + {#if creationUnsupported} + + Origin does not allow apps to create repositories. Create it in the + Codebase section on cursor.com + and it will appear in the repository list here. + + {/if} {:else} {#if repositoryBehaviour === 'existing'} - - - Missing a repository? check your permissions - - + {#if $installation?.provider === 'origin'} + + + Missing a repository? check your repository access + + + {:else} + + + Missing a repository? check your permissions + + + {/if} {:else if repositoryBehaviour === 'new'} - {/if} diff --git a/src/lib/components/git/repositories.svelte b/src/lib/components/git/repositories.svelte index df2c800501..6e6181dadd 100644 --- a/src/lib/components/git/repositories.svelte +++ b/src/lib/components/git/repositories.svelte @@ -19,7 +19,8 @@ import SvgIcon from '../svgIcon.svelte'; import { Query, VCSDetectionType, type Models } from '@appwrite.io/console'; import { getFrameworkIcon } from '$lib/stores/sites'; - import { connectGitHub } from '$lib/stores/git'; + import { connectVcsProvider, enabledVcsProviders, getVcsProvider } from '$lib/stores/git'; + import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { addNotification } from '$lib/stores/notifications'; import { page } from '$app/state'; import Card from '../card.svelte'; @@ -27,6 +28,11 @@ import { onMount, untrack, onDestroy } from 'svelte'; import { debounce } from '$lib/helpers/debounce'; + // Not in the SDK's generated types yet -- server already returns it. + const vcsProviders = enabledVcsProviders( + ($regionalConsoleVariables as { _APP_VCS_PROVIDERS?: string[] })?._APP_VCS_PROVIDERS + ); + let { action = $bindable('select'), selectedRepository = $bindable(undefined), @@ -176,18 +182,27 @@ ...installationsMap.map((entry) => { return { label: entry.organization, + leadingIcon: getVcsProvider(entry.provider).icon, value: entry.$id }; }), - { - label: 'Add installation', - leadingIcon: IconPlus, - value: 'new' - } + ...vcsProviders.map((provider) => { + return { + label: + vcsProviders.length > 1 + ? `Add ${provider.label} installation` + : 'Add installation', + leadingIcon: IconPlus, + value: `new-${provider.id}` + }; + }) ]} on:change={() => { - if (selectedInstallation === 'new') { - window.location.href = connectGitHub(callbackState).toString(); + if (selectedInstallation?.startsWith('new-')) { + window.location.href = connectVcsProvider( + selectedInstallation.slice('new-'.length), + callbackState + ).toString(); } search = ''; installation.set( diff --git a/src/lib/stores/git.ts b/src/lib/stores/git.ts index 580c181a41..00785a6501 100644 --- a/src/lib/stores/git.ts +++ b/src/lib/stores/git.ts @@ -1,6 +1,79 @@ import { page } from '$app/state'; +import type { ComponentType } from 'svelte'; +import { IconGithub } from '@appwrite.io/pink-icons-svelte'; +import IconGitea from '$lib/components/git/IconGitea.svelte'; +import IconOrigin from '$lib/components/git/IconOrigin.svelte'; +import { isSelfHosted } from '$lib/system'; import { getApiEndpoint } from './sdk'; +export type VcsProviderId = 'github' | 'origin' | 'gitea'; + +export interface VcsProviderMeta { + id: VcsProviderId; + label: string; + icon: ComponentType; + /** Web URL of an owner/organization on the provider, or '' when the host is not knowable client-side. */ + organizationUrl: (organization: string) => string; + /** Where the user manages the installation's repository access, or null when the provider has no such page. */ + installationSettingsUrl: (providerInstallationId: string) => string | null; + /** GitHub predates the _APP_VCS_PROVIDERS variable, so it stays on unconditionally. */ + alwaysEnabled?: boolean; + /** Gitea is a self-hosted-only feature, not offered on Appwrite Cloud. */ + selfHostedOnly?: boolean; +} + +/** + * Single source of truth for the console's git providers - icons, labels, + * provider URLs and the authorize redirects. Key order is display order: + * GitHub first, Origin second, then the rest. + */ +export const VCS_PROVIDERS: Record = { + github: { + id: 'github', + label: 'GitHub', + icon: IconGithub, + organizationUrl: (organization) => `https://github.com/${organization}`, + installationSettingsUrl: (providerInstallationId) => + `https://github.com/settings/installations/${providerInstallationId}`, + alwaysEnabled: true + }, + origin: { + id: 'origin', + label: 'Origin', + icon: IconOrigin as unknown as ComponentType, + // Origin repositories are browsed in the Codebase section of cursor.com + organizationUrl: (organization) => `https://cursor.com/codebase/${organization}`, + installationSettingsUrl: () => 'https://cursor.com/codebase/settings/apps' + }, + gitea: { + id: 'gitea', + label: 'Gitea', + icon: IconGitea as unknown as ComponentType, + // The Gitea host is per-deployment server configuration the client does not know + organizationUrl: () => '', + installationSettingsUrl: () => null, + selfHostedOnly: true + } +}; + +/** Provider metadata with a GitHub fallback for unknown ids (cosmetic use only). */ +export function getVcsProvider(provider?: string): VcsProviderMeta { + return VCS_PROVIDERS[provider as VcsProviderId] ?? VCS_PROVIDERS.github; +} + +/** + * Providers the connect UI should offer, honoring the server's + * _APP_VCS_PROVIDERS list and each provider's own gates. + */ +export function enabledVcsProviders(vcsProviders: string[] | undefined): VcsProviderMeta[] { + return Object.values(VCS_PROVIDERS).filter( + (provider) => + provider.alwaysEnabled || + ((vcsProviders?.includes(provider.id) ?? false) && + (!provider.selfHostedOnly || isSelfHosted)) + ); +} + export function connectVcsProvider(provider: string, callbackState: Record = null) { const redirect = new URL(page.url); if (callbackState) { @@ -29,16 +102,11 @@ export function connectOrigin(callbackState: Record = null) { } /** - * Browser URL of an owner/organization on the provider. Origin repositories - * are browsed in the Codebase section of cursor.com. + * Browser URL of an owner/organization on the provider. Unknown providers + * fall back to GitHub, matching the console's historical behavior. */ export function getProviderOrganizationUrl(provider: string, organization: string): string { - switch (provider) { - case 'origin': - return `https://cursor.com/codebase/${organization}`; - default: - return `https://github.com/${organization}`; - } + return getVcsProvider(provider).organizationUrl(organization); } /** diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/aside.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/aside.svelte index 4acf3d41a7..2b473c61a7 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/aside.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/aside.svelte @@ -1,7 +1,9 @@ @@ -38,7 +35,7 @@ title="No installation was added to the project yet" description="Add an installation to connect repositories"> - + {#each providers as provider (provider.id)} - {/if} diff --git a/src/lib/components/git/newRepository.svelte b/src/lib/components/git/newRepository.svelte index ffbb7cf5ce..ea4c0acbb2 100644 --- a/src/lib/components/git/newRepository.svelte +++ b/src/lib/components/git/newRepository.svelte @@ -1,14 +1,55 @@ @@ -18,14 +59,14 @@ id="installation" label="Git organization" disabled={disableFields} - options={installations.installations.map((entry) => { + options={creatableInstallations.map((entry) => { return { label: entry.organization, value: entry.$id }; })} on:change={() => { - $installation = installations.installations.find( + $installation = creatableInstallations.find( (entry) => entry.$id === selectedInstallationId ); }} @@ -38,9 +79,11 @@ disabled={disableFields} bind:value={repositoryName} /> - + {#if supportsPublic} + + {/if} diff --git a/src/lib/components/git/repositories.svelte b/src/lib/components/git/repositories.svelte index 6e6181dadd..619e9ee953 100644 --- a/src/lib/components/git/repositories.svelte +++ b/src/lib/components/git/repositories.svelte @@ -28,10 +28,7 @@ import { onMount, untrack, onDestroy } from 'svelte'; import { debounce } from '$lib/helpers/debounce'; - // Not in the SDK's generated types yet -- server already returns it. - const vcsProviders = enabledVcsProviders( - ($regionalConsoleVariables as { _APP_VCS_PROVIDERS?: string[] })?._APP_VCS_PROVIDERS - ); + const vcsProviders = enabledVcsProviders($regionalConsoleVariables?._APP_VCS_PROVIDERS); let { action = $bindable('select'), diff --git a/src/lib/stores/git.ts b/src/lib/stores/git.ts index 00785a6501..30851d99e0 100644 --- a/src/lib/stores/git.ts +++ b/src/lib/stores/git.ts @@ -1,4 +1,5 @@ import { page } from '$app/state'; +import type { Models } from '@appwrite.io/console'; import type { ComponentType } from 'svelte'; import { IconGithub } from '@appwrite.io/pink-icons-svelte'; import IconGitea from '$lib/components/git/IconGitea.svelte'; @@ -42,7 +43,7 @@ export const VCS_PROVIDERS: Record = { label: 'Origin', icon: IconOrigin as unknown as ComponentType, // Origin repositories are browsed in the Codebase section of cursor.com - organizationUrl: (organization) => `https://cursor.com/codebase/${organization}`, + organizationUrl: () => 'https://cursor.com/codebase', installationSettingsUrl: () => 'https://cursor.com/codebase/settings/apps' }, gitea: { @@ -74,6 +75,29 @@ export function enabledVcsProviders(vcsProviders: string[] | undefined): VcsProv ); } +/** + * Whether the console can create repositories on the provider. Servers that + * predate _APP_VCS_PROVIDERS_WITH_REPOSITORY_CREATION omit the list; keep the + * console's historical behavior (creation offered) for them. + */ +export function supportsRepositoryCreation( + provider: string, + variables: Models.ConsoleVariables +): boolean { + return variables?._APP_VCS_PROVIDERS_WITH_REPOSITORY_CREATION?.includes(provider) ?? true; +} + +/** + * Whether the provider can host public repositories. When it cannot, every + * repository is private and the public/private choice is meaningless. + */ +export function supportsPublicRepositories( + provider: string, + variables: Models.ConsoleVariables +): boolean { + return variables?._APP_VCS_PROVIDERS_WITH_PUBLIC_REPOSITORIES?.includes(provider) ?? true; +} + export function connectVcsProvider(provider: string, callbackState: Record = null) { const redirect = new URL(page.url); if (callbackState) { diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte index 8746d9382b..cd65ce6326 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte @@ -31,7 +31,11 @@ import Aside from '../(components)/aside.svelte'; import { iconPath } from '$lib/stores/app'; import Permissions from './permissions.svelte'; - import { connectVcsProvider, enabledVcsProviders } from '$lib/stores/git'; + import { + connectVcsProvider, + enabledVcsProviders, + supportsRepositoryCreation + } from '$lib/stores/git'; import RepoCard from './repoCard.svelte'; import { Dependencies } from '$lib/constants'; import { getIconFromRuntime } from '$lib/stores/runtimes'; @@ -41,6 +45,12 @@ let { data }: { data: PageData } = $props(); + const canCreateRepository = $derived( + data.installations.installations.some((entry) => + supportsRepositoryCreation(entry.provider, $regionalConsoleVariables) + ) + ); + const specificationOptions = $derived( (data.specificationsList?.specifications ?? []).map((size) => ({ label: @@ -239,6 +249,12 @@ } } + $effect(() => { + if (!canCreateRepository) { + repositoryBehaviour = 'existing'; + } + }); + $effect(() => { if (repositoryBehaviour === 'new') { selectedInstallationId ??= $installation?.$id; @@ -312,7 +328,9 @@ {#if !!data?.installations?.total}
- + {#if canCreateRepository} + + {/if} {#if repositoryBehaviour === 'new'} - - {#each enabledVcsProviders(($regionalConsoleVariables as { _APP_VCS_PROVIDERS?: string[] })?._APP_VCS_PROVIDERS) as provider (provider.id)} + + {#each enabledVcsProviders($regionalConsoleVariables?._APP_VCS_PROVIDERS) as provider (provider.id)}