From ef1cbeebbcf0a6e72c12db9ffe6fe14f36ec5d1b Mon Sep 17 00:00:00 2001 From: Atharva Deosthale Date: Thu, 14 Aug 2025 18:27:23 +0530 Subject: [PATCH] feat: one click deployments for appwrite sites --- .../sites/create-site/deploy/+page.svelte | 365 ++++++++++++++++++ .../sites/create-site/deploy/+page.ts | 50 +++ src/routes/(public)/deploy/+page.svelte | 263 +++++++++++++ src/routes/(public)/deploy/+page.ts | 90 +++++ 4 files changed, 768 insertions(+) create mode 100644 src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts create mode 100644 src/routes/(public)/deploy/+page.svelte create mode 100644 src/routes/(public)/deploy/+page.ts 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 new file mode 100644 index 000000000..b845b70df --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte @@ -0,0 +1,365 @@ + + + + Deploy {data.repository.name} - Appwrite + + + +
+ + + + + Repository + + + + + {data.repository.owner}/{data.repository.name} + + + + + + +
+ + + + {#if showCustomId} + + {:else} +
+ (showCustomId = !showCustomId)}> + + Site ID + +
+ {/if} +
+ ({ + value: fw.key, + label: fw.name, + leadingHtml: `` + }))} /> +
+
+ +
+ + + + +
+ +
+ + + + + +
+ + {#if variables.length > 0} +
+ + {#each variables as variable, i} + + + +
+ {#if i === 0} + Secret + {/if} +
+ +
+
+
+ {/each} +
+
+ {/if} + + + + {#if !data.installations?.total} + + + + + Note: You can connect your GitHub account later to enable automatic + deployments + + + + {/if} +
+
+ + + + + +
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 new file mode 100644 index 000000000..8b8a33bb3 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.ts @@ -0,0 +1,50 @@ +import { sdk } from '$lib/stores/sdk'; +import { redirect } from '@sveltejs/kit'; +import { base } from '$app/paths'; +import type { PageLoad } from './$types'; +import type { Models } from '@appwrite.io/console'; + +export const load: PageLoad = async ({ url, params }) => { + // Get repository URL from query params + const repoUrl = url.searchParams.get('repo'); + if (!repoUrl) { + redirect(302, `${base}/project-${params.region}-${params.project}/sites`); + } + + // Parse repository information + const repoMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\?\s]+)/); + if (!repoMatch) { + redirect(302, `${base}/project-${params.region}-${params.project}/sites`); + } + + const [, owner, repoName] = repoMatch; + // Clean repository name (remove .git extension if present) + const cleanRepoName = repoName.replace(/\.git$/, ''); + + // Get environment variables from query params + const envParam = url.searchParams.get('env'); + const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Try to fetch installations + let installations: Models.InstallationList | null = null; + try { + installations = await sdk.forProject(params.region, params.project).vcs.listInstallations(); + } catch (error) { + // If error, user might not have GitHub connected + installations = null; + } + + // Fetch frameworks list + const frameworks = await sdk.forProject(params.region, params.project).sites.listFrameworks(); + + return { + repository: { + url: repoUrl, + owner, + name: cleanRepoName + }, + envKeys, + installations, + frameworks + }; +}; diff --git a/src/routes/(public)/deploy/+page.svelte b/src/routes/(public)/deploy/+page.svelte new file mode 100644 index 000000000..26560c06d --- /dev/null +++ b/src/routes/(public)/deploy/+page.svelte @@ -0,0 +1,263 @@ + + + + Deploy {data.repository.name} - Appwrite + + +
+
+
+ {#if $app.themeInUse === 'dark'} + Appwrite + {:else} + Appwrite + {/if} +
+ + + + + + + + Repository + + + + + {data.repository.owner}/{data.repository.name} + + + {#if data.envKeys.length > 0} + + + + Environment Variables Required + + + {#each data.envKeys as envKey} + {envKey} + {/each} + + + {/if} + + + +
+ + ({ + label: o.name, + value: o.$id + }))} + bind:value={selectedOrg} /> + + {#if projects?.total} + {#key selectedProject} + ({ + label: p.name, + value: p.$id + })), + { + label: 'Create project', + leadingIcon: IconPlusSm, + value: null + } + ]} + bind:value={selectedProject} /> + {/key} + {/if} + {#if selectedProject === null} + + + {#if !showCustomId} +
+ { + showCustomId = true; + }} + > Project + ID +
+ {/if} + +
+ {#if isCloud} + + + + Region cannot be changed after creation + + + {/if} + {/if} + + + +
+ +
+
+
+
+
+
+
+
+
diff --git a/src/routes/(public)/deploy/+page.ts b/src/routes/(public)/deploy/+page.ts new file mode 100644 index 000000000..db5148db7 --- /dev/null +++ b/src/routes/(public)/deploy/+page.ts @@ -0,0 +1,90 @@ +import { sdk } from '$lib/stores/sdk.js'; +import { redirect } from '@sveltejs/kit'; +import { base } from '$app/paths'; +import { isCloud } from '$lib/system'; +import { BillingPlan } from '$lib/constants'; +import { ID, type Models } from '@appwrite.io/console'; +import type { OrganizationList } from '$lib/stores/organization'; +import { redirectTo } from '$routes/store'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ parent, url }) => { + const { account } = await parent(); + + // Store the full URL for redirect after auth + const fullUrl = url.pathname + url.search; + + // Check if user is authenticated + if (!account) { + redirectTo.set(fullUrl); + redirect(302, base + '/login?redirect=' + encodeURIComponent(fullUrl)); + } + + // Get repository URL from query params + const repoUrl = url.searchParams.get('repo'); + if (!repoUrl) { + redirect(302, base + '/'); + } + + // Parse repository information + const repoMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\?\s]+)/); + if (!repoMatch) { + redirect(302, base + '/'); + } + + const [, owner, repoName] = repoMatch; + // Clean repository name (remove .git extension if present) + const cleanRepoName = repoName.replace(/\.git$/, ''); + + // Get environment variables from query params + const envParam = url.searchParams.get('env'); + const envKeys = envParam ? envParam.split(',').map((key: string) => key.trim()) : []; + + // Get organizations + let organizations: Models.TeamList> | OrganizationList | undefined; + if (isCloud) { + organizations = await sdk.forConsole.billing.listOrganization(); + } else { + organizations = await sdk.forConsole.teams.list(); + } + + // Create default organization if none exists - matches console's onboarding behavior + if (!organizations?.total) { + let org = null; + try { + if (isCloud) { + org = await sdk.forConsole.billing.createOrganization( + ID.unique(), + 'Personal Projects', + BillingPlan.FREE, + null, + null + ); + } else { + org = await sdk.forConsole.teams.create(ID.unique(), 'Personal Projects'); + } + + // Refetch organizations after creation + if (isCloud) { + organizations = await sdk.forConsole.billing.listOrganization(); + } else { + organizations = await sdk.forConsole.teams.list(); + } + } catch (e) { + // If organization creation fails, still redirect to deploy page + // The page will handle showing an error + console.error('Failed to create default organization:', e); + } + } + + return { + account, + organizations, + repository: { + url: repoUrl, + owner, + name: cleanRepoName + }, + envKeys + }; +};