From 0f3aabaa62cd415a9a9c986c650926a2e2e1b443 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 02:45:50 +0530 Subject: [PATCH 1/9] add branch deployments to one-click deployments --- src/lib/helpers/github.ts | 17 ++++ .../create-function/deploy/+page.svelte | 94 ++++++++++++++++--- .../sites/create-site/deploy/+page.svelte | 77 +++++++++++++-- 3 files changed, 165 insertions(+), 23 deletions(-) diff --git a/src/lib/helpers/github.ts b/src/lib/helpers/github.ts index b173d6d221..e05f036bb8 100644 --- a/src/lib/helpers/github.ts +++ b/src/lib/helpers/github.ts @@ -54,3 +54,20 @@ export async function getDefaultBranch(owner: string, name: string): Promise { + try { + const branchesResponse = await fetch( + `https://api.github.com/repos/${owner}/${name}/branches` + ); + if (!branchesResponse.ok) { + return null; + } + + const branches = await branchesResponse.json(); + return branches.map((branch) => branch.name); + } catch (error) { + console.error('Failed to fetch branches from GitHub:', error); + return null; + } +} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte index 6166daa519..f93124bd18 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte @@ -17,7 +17,7 @@ import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { iconPath } from '$lib/stores/app'; import type { PageData } from './$types'; - import { getLatestTag } from '$lib/helpers/github'; + import { getDefaultBranch, getBranches } from '$lib/helpers/github'; import { writable } from 'svelte/store'; import Link from '$lib/elements/link.svelte'; @@ -42,8 +42,9 @@ let selectedScopes = $state([]); let rootDir = $state(data.repository?.rootDirectory); let variables = $state>([]); - - let latestTag = $state(null); + let branches = $state([]); + let selectedBranch = $state(''); + let loadingBranches = $state(false); const specificationOptions = $derived( data.specificationsList?.specifications?.map((size) => ({ @@ -63,7 +64,7 @@ })) || [] ); - onMount(() => { + onMount(async () => { const runtimeParam = data.runtime || page.url.searchParams.get('runtime') || 'node-18.0'; runtime = runtimeParam as Runtime; @@ -80,19 +81,61 @@ variables = data.envKeys.map((key) => ({ key, value: '', secret: false })); } - getLatestTag(data.repository.owner, data.repository.name).then( - (tagName) => (latestTag = tagName) - ); + // Load branches and set default branch + if (data.repository?.owner && data.repository?.name) { + loadingBranches = true; + try { + const [branchList, defaultBranch] = await Promise.all([ + getBranches(data.repository.owner, data.repository.name), + getDefaultBranch(data.repository.owner, data.repository.name) + ]); + + if (branchList && branchList.length > 0) { + branches = branchList; + // Pre-select default branch, or first branch if default not found + selectedBranch = + defaultBranch && branchList.includes(defaultBranch) + ? defaultBranch + : branchList[0]; + } else { + // Branch list is empty or null + addNotification({ + type: 'error', + message: + 'Failed to load branches from repository. Please check the repository URL or try again.' + }); + } + } catch (error) { + console.error('Failed to load branches:', error); + addNotification({ + type: 'error', + message: + 'Failed to load branches from repository. Please check the repository URL or try again.' + }); + } finally { + loadingBranches = false; + } + } else { + // Repository info is missing + addNotification({ + type: 'error', + message: 'Repository information is missing. Please check the repository URL.' + }); + } }); async function create() { + if (!selectedBranch || branches.length === 0) { + addNotification({ + type: 'error', + message: 'Please wait for branches to load or check the repository URL.' + }); + return; + } + $isSubmitting = true; try { - if (!latestTag) { - latestTag = await getLatestTag(data.repository.owner, data.repository.name); - } - // Create function with configuration const func = await sdk .forProject(page.params.region, page.params.project) @@ -126,7 +169,7 @@ await Promise.all(promises); - // Create deployment from GitHub repository using the latest tag + // Create deployment from GitHub repository using the selected branch await sdk .forProject(page.params.region, page.params.project) .functions.createTemplateDeployment({ @@ -134,8 +177,8 @@ repository: data.repository.name, owner: data.repository.owner, rootDirectory: rootDir || '.', - type: Type.Tag, - reference: latestTag ?? '1.0.0', + type: Type.Branch, + reference: selectedBranch, activate: true }); @@ -220,6 +263,22 @@ +
+ + ({ + value: branch, + label: branch + }))} /> + +
+
+ disabled={!name || + !runtime || + !specification || + !selectedBranch || + branches.length === 0 || + $isSubmitting}> Deploy function diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte index 5dc5b86c37..cd1df97c95 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte @@ -19,7 +19,7 @@ import { iconPath } from '$lib/stores/app'; import type { PageData } from './$types'; import { writable } from 'svelte/store'; - import { getLatestTag } from '$lib/helpers/github'; + import { getDefaultBranch, getBranches } from '$lib/helpers/github'; import Link from '$lib/elements/link.svelte'; let { @@ -44,6 +44,9 @@ let domainIsValid = $state(false); let framework = $state(Framework.Nextjs); let variables = $state>([]); + let branches = $state([]); + let selectedBranch = $state(''); + let loadingBranches = $state(false); // Track if we have custom commands from URL let hasCustomCommands = $state(false); @@ -86,7 +89,7 @@ } }); - onMount(() => { + onMount(async () => { const preset = page.url.searchParams.get('preset') || 'nextjs'; // Map preset string to Framework enum @@ -117,6 +120,48 @@ if (data.envKeys.length > 0) { variables = data.envKeys.map((key) => ({ key, value: '', secret: false })); } + + // Load branches and set default branch + if (data.repository?.owner && data.repository?.name) { + loadingBranches = true; + try { + const [branchList, defaultBranch] = await Promise.all([ + getBranches(data.repository.owner, data.repository.name), + getDefaultBranch(data.repository.owner, data.repository.name) + ]); + + if (branchList && branchList.length > 0) { + branches = branchList; + // Pre-select default branch, or first branch if default not found + selectedBranch = + defaultBranch && branchList.includes(defaultBranch) + ? defaultBranch + : branchList[0]; + } else { + // Branch list is empty or null + addNotification({ + type: 'error', + message: + 'Failed to load branches from repository. Please check the repository URL or try again.' + }); + } + } catch (error) { + console.error('Failed to load branches:', error); + addNotification({ + type: 'error', + message: + 'Failed to load branches from repository. Please check the repository URL or try again.' + }); + } finally { + loadingBranches = false; + } + } else { + // Repository info is missing + addNotification({ + type: 'error', + message: 'Repository information is missing. Please check the repository URL.' + }); + } }); async function create() { @@ -128,6 +173,14 @@ return; } + if (!selectedBranch || branches.length === 0) { + addNotification({ + type: 'error', + message: 'Please wait for branches to load or check the repository URL.' + }); + return; + } + $isSubmitting = true; try { @@ -161,10 +214,7 @@ ); await Promise.all(promises); - // Fetch latest tag from GitHub - const latestTag = await getLatestTag(data.repository.owner, data.repository.name); - - // Create deployment from GitHub repository using the latest tag + // Create deployment from GitHub repository using the selected branch const deployment = await sdk .forProject(page.params.region, page.params.project) .sites.createTemplateDeployment({ @@ -172,8 +222,8 @@ repository: data.repository.name, owner: data.repository.owner, rootDirectory: rootDir || '.', - type: Type.Tag, - reference: latestTag ?? '1.0.0', + type: Type.Branch, + reference: selectedBranch, activate: true }); @@ -252,6 +302,17 @@
+ ({ + value: branch, + label: branch + }))} />
From d5436e1e2d813eb7c967fa0506222f1e63e76eb2 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 17:28:30 +0530 Subject: [PATCH 2/9] fix region mismatch --- src/routes/(public)/functions/deploy/+page.svelte | 2 +- src/routes/(public)/sites/deploy/+page.svelte | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index 4b353d17ba..382d760945 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -93,7 +93,7 @@ function buildDeployUrl(project: Models.Project) { let url: URL; - const projectRegion = isCloud ? region : 'default'; + const projectRegion = isCloud ? (project.region ?? 'default') : 'default'; url = new URL( `${base}/project-${projectRegion}-${project.$id}/functions/create-function/deploy`, diff --git a/src/routes/(public)/sites/deploy/+page.svelte b/src/routes/(public)/sites/deploy/+page.svelte index ef6000fbb6..c572e37c6b 100644 --- a/src/routes/(public)/sites/deploy/+page.svelte +++ b/src/routes/(public)/sites/deploy/+page.svelte @@ -97,8 +97,8 @@ } function buildDeployUrl(project: Models.Project) { - // Use the selected region or default to 'default' if not available - const projectRegion = isCloud ? region : 'default'; + // Use the project's region when available; fall back to default for self-hosted + const projectRegion = isCloud ? (project.region ?? 'default') : 'default'; let url: URL; if (data.deploymentData.type === 'template') { From 2bb3f6602345eb5fed097b456d20a130c36d93df Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 20:01:04 +0530 Subject: [PATCH 3/9] add branch search param, with branch validation, fallback to default branch --- src/lib/helpers/github.ts | 16 +++++++++++ .../create-function/deploy/+page.svelte | 28 +++++++++++++------ .../sites/create-site/deploy/+page.svelte | 28 +++++++++++++------ .../(public)/functions/deploy/+page.svelte | 2 ++ src/routes/(public)/sites/deploy/+page.svelte | 2 ++ 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/lib/helpers/github.ts b/src/lib/helpers/github.ts index e05f036bb8..287fb35502 100644 --- a/src/lib/helpers/github.ts +++ b/src/lib/helpers/github.ts @@ -71,3 +71,19 @@ export async function getBranches(owner: string, name: string): Promise { + try { + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}` + ); + return response.ok; + } catch (error) { + console.error('Failed to validate branch from GitHub:', error); + return false; + } +} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte index f93124bd18..323dcc1e73 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte @@ -17,7 +17,7 @@ import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { iconPath } from '$lib/stores/app'; import type { PageData } from './$types'; - import { getDefaultBranch, getBranches } from '$lib/helpers/github'; + import { getDefaultBranch, getBranches, validateBranch } from '$lib/helpers/github'; import { writable } from 'svelte/store'; import Link from '$lib/elements/link.svelte'; @@ -85,18 +85,30 @@ if (data.repository?.owner && data.repository?.name) { loadingBranches = true; try { - const [branchList, defaultBranch] = await Promise.all([ + // Check for branch param from URL + const branchParam = page.url.searchParams.get('branch'); + + const [branchList, defaultBranch, isBranchValid] = await Promise.all([ getBranches(data.repository.owner, data.repository.name), - getDefaultBranch(data.repository.owner, data.repository.name) + getDefaultBranch(data.repository.owner, data.repository.name), + branchParam + ? validateBranch(data.repository.owner, data.repository.name, branchParam) + : Promise.resolve(false) ]); if (branchList && branchList.length > 0) { branches = branchList; - // Pre-select default branch, or first branch if default not found - selectedBranch = - defaultBranch && branchList.includes(defaultBranch) - ? defaultBranch - : branchList[0]; + + if (branchParam && isBranchValid) { + // Use the provided branch if it's valid + selectedBranch = branchParam; + } else { + // Fall back to default branch, or first branch if default not found + selectedBranch = + defaultBranch && branchList.includes(defaultBranch) + ? defaultBranch + : branchList[0]; + } } else { // Branch list is empty or null addNotification({ diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte index cd1df97c95..089081efad 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte @@ -19,7 +19,7 @@ import { iconPath } from '$lib/stores/app'; import type { PageData } from './$types'; import { writable } from 'svelte/store'; - import { getDefaultBranch, getBranches } from '$lib/helpers/github'; + import { getDefaultBranch, getBranches, validateBranch } from '$lib/helpers/github'; import Link from '$lib/elements/link.svelte'; let { @@ -125,18 +125,30 @@ if (data.repository?.owner && data.repository?.name) { loadingBranches = true; try { - const [branchList, defaultBranch] = await Promise.all([ + // Check for branch param from URL + const branchParam = page.url.searchParams.get('branch'); + + const [branchList, defaultBranch, isBranchValid] = await Promise.all([ getBranches(data.repository.owner, data.repository.name), - getDefaultBranch(data.repository.owner, data.repository.name) + getDefaultBranch(data.repository.owner, data.repository.name), + branchParam + ? validateBranch(data.repository.owner, data.repository.name, branchParam) + : Promise.resolve(false) ]); if (branchList && branchList.length > 0) { branches = branchList; - // Pre-select default branch, or first branch if default not found - selectedBranch = - defaultBranch && branchList.includes(defaultBranch) - ? defaultBranch - : branchList[0]; + + if (branchParam && isBranchValid) { + // Use the provided branch if it's valid + selectedBranch = branchParam; + } else { + // Fall back to default branch, or first branch if default not found + selectedBranch = + defaultBranch && branchList.includes(defaultBranch) + ? defaultBranch + : branchList[0]; + } } else { // Branch list is empty or null addNotification({ diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index 382d760945..0e4d90c110 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -113,6 +113,7 @@ const install = currentUrl.searchParams.get('install'); const build = currentUrl.searchParams.get('build'); const rootDir = currentUrl.searchParams.get('rootDir'); + const branch = currentUrl.searchParams.get('branch'); if (entrypoint) url.searchParams.set('entrypoint', entrypoint); if (install) url.searchParams.set('install', install); @@ -122,6 +123,7 @@ 'rootDir', rootDir || data.deploymentData.repository.rootDirectory ); + if (branch) url.searchParams.set('branch', branch); if (data.envKeys.length > 0) { url.searchParams.set('env', data.envKeys.join(',')); diff --git a/src/routes/(public)/sites/deploy/+page.svelte b/src/routes/(public)/sites/deploy/+page.svelte index c572e37c6b..1bd38f2226 100644 --- a/src/routes/(public)/sites/deploy/+page.svelte +++ b/src/routes/(public)/sites/deploy/+page.svelte @@ -120,11 +120,13 @@ const install = currentUrl.searchParams.get('install'); const build = currentUrl.searchParams.get('build'); const output = currentUrl.searchParams.get('output'); + const branch = currentUrl.searchParams.get('branch'); if (preset) url.searchParams.set('preset', preset); if (install) url.searchParams.set('install', install); if (build) url.searchParams.set('build', build); if (output) url.searchParams.set('output', output); + if (branch) url.searchParams.set('branch', branch); } if (data.envKeys.length > 0) { From 5aae0154d99f94a92435ef18c014014c94cb8ad5 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 20:16:52 +0530 Subject: [PATCH 4/9] fetch branch from url if provided --- src/lib/helpers/github.ts | 5 +++++ src/routes/(public)/functions/deploy/+page.svelte | 4 +++- src/routes/(public)/functions/deploy/+page.ts | 7 ++++++- src/routes/(public)/sites/deploy/+page.svelte | 4 +++- src/routes/(public)/sites/deploy/+page.ts | 10 +++++++--- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lib/helpers/github.ts b/src/lib/helpers/github.ts index 287fb35502..41f8523fc1 100644 --- a/src/lib/helpers/github.ts +++ b/src/lib/helpers/github.ts @@ -3,6 +3,11 @@ export function getNestedRootDirectory(repository: string): string | null { return match ? match[1] : null; } +export function getBranchFromUrl(repository: string): string | null { + const match = repository.match(/\/tree\/([^/]+)/); + return match ? decodeURIComponent(match[1]) : null; +} + export function getRepositoryInfo( repository: string ): { owner: string; name: string; url: string } | null { diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index 0e4d90c110..e7b8f2ecac 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -113,7 +113,9 @@ const install = currentUrl.searchParams.get('install'); const build = currentUrl.searchParams.get('build'); const rootDir = currentUrl.searchParams.get('rootDir'); - const branch = currentUrl.searchParams.get('branch'); + // Branch can come from explicit param or extracted from repo URL (e.g., /tree/branch) + const branch = + currentUrl.searchParams.get('branch') || data.deploymentData.repository.branch; if (entrypoint) url.searchParams.set('entrypoint', entrypoint); if (install) url.searchParams.set('install', install); diff --git a/src/routes/(public)/functions/deploy/+page.ts b/src/routes/(public)/functions/deploy/+page.ts index 669756550f..68add2188a 100644 --- a/src/routes/(public)/functions/deploy/+page.ts +++ b/src/routes/(public)/functions/deploy/+page.ts @@ -7,7 +7,7 @@ import { ID, type Models, Query } from '@appwrite.io/console'; import type { OrganizationList } from '$lib/stores/organization'; import { redirectTo } from '$routes/store'; import type { PageLoad } from './$types'; -import { getRepositoryInfo } from '$lib/helpers/github'; +import { getRepositoryInfo, getBranchFromUrl } from '$lib/helpers/github'; export const load: PageLoad = async ({ parent, url }) => { const { account } = await parent(); @@ -38,6 +38,7 @@ export const load: PageLoad = async ({ parent, url }) => { owner?: string; name?: string; rootDirectory?: string; + branch?: string; }; runtime?: string; name?: string; @@ -59,9 +60,13 @@ export const load: PageLoad = async ({ parent, url }) => { redirect(302, base + '/'); } + // Extract branch from URL if present (e.g., github.com/owner/repo/tree/branch) + const branchFromUrl = getBranchFromUrl(repository); + deploymentData.name = name || info.name; deploymentData.repository.name = info.name; deploymentData.repository.owner = info.owner; + deploymentData.repository.branch = branchFromUrl; // Get organizations let organizations: Models.TeamList> | OrganizationList | undefined; diff --git a/src/routes/(public)/sites/deploy/+page.svelte b/src/routes/(public)/sites/deploy/+page.svelte index 1bd38f2226..314db725f6 100644 --- a/src/routes/(public)/sites/deploy/+page.svelte +++ b/src/routes/(public)/sites/deploy/+page.svelte @@ -120,7 +120,9 @@ const install = currentUrl.searchParams.get('install'); const build = currentUrl.searchParams.get('build'); const output = currentUrl.searchParams.get('output'); - const branch = currentUrl.searchParams.get('branch'); + // Branch can come from explicit param or extracted from repo URL (e.g., /tree/branch) + const branch = + currentUrl.searchParams.get('branch') || data.deploymentData.repository.branch; if (preset) url.searchParams.set('preset', preset); if (install) url.searchParams.set('install', install); diff --git a/src/routes/(public)/sites/deploy/+page.ts b/src/routes/(public)/sites/deploy/+page.ts index 666c809e93..d21665ea62 100644 --- a/src/routes/(public)/sites/deploy/+page.ts +++ b/src/routes/(public)/sites/deploy/+page.ts @@ -7,7 +7,7 @@ import { ID, type Models, Query } from '@appwrite.io/console'; import type { OrganizationList } from '$lib/stores/organization'; import { redirectTo } from '$routes/store'; import type { PageLoad } from './$types'; -import { getRepositoryInfo } from '$lib/helpers/github'; +import { getRepositoryInfo, getBranchFromUrl } from '$lib/helpers/github'; export const load: PageLoad = async ({ parent, url }) => { const { account } = await parent(); @@ -40,7 +40,7 @@ export const load: PageLoad = async ({ parent, url }) => { let deploymentData: { type: 'template' | 'repo'; template?: Models.TemplateSite; - repository?: { url: string; owner: string; name: string }; + repository?: { url: string; owner: string; name: string; branch?: string }; screenshot?: string; name?: string; tagline?: string; @@ -67,12 +67,16 @@ export const load: PageLoad = async ({ parent, url }) => { redirect(302, base + '/'); } + // Extract branch from URL if present (e.g., github.com/owner/repo/tree/branch) + const branchFromUrl = getBranchFromUrl(repository); + deploymentData = { type: 'repo', repository: { url: repository, name: info.name, - owner: info.owner + owner: info.owner, + branch: branchFromUrl }, screenshot, name: name || info.name, From f5b6f9914213775a8d59ff2e9b516db17f29e93f Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 21:35:12 +0530 Subject: [PATCH 5/9] add env values support --- .../create-function/deploy/+page.svelte | 11 +++++++--- .../functions/create-function/deploy/+page.ts | 18 +++++++++++++-- .../sites/create-site/deploy/+page.svelte | 10 ++++++--- .../sites/create-site/deploy/+page.ts | 18 +++++++++++++-- .../(public)/functions/deploy/+page.svelte | 8 +++++-- src/routes/(public)/functions/deploy/+page.ts | 20 ++++++++++++++++- src/routes/(public)/sites/deploy/+page.svelte | 8 +++++-- src/routes/(public)/sites/deploy/+page.ts | 22 +++++++++++++++++-- 8 files changed, 98 insertions(+), 17 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte index 323dcc1e73..01725816dd 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte @@ -77,8 +77,13 @@ specification = specificationOptions[0].value; } - if (data.envKeys.length > 0) { - variables = data.envKeys.map((key) => ({ key, value: '', secret: false })); + // Initialize environment variables from query params (with prefilled values if provided) + if (data.envVars.length > 0) { + variables = data.envVars.map((env) => ({ + key: env.key, + value: env.value, + secret: false + })); } // Load branches and set default branch @@ -314,7 +319,7 @@
- {#if data.envKeys.length > 0} + {#if data.envVars.length > 0}
{#each variables as variable, i} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts index 8169ab7089..504371be37 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts @@ -19,7 +19,21 @@ export const load: PageLoad = async ({ url, params, parent }) => { } const envParam = url.searchParams.get('env'); - const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Parse env vars - supports KEY or KEY=value format + const envVars: Array<{ key: string; value: string }> = envParam + ? envParam.split(',').map((entry: string) => { + const trimmed = entry.trim(); + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) { + return { key: trimmed, value: '' }; + } + return { + key: trimmed.substring(0, eqIndex), + value: trimmed.substring(eqIndex + 1) + }; + }) + : []; const runtime = url.searchParams.get('runtime'); @@ -35,7 +49,7 @@ export const load: PageLoad = async ({ url, params, parent }) => { } return { - envKeys, + envVars, runtime, runtimesList, installations, diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte index 089081efad..2234fdff9c 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte @@ -116,9 +116,13 @@ } } - // Initialize environment variables from query params - if (data.envKeys.length > 0) { - variables = data.envKeys.map((key) => ({ key, value: '', secret: false })); + // Initialize environment variables from query params (with prefilled values if provided) + if (data.envVars.length > 0) { + variables = data.envVars.map((env) => ({ + key: env.key, + value: env.value, + secret: false + })); } // Load branches and set default branch diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts index 90f10807db..dc38b502f5 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts @@ -17,7 +17,21 @@ export const load: PageLoad = async ({ url, params }) => { } const envParam = url.searchParams.get('env'); - const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Parse env vars - supports KEY or KEY=value format + const envVars: Array<{ key: string; value: string }> = envParam + ? envParam.split(',').map((entry: string) => { + const trimmed = entry.trim(); + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) { + return { key: trimmed, value: '' }; + } + return { + key: trimmed.substring(0, eqIndex), + value: trimmed.substring(eqIndex + 1) + }; + }) + : []; const [frameworks, installations] = await Promise.all([ sdk.forProject(params.region, params.project).sites.listFrameworks(), @@ -28,7 +42,7 @@ export const load: PageLoad = async ({ url, params }) => { ]); return { - envKeys, + envVars, frameworks, installations, repository: { diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index e7b8f2ecac..4f2b5eeb87 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -127,8 +127,12 @@ ); if (branch) url.searchParams.set('branch', branch); - if (data.envKeys.length > 0) { - url.searchParams.set('env', data.envKeys.join(',')); + if (data.envVars.length > 0) { + // Preserve KEY=value format for prefilled values + url.searchParams.set( + 'env', + data.envVars.map((v) => (v.value ? `${v.key}=${v.value}` : v.key)).join(',') + ); } return url.toString(); diff --git a/src/routes/(public)/functions/deploy/+page.ts b/src/routes/(public)/functions/deploy/+page.ts index 68add2188a..e5fb0c95aa 100644 --- a/src/routes/(public)/functions/deploy/+page.ts +++ b/src/routes/(public)/functions/deploy/+page.ts @@ -29,7 +29,24 @@ export const load: PageLoad = async ({ parent, url }) => { // Get common parameters const name = url.searchParams.get('name'); const envParam = url.searchParams.get('env'); - const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Parse env vars - supports KEY or KEY=value format + const envVars: Array<{ key: string; value: string }> = envParam + ? envParam.split(',').map((entry: string) => { + const trimmed = entry.trim(); + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) { + return { key: trimmed, value: '' }; + } + return { + key: trimmed.substring(0, eqIndex), + value: trimmed.substring(eqIndex + 1) + }; + }) + : []; + + // Keep envKeys for backward compatibility + const envKeys = envVars.map((v) => v.key); const deploymentData: { type: 'repo'; @@ -111,6 +128,7 @@ export const load: PageLoad = async ({ parent, url }) => { organizations, deploymentData, envKeys, + envVars, runtimesList }; }; diff --git a/src/routes/(public)/sites/deploy/+page.svelte b/src/routes/(public)/sites/deploy/+page.svelte index 314db725f6..32d09c3702 100644 --- a/src/routes/(public)/sites/deploy/+page.svelte +++ b/src/routes/(public)/sites/deploy/+page.svelte @@ -131,8 +131,12 @@ if (branch) url.searchParams.set('branch', branch); } - if (data.envKeys.length > 0) { - url.searchParams.set('env', data.envKeys.join(',')); + if (data.envVars.length > 0) { + // Preserve KEY=value format for prefilled values + url.searchParams.set( + 'env', + data.envVars.map((v) => (v.value ? `${v.key}=${v.value}` : v.key)).join(',') + ); } return url.toString(); diff --git a/src/routes/(public)/sites/deploy/+page.ts b/src/routes/(public)/sites/deploy/+page.ts index d21665ea62..6eb713ec2a 100644 --- a/src/routes/(public)/sites/deploy/+page.ts +++ b/src/routes/(public)/sites/deploy/+page.ts @@ -35,7 +35,24 @@ export const load: PageLoad = async ({ parent, url }) => { const envParam = url.searchParams.get('env'); const tagline = url.searchParams.get('tagline'); const screenshot = url.searchParams.get('screenshot'); - const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Parse env vars - supports KEY or KEY=value format + const envVars: Array<{ key: string; value: string }> = envParam + ? envParam.split(',').map((entry: string) => { + const trimmed = entry.trim(); + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) { + return { key: trimmed, value: '' }; + } + return { + key: trimmed.substring(0, eqIndex), + value: trimmed.substring(eqIndex + 1) + }; + }) + : []; + + // Keep envKeys for backward compatibility (just the keys) + const envKeys = envVars.map((v) => v.key); let deploymentData: { type: 'template' | 'repo'; @@ -127,6 +144,7 @@ export const load: PageLoad = async ({ parent, url }) => { account, organizations, deploymentData, - envKeys + envKeys, + envVars }; }; From 20ca4070119ddca4ecacc43001d39f04660fd285 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 27 Nov 2025 22:20:15 +0530 Subject: [PATCH 6/9] added quick mode to skip wizard --- .../functions/create-function/deploy/+page.ts | 105 ++++++++++++++- .../sites/create-site/deploy/+page.ts | 121 +++++++++++++++++- .../(public)/functions/deploy/+page.svelte | 3 + src/routes/(public)/sites/deploy/+page.svelte | 3 + 4 files changed, 226 insertions(+), 6 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts index 504371be37..7b9f6ff25c 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.ts @@ -1,11 +1,22 @@ import { sdk } from '$lib/stores/sdk'; -import { redirect } from '@sveltejs/kit'; +import { redirect, isRedirect } from '@sveltejs/kit'; import { base } from '$app/paths'; import type { PageLoad } from './$types'; -import { getNestedRootDirectory, getRepositoryInfo } from '$lib/helpers/github'; +import { + getNestedRootDirectory, + getRepositoryInfo, + getDefaultBranch, + validateBranch +} from '$lib/helpers/github'; +import { ID, Runtime, Type } from '@appwrite.io/console'; export const load: PageLoad = async ({ url, params, parent }) => { - const { installations: vcsInstallations, runtimesList, specificationsList } = await parent(); + const { + installations: vcsInstallations, + runtimesList, + specificationsList, + regionalConsoleVariables + } = await parent(); const repository = url.searchParams.get('repo') || url.searchParams.get('repository'); @@ -37,6 +48,94 @@ export const load: PageLoad = async ({ url, params, parent }) => { const runtime = url.searchParams.get('runtime'); + // Quick mode - create function and redirect directly to function page + const quickMode = url.searchParams.get('quick') === 'true'; + + if (quickMode) { + try { + const runtimeParam = runtime || 'node-18.0'; + const selectedRuntime = runtimeParam as Runtime; + + const entrypoint = url.searchParams.get('entrypoint') || ''; + const installCommand = url.searchParams.get('install') || ''; + const rootDir = + getNestedRootDirectory(repository) || url.searchParams.get('rootDir') || '.'; + + // Get branch - validate provided or use default + const branchParam = url.searchParams.get('branch'); + let selectedBranch: string; + + if (branchParam) { + const isValid = await validateBranch(info.owner, info.name, branchParam); + if (isValid) { + selectedBranch = branchParam; + } else { + selectedBranch = (await getDefaultBranch(info.owner, info.name)) || 'main'; + } + } else { + selectedBranch = (await getDefaultBranch(info.owner, info.name)) || 'main'; + } + + // Get first available specification + const specification = + specificationsList?.specifications?.[0]?.slug || 's-0.5vcpu-512mb'; + + // Create function + const func = await sdk.forProject(params.region, params.project).functions.create({ + functionId: ID.unique(), + name: info.name, + runtime: selectedRuntime, + execute: ['any'], + entrypoint: entrypoint || undefined, + commands: installCommand || undefined, + providerSilentMode: false, + specification + }); + + // Add auto-generated domain + await sdk.forProject(params.region, params.project).proxy.createFunctionRule({ + domain: `${ID.unique()}.${regionalConsoleVariables._APP_DOMAIN_FUNCTIONS}`, + functionId: func.$id + }); + + // Add variables (empty values used as empty strings) + await Promise.all( + envVars.map((variable) => + sdk.forProject(params.region, params.project).functions.createVariable({ + functionId: func.$id, + key: variable.key, + value: variable.value, + secret: false + }) + ) + ); + + // Create deployment + await sdk.forProject(params.region, params.project).functions.createTemplateDeployment({ + functionId: func.$id, + repository: info.name, + owner: info.owner, + rootDirectory: rootDir, + type: Type.Branch, + reference: selectedBranch, + activate: true + }); + + // Redirect to function page + redirect( + 302, + `${base}/project-${params.region}-${params.project}/functions/function-${func.$id}` + ); + } catch (e) { + // Re-throw redirects (they're not errors) + if (isRedirect(e)) { + throw e; + } + // On error, fall through to show the wizard + console.error('Quick deploy failed:', e); + } + } + let installations = vcsInstallations || null; if (!installations) { try { diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts index dc38b502f5..30206adcf9 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts @@ -1,10 +1,16 @@ import { sdk } from '$lib/stores/sdk'; -import { redirect } from '@sveltejs/kit'; +import { redirect, isRedirect } from '@sveltejs/kit'; import { base } from '$app/paths'; import type { PageLoad } from './$types'; -import { getNestedRootDirectory, getRepositoryInfo } from '$lib/helpers/github'; +import { + getNestedRootDirectory, + getRepositoryInfo, + getDefaultBranch, + validateBranch +} from '$lib/helpers/github'; +import { Adapter, BuildRuntime, Framework, ID, Type } from '@appwrite.io/console'; -export const load: PageLoad = async ({ url, params }) => { +export const load: PageLoad = async ({ url, params, parent }) => { const repository = url.searchParams.get('repo') || url.searchParams.get('repository'); if (!repository) { @@ -33,6 +39,115 @@ export const load: PageLoad = async ({ url, params }) => { }) : []; + // Quick mode - create site and redirect directly to deploying page + const quickMode = url.searchParams.get('quick') === 'true'; + + if (quickMode) { + try { + const { regionalConsoleVariables } = await parent(); + + const preset = url.searchParams.get('preset') || 'nextjs'; + const framework = (Object.values(Framework) as string[]).includes(preset.toLowerCase()) + ? (preset.toLowerCase() as Framework) + : Framework.Nextjs; + + // Get framework defaults + const frameworks = await sdk + .forProject(params.region, params.project) + .sites.listFrameworks(); + const fw = frameworks.frameworks.find((f) => f.key === framework); + + let installCommand = url.searchParams.get('install') || ''; + let buildCommand = url.searchParams.get('build') || ''; + let outputDirectory = url.searchParams.get('output') || ''; + + // Use framework defaults if not provided + if (!installCommand && !buildCommand && !outputDirectory && fw?.adapters?.length > 0) { + const adapter = fw.adapters[0]; + installCommand = adapter.installCommand || ''; + buildCommand = adapter.buildCommand || ''; + outputDirectory = adapter.outputDirectory || ''; + } + + // Get branch - validate provided or use default + const branchParam = url.searchParams.get('branch'); + let selectedBranch: string; + + if (branchParam) { + const isValid = await validateBranch(info.owner, info.name, branchParam); + if (isValid) { + selectedBranch = branchParam; + } else { + selectedBranch = (await getDefaultBranch(info.owner, info.name)) || 'main'; + } + } else { + selectedBranch = (await getDefaultBranch(info.owner, info.name)) || 'main'; + } + + const rootDir = + getNestedRootDirectory(repository) || url.searchParams.get('rootDir') || '.'; + const buildRuntime = + framework === Framework.Other ? BuildRuntime.Static1 : BuildRuntime.Node210; + + // Create site + const site = await sdk.forProject(params.region, params.project).sites.create({ + siteId: ID.unique(), + name: info.name, + framework, + buildRuntime, + installCommand: installCommand || undefined, + buildCommand: buildCommand || undefined, + outputDirectory: outputDirectory || undefined, + adapter: framework === Framework.Other ? Adapter.Static : undefined, + providerSilentMode: false + }); + + // Add auto-generated domain + await sdk.forProject(params.region, params.project).proxy.createSiteRule({ + domain: `${ID.unique()}.${regionalConsoleVariables._APP_DOMAIN_SITES}`, + siteId: site.$id + }); + + // Add variables (empty values used as empty strings) + await Promise.all( + envVars.map((variable) => + sdk.forProject(params.region, params.project).sites.createVariable({ + siteId: site.$id, + key: variable.key, + value: variable.value, + secret: false + }) + ) + ); + + // Create deployment + const deployment = await sdk + .forProject(params.region, params.project) + .sites.createTemplateDeployment({ + siteId: site.$id, + repository: info.name, + owner: info.owner, + rootDirectory: rootDir, + type: Type.Branch, + reference: selectedBranch, + activate: true + }); + + // Redirect to deploying page + redirect( + 302, + `${base}/project-${params.region}-${params.project}/sites/create-site/deploying?site=${site.$id}&deployment=${deployment.$id}` + ); + } catch (e) { + // Re-throw redirects (they're not errors) + if (isRedirect(e)) { + throw e; + } + // On error, fall through to show the wizard + console.error('Quick deploy failed:', e); + } + } + const [frameworks, installations] = await Promise.all([ sdk.forProject(params.region, params.project).sites.listFrameworks(), sdk diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index 4f2b5eeb87..2da5c21bc4 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -117,6 +117,8 @@ const branch = currentUrl.searchParams.get('branch') || data.deploymentData.repository.branch; + const quick = currentUrl.searchParams.get('quick'); + if (entrypoint) url.searchParams.set('entrypoint', entrypoint); if (install) url.searchParams.set('install', install); if (build) url.searchParams.set('build', build); @@ -126,6 +128,7 @@ rootDir || data.deploymentData.repository.rootDirectory ); if (branch) url.searchParams.set('branch', branch); + if (quick === 'true') url.searchParams.set('quick', 'true'); if (data.envVars.length > 0) { // Preserve KEY=value format for prefilled values diff --git a/src/routes/(public)/sites/deploy/+page.svelte b/src/routes/(public)/sites/deploy/+page.svelte index 32d09c3702..a8364205b9 100644 --- a/src/routes/(public)/sites/deploy/+page.svelte +++ b/src/routes/(public)/sites/deploy/+page.svelte @@ -124,11 +124,14 @@ const branch = currentUrl.searchParams.get('branch') || data.deploymentData.repository.branch; + const quick = currentUrl.searchParams.get('quick'); + if (preset) url.searchParams.set('preset', preset); if (install) url.searchParams.set('install', install); if (build) url.searchParams.set('build', build); if (output) url.searchParams.set('output', output); if (branch) url.searchParams.set('branch', branch); + if (quick === 'true') url.searchParams.set('quick', 'true'); } if (data.envVars.length > 0) { From 2d3699c4fab7f7eb9560e5c280aa3ab0f2a3a546 Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Sat, 29 Nov 2025 16:26:23 +0530 Subject: [PATCH 7/9] add plan checks --- .../(public)/functions/deploy/+page.svelte | 51 ++++++++++++++++++- src/routes/(public)/sites/deploy/+page.svelte | 51 ++++++++++++++++++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/routes/(public)/functions/deploy/+page.svelte b/src/routes/(public)/functions/deploy/+page.svelte index 2da5c21bc4..74a76ae5c4 100644 --- a/src/routes/(public)/functions/deploy/+page.svelte +++ b/src/routes/(public)/functions/deploy/+page.svelte @@ -5,13 +5,14 @@ import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import CustomId from '$lib/components/customId.svelte'; import { Button, Form, InputSelect } from '$lib/elements/forms'; - import type { AllowedRegions } from '$lib/sdk/billing.js'; + import type { AllowedRegions, Plan } from '$lib/sdk/billing.js'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import { ID, Query, type Models, Region } from '@appwrite.io/console'; import { IconGithub, IconPencil, IconPlus } from '@appwrite.io/pink-icons-svelte'; import { + Alert, Badge, Card, Divider, @@ -24,6 +25,7 @@ import { filterRegions } from '$lib/helpers/regions'; import { loadAvailableRegions } from '$routes/(console)/regions'; import { regions as regionsStore } from '$lib/stores/organization'; + import { formatCurrency } from '$lib/helpers/numbers'; let { data } = $props(); @@ -38,6 +40,21 @@ let id = $state(''); let loadingProjects = $state(false); + let currentPlan = $state(null); + + // Billing checks for project creation + let projectsLimited = $derived( + isCloud && + currentPlan?.projects > 0 && + projects?.total !== undefined && + projects.total >= currentPlan.projects + ); + let isAddonProject = $derived( + isCloud && + currentPlan?.addons?.projects?.supported && + projects?.total !== undefined && + projects.total >= (currentPlan?.addons?.projects?.planIncluded ?? 0) + ); async function fetchProjects() { loadingProjects = true; @@ -46,6 +63,15 @@ queries: [Query.equal('teamId', selectedOrg), Query.orderDesc('')] }); + // Fetch plan info for billing checks + if (isCloud && selectedOrg) { + try { + currentPlan = await sdk.forConsole.billing.getOrganizationPlan(selectedOrg); + } catch { + currentPlan = null; + } + } + selectedProject = projects?.total ? projects.projects[0].$id : null; loadingProjects = false; } @@ -244,8 +270,9 @@ label="Name" placeholder="Project name" required + disabled={projectsLimited} bind:value={projectName} /> - {#if !showCustomId} + {#if !showCustomId && !projectsLimited}
{/if} + {#if isAddonProject && !projectsLimited} + + Each added project comes with its own dedicated pool of + resources. + + {/if} + {#if projectsLimited} + + Extra projects are available on paid plans for an + additional fee + + {/if} {/if} @@ -284,6 +330,7 @@