From c3fb40262d267f9077aefc03186234179a25a1c1 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:34:18 +0530 Subject: [PATCH 001/103] fix: Buggy root directory Modal --- src/lib/components/git/selectRootModal.svelte | 68 +++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index bc817623e..65ebd6940 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -37,7 +37,10 @@ ]; let currentPath: string = './'; let currentDir: Directory; - export let expanded = writable(['lib-0', 'tree-0']); + export let expanded = writable([]); + let initialized = false; + let initialPath: string = './'; + let pathNotFound = false; onMount(async () => { try { @@ -61,13 +64,13 @@ })); currentDir = directories[0]; isLoading = false; + $expanded = Array.from(new Set([...$expanded, './'])); } catch { return; } }); - async function fetchContents(e: CustomEvent) { - const path = e.detail.fullPath as string; + async function loadPath(path: string) { currentPath = path; const pathSegments = path.split('/').filter((segment) => segment !== '.' && segment !== ''); @@ -99,6 +102,7 @@ const contentDirectories = content.contents.filter((e) => e.isDirectory); if (contentDirectories.length === 0) { + $expanded = Array.from(new Set([...$expanded, path])); return; } @@ -133,7 +137,7 @@ }); } directories = [...directories]; - $expanded = [...$expanded, path]; + $expanded = Array.from(new Set([...$expanded, path])); } catch (error) { console.error(error); } finally { @@ -142,6 +146,60 @@ } } + async function fetchContents(e: CustomEvent) { + const path = e.detail.fullPath as string; + await loadPath(path); + } + + function normalizePath(path: string): string { + if (!path || path === '') return './'; + if (path === './') return './'; + if (path.startsWith('./')) return path.replace(/\/$/, ''); + if (path.startsWith('/')) return '.' + path.replace(/\/$/, ''); + return './' + path.replace(/\/$/, ''); + } + + async function expandToPath(path: string) { + pathNotFound = false; + const normalized = normalizePath(path); + const segments = normalized.split('/').filter((s) => s !== '.' && s !== ''); + let cumulative = './'; + $expanded = Array.from(new Set([...$expanded, './'])); + for (const segment of segments) { + cumulative = cumulative === './' ? `./${segment}` : `${cumulative}/${segment}`; + const parentSegments = cumulative.split('/').filter((s) => s !== '.' && s !== ''); + let cursor = directories[0]; + for (const s of parentSegments.slice(0, -1)) { + const next = cursor.children?.find((d) => d.title === s); + if (!next) { + pathNotFound = true; + return; + } + cursor = next; + } + await loadPath(cursor.fullPath ?? './'); + const exists = cursor.children?.some((d) => d.title === segment); + if (!exists) { + pathNotFound = true; + return; + } + $expanded = Array.from(new Set([...$expanded, cumulative])); + } + currentPath = normalized; + } + + $: if (show && !initialized && !isLoading) { + initialized = true; + initialPath = normalizePath(rootDir ?? './'); + currentPath = initialPath; + expandToPath(initialPath); + } + + $: if (!show && initialized) { + initialized = false; + pathNotFound = false; + } + function handleSubmit() { rootDir = currentPath; show = false; @@ -156,6 +214,6 @@ - + From 45e9a40583c88031dbefa165d210242870c7f40f Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:52:05 +0530 Subject: [PATCH 002/103] some fixes --- package.json | 4 +- pnpm-lock.yaml | 20 +- src/lib/components/git/selectRootModal.svelte | 311 ++++++++++-------- 3 files changed, 188 insertions(+), 147 deletions(-) diff --git a/package.json b/package.json index 496818696..f23d02803 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@ai-sdk/svelte": "^1.1.24", "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 827befa27..08b9db4e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3709,7 +3709,7 @@ snapshots: dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@aa2ed6e(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3722,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@aa2ed6e(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 65ebd6940..f50122caf 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -7,25 +7,31 @@ import { installation, repository } from '$lib/stores/vcs'; import { VCSDetectionType, type Models } from '@appwrite.io/console'; import { DirectoryPicker } from '@appwrite.io/pink-svelte'; - import { onMount } from 'svelte'; import { writable } from 'svelte/store'; type Directory = { title: string; fullPath: string; - fileCount: number; - thumbnailUrl: string; + fileCount?: number; + thumbnailUrl?: string; children?: Directory[]; loading?: boolean; }; - export let show = false; - export let rootDir: string; - export let product: 'sites' | 'functions' = 'functions'; - export let branch: string; + let { + show = $bindable(false), + rootDir = $bindable(''), + product = 'functions' as 'sites' | 'functions', + branch + }: { + show?: boolean; + rootDir?: string; + product?: 'sites' | 'functions'; + branch: string; + } = $props(); - let isLoading = true; - let directories: Directory[] = [ + let isLoading = $state(true); + let directories = $state([ { title: 'Root', fullPath: './', @@ -34,170 +40,199 @@ children: [], loading: false } - ]; - let currentPath: string = './'; - let currentDir: Directory; - export let expanded = writable([]); - let initialized = false; - let initialPath: string = './'; - let pathNotFound = false; + ]); + let currentPath = $state('./'); + let expandedStore = writable([]); + let initialized = $state(false); + let initialPath = $state('./'); + let isFetching = false; - onMount(async () => { + let hasChanges = $derived(currentPath !== initialPath); + + async function detectRuntimeOrFramework(path: string): Promise { try { - const content = await sdk + const detection = await sdk .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ + .vcs.createRepositoryDetection({ installationId: $installation.$id, providerRepositoryId: $repository.id, - providerRootDirectory: currentPath, - providerReference: branch + type: + product === 'sites' ? VCSDetectionType.Framework : VCSDetectionType.Runtime, + providerRootDirectory: path }); - directories[0].fileCount = content.contents?.length ?? 0; - directories[0].children = content.contents - .filter((e) => e.isDirectory) - .map((dir) => ({ - title: dir.name, - fullPath: currentPath + dir.name, - fileCount: undefined, - thumbnailUrl: dir.name, - loading: false - })); - currentDir = directories[0]; - isLoading = false; - $expanded = Array.from(new Set([...$expanded, './'])); - } catch { - return; + + const iconName = + product === 'sites' + ? detection.framework + : (detection as unknown as Models.DetectionRuntime).runtime; + return $iconPath(iconName, 'color'); + } catch (err) { + return null; } - }); + } - async function loadPath(path: string) { - currentPath = path; - - const pathSegments = path.split('/').filter((segment) => segment !== '.' && segment !== ''); - let traversedDir = directories[0]; // Start at root - - for (const segment of pathSegments) { - const nextDir = traversedDir.children?.find((dir) => dir.title === segment); - if (!nextDir) break; - traversedDir = nextDir; - } - - currentDir = traversedDir; - - if (!currentDir.fileCount) { - currentDir.loading = true; - directories = [...directories]; + $effect(() => { + if (!isLoading) return; + (async () => { try { const content = await sdk .forProject(page.params.region, page.params.project) .vcs.getRepositoryContents({ installationId: $installation.$id, providerRepositoryId: $repository.id, - providerRootDirectory: path, + providerRootDirectory: './', providerReference: branch }); - const fileCount = content.contents?.length ?? 0; - const contentDirectories = content.contents.filter((e) => e.isDirectory); + directories[0] = { + ...directories[0], + fileCount: content.contents?.length ?? 0, + children: content.contents + .filter((e) => e.isDirectory) + .map((dir) => ({ + title: dir.name, + fullPath: `./${dir.name}`, + fileCount: undefined, + // set logo for root directories + thumbnailUrl: dir.name, + loading: false + })) + }; - if (contentDirectories.length === 0) { - $expanded = Array.from(new Set([...$expanded, path])); - return; + const detectedIcon = await detectRuntimeOrFramework('./'); + if (detectedIcon) { + directories[0].thumbnailUrl = detectedIcon; } - currentDir.fileCount = fileCount; - currentDir.children = contentDirectories.map((dir) => ({ - title: dir.name, - fullPath: path + '/' + dir.name, - fileCount: undefined, - thumbnailUrl: undefined - })); - const runtime = await sdk - .forProject(page.params.region, page.params.project) - .vcs.createRepositoryDetection({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - type: - product === 'sites' - ? VCSDetectionType.Framework - : VCSDetectionType.Runtime, - providerRootDirectory: path - }); - if (product === 'sites') { - currentDir.children.forEach((dir) => { - dir.thumbnailUrl = $iconPath(runtime.framework, 'color'); - }); - } else if (product === 'functions') { - currentDir.children.forEach((dir) => { - dir.thumbnailUrl = $iconPath( - (runtime as unknown as Models.DetectionRuntime).runtime, - 'color' - ); - }); - } - directories = [...directories]; - $expanded = Array.from(new Set([...$expanded, path])); + isLoading = false; + expandedStore.update((exp) => [...exp, './']); } catch (error) { - console.error(error); - } finally { - currentDir.loading = false; + console.error('Failed to load root directory:', error); + isLoading = false; } + })(); + }); + + function getDirByPath(path: string): Directory | null { + const segments = path.split('/').filter((s) => s !== '.' && s !== ''); + let node: Directory | null = directories[0] ?? null; + for (const seg of segments) { + const next = node?.children?.find((d) => d.title === seg) ?? null; + if (!next) return null; + node = next; } + return node; } - async function fetchContents(e: CustomEvent) { - const path = e.detail.fullPath as string; - await loadPath(path); + async function loadPath(path: string) { + // skip loading if this directory was donee + const targetDir = getDirByPath(path); + if (!targetDir || targetDir.fileCount !== undefined) return; + + if (isFetching) return; + isFetching = true; + targetDir.loading = true; + + try { + const content = await sdk + .forProject(page.params.region, page.params.project) + .vcs.getRepositoryContents({ + installationId: $installation.$id, + providerRepositoryId: $repository.id, + providerRootDirectory: path, + providerReference: branch + }); + + const fileCount = content.contents?.length ?? 0; + const contentDirectories = content.contents.filter((e) => e.isDirectory); + + if (contentDirectories.length === 0) { + expandedStore.update((exp) => [...new Set([...exp, path])]); + return; + } + + targetDir.fileCount = fileCount; + + // set logo only for the current folder, not for the children + const detectedIcon = await detectRuntimeOrFramework(path); + if (detectedIcon) { + targetDir.thumbnailUrl = detectedIcon; + } + + targetDir.children = contentDirectories.map((dir) => { + return { + title: dir.name, + fullPath: `${path}/${dir.name}`, + fileCount: undefined, + thumbnailUrl: dir.name + }; + }); + + expandedStore.update((exp) => [...new Set([...exp, path])]); + } catch (error) { + console.error('Failed to load directory:', error); + } finally { + targetDir.loading = false; + isFetching = false; + } } function normalizePath(path: string): string { - if (!path || path === '') return './'; - if (path === './') return './'; - if (path.startsWith('./')) return path.replace(/\/$/, ''); - if (path.startsWith('/')) return '.' + path.replace(/\/$/, ''); - return './' + path.replace(/\/$/, ''); + if (!path || path === './') return './'; + const trimmed = path.replace(/\/$/, ''); + return trimmed.startsWith('./') ? trimmed : `./${trimmed}`; } async function expandToPath(path: string) { - pathNotFound = false; const normalized = normalizePath(path); const segments = normalized.split('/').filter((s) => s !== '.' && s !== ''); - let cumulative = './'; - $expanded = Array.from(new Set([...$expanded, './'])); + + expandedStore.update((exp) => [...new Set([...exp, './'])]); + + let currentDir = directories[0]; + let currentPath = './'; + for (const segment of segments) { - cumulative = cumulative === './' ? `./${segment}` : `${cumulative}/${segment}`; - const parentSegments = cumulative.split('/').filter((s) => s !== '.' && s !== ''); - let cursor = directories[0]; - for (const s of parentSegments.slice(0, -1)) { - const next = cursor.children?.find((d) => d.title === s); - if (!next) { - pathNotFound = true; - return; - } - cursor = next; - } - await loadPath(cursor.fullPath ?? './'); - const exists = cursor.children?.some((d) => d.title === segment); - if (!exists) { - pathNotFound = true; - return; - } - $expanded = Array.from(new Set([...$expanded, cumulative])); + currentPath = currentPath === './' ? `./${segment}` : `${currentPath}/${segment}`; + + // Load the parent directory if not already loaded + await loadPath(currentDir.fullPath); + + // Find the next directory + const nextDir = currentDir.children?.find((d) => d.title === segment); + if (!nextDir) return; // Path doesn't exist + + currentDir = nextDir; + expandedStore.update((exp) => [...new Set([...exp, currentPath])]); } + currentPath = normalized; } - $: if (show && !initialized && !isLoading) { - initialized = true; - initialPath = normalizePath(rootDir ?? './'); - currentPath = initialPath; - expandToPath(initialPath); - } + $effect(() => { + if (show && !initialized && !isLoading) { + initialized = true; + const normalized = normalizePath(rootDir || './'); + initialPath = normalized; + currentPath = normalized; + expandToPath(normalized); + } + }); - $: if (!show && initialized) { - initialized = false; - pathNotFound = false; + // reset state when modal closes + $effect(() => { + if (!show && initialized) { + initialized = false; + } + }); + + async function handleSelect(e: CustomEvent) { + const path = e.detail.fullPath as string; + if (isFetching) return; + + currentPath = path; + await loadPath(path); } function handleSubmit() { @@ -210,10 +245,16 @@ Select the directory where your site code is located using the menu below. - + - + From b85c938cf63e0b3449192ecc1e5a355f6f3a1b82 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 4 Feb 2026 02:06:36 +0000 Subject: [PATCH 003/103] fix: update @appwrite.io/console dependency to latest version improve console access --- bun.lock | 4 ++-- package.json | 2 +- src/routes/(console)/project-[region]-[project]/+layout.ts | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 8a28be835..5c92cd04d 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@95675c4", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@7dc3a8f", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@c1feb89", "@appwrite.io/pink-legacy": "^1.0.3", @@ -107,7 +107,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@95675c4", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@7dc3a8f", { "dependencies": { "bignumber.js": "9.0.0", "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 604624d1d..985efb2fa 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@95675c4", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@7dc3a8f", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@c1feb89", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 3701c401a..2e0684320 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -19,6 +19,9 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { const project = await sdk.forConsole.projects.get({ projectId: params.project }); project.region ??= 'default'; + // Track console access (fire-and-forget, backend has 6-day cooldown) + sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }).catch(() => {}); + // fast path without a network call! let organization = (organizations as Models.OrganizationList)?.teams?.find( (org) => org.$id === project.teamId From 09ef6f3538725fc6167fe45b8dbe18c20561178d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 4 Feb 2026 07:31:19 +0000 Subject: [PATCH 004/103] feat: implement fingerprint generation for console access tracking --- src/lib/helpers/fingerprint.ts | 174 ++++++++++++++++++ .../project-[region]-[project]/+layout.ts | 8 +- 2 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/lib/helpers/fingerprint.ts diff --git a/src/lib/helpers/fingerprint.ts b/src/lib/helpers/fingerprint.ts new file mode 100644 index 000000000..fdf59d03d --- /dev/null +++ b/src/lib/helpers/fingerprint.ts @@ -0,0 +1,174 @@ +import { env } from '$env/dynamic/public'; + +const SECRET = env.PUBLIC_CONSOLE_FINGERPRINT_KEY ?? ''; + +async function sha256(message: string): Promise { + const data = new TextEncoder().encode(message); + const hash = await crypto.subtle.digest('SHA-256', data); + return Array.from(new Uint8Array(hash)) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} + +async function hmacSha256(message: string, secret: string): Promise { + const key = await crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(secret), + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ); + const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message)); + return Array.from(new Uint8Array(sig)) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} + +function getCanvasFingerprint(): string { + try { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) return ''; + + canvas.width = 200; + canvas.height = 50; + + ctx.textBaseline = 'top'; + ctx.font = '14px Arial'; + ctx.fillStyle = '#f60'; + ctx.fillRect(125, 1, 62, 20); + ctx.fillStyle = '#069'; + ctx.fillText('Appwrite Console', 2, 15); + ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; + ctx.fillText('Appwrite Console', 4, 17); + + return canvas.toDataURL(); + } catch { + return ''; + } +} + +function getWebGLFingerprint(): string { + try { + const canvas = document.createElement('canvas'); + const gl = + canvas.getContext('webgl') || (canvas.getContext('experimental-webgl') as WebGLRenderingContext | null); + if (!gl) return ''; + + const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); + if (!debugInfo) return 'webgl-no-debug'; + + const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || ''; + const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || ''; + + return `${vendor}~${renderer}`; + } catch { + return ''; + } +} + +function getAudioFingerprint(): Promise { + return new Promise((resolve) => { + try { + const AudioContext = window.AudioContext || (window as unknown as { webkitAudioContext: typeof window.AudioContext }).webkitAudioContext; + if (!AudioContext) { + resolve(''); + return; + } + + const context = new AudioContext(); + const oscillator = context.createOscillator(); + const analyser = context.createAnalyser(); + const gain = context.createGain(); + const processor = context.createScriptProcessor(4096, 1, 1); + + gain.gain.value = 0; + oscillator.type = 'triangle'; + oscillator.frequency.value = 10000; + + oscillator.connect(analyser); + analyser.connect(processor); + processor.connect(gain); + gain.connect(context.destination); + + oscillator.start(0); + + const dataArray = new Float32Array(analyser.frequencyBinCount); + analyser.getFloatFrequencyData(dataArray); + + let sum = 0; + for (let i = 0; i < dataArray.length; i++) { + sum += Math.abs(dataArray[i]); + } + + oscillator.stop(); + processor.disconnect(); + context.close(); + + resolve(sum.toString()); + } catch { + resolve(''); + } + }); +} + +interface BrowserSignals { + timestamp: number; + userAgent: string; + language: string; + languages: string[]; + platform: string; + hardwareConcurrency: number; + deviceMemory: number | undefined; + maxTouchPoints: number; + screenWidth: number; + screenHeight: number; + screenColorDepth: number; + screenPixelDepth: number; + devicePixelRatio: number; + timezoneOffset: number; + timezone: string; + canvas: string; + webgl: string; + audio: string; +} + +async function collectBrowserSignals(): Promise { + const [canvasRaw, webgl, audio] = await Promise.all([ + Promise.resolve(getCanvasFingerprint()), + Promise.resolve(getWebGLFingerprint()), + getAudioFingerprint() + ]); + + const canvas = canvasRaw ? await sha256(canvasRaw) : ''; + + return { + timestamp: Math.floor(Date.now() / 1000), + userAgent: navigator.userAgent, + language: navigator.language, + languages: [...(navigator.languages || [])], + platform: navigator.platform, + hardwareConcurrency: navigator.hardwareConcurrency || 0, + deviceMemory: (navigator as Navigator & { deviceMemory?: number }).deviceMemory, + maxTouchPoints: navigator.maxTouchPoints || 0, + screenWidth: screen.width, + screenHeight: screen.height, + screenColorDepth: screen.colorDepth, + screenPixelDepth: screen.pixelDepth, + devicePixelRatio: window.devicePixelRatio || 1, + timezoneOffset: new Date().getTimezoneOffset(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + canvas, + webgl, + audio + }; +} + +export async function generateFingerprintToken(): Promise { + const signals = await collectBrowserSignals(); + const payload = JSON.stringify(signals); + const encoded = btoa(payload); + const signature = await hmacSha256(encoded, SECRET); + + return `${encoded}.${signature}`; +} diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 2e0684320..53b0af8a4 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -11,6 +11,7 @@ import { loadAvailableRegions } from '$routes/(console)/regions'; import { type Models, Platform } from '@appwrite.io/console'; import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; +import { generateFingerprintToken } from '$lib/helpers/fingerprint'; export const load: LayoutLoad = async ({ params, depends, parent }) => { const { plansInfo, organizations, preferences: prefs } = await parent(); @@ -20,7 +21,12 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { project.region ??= 'default'; // Track console access (fire-and-forget, backend has 6-day cooldown) - sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }).catch(() => {}); + generateFingerprintToken() + .then((fingerprint) => { + sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; + return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); + }) + .catch(() => {}); // fast path without a network call! let organization = (organizations as Models.OrganizationList)?.teams?.find( From ad097ec20e1b8b8ac69dfb4ea3219df4b8f964a6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 4 Feb 2026 07:44:06 +0000 Subject: [PATCH 005/103] cached finterprint data --- src/lib/helpers/fingerprint.ts | 57 +++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/src/lib/helpers/fingerprint.ts b/src/lib/helpers/fingerprint.ts index fdf59d03d..4a0ba3988 100644 --- a/src/lib/helpers/fingerprint.ts +++ b/src/lib/helpers/fingerprint.ts @@ -1,6 +1,7 @@ import { env } from '$env/dynamic/public'; const SECRET = env.PUBLIC_CONSOLE_FINGERPRINT_KEY ?? ''; +const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour async function sha256(message: string): Promise { const data = new TextEncoder().encode(message); @@ -52,7 +53,8 @@ function getWebGLFingerprint(): string { try { const canvas = document.createElement('canvas'); const gl = - canvas.getContext('webgl') || (canvas.getContext('experimental-webgl') as WebGLRenderingContext | null); + canvas.getContext('webgl') || + (canvas.getContext('experimental-webgl') as WebGLRenderingContext | null); if (!gl) return ''; const debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); @@ -70,7 +72,10 @@ function getWebGLFingerprint(): string { function getAudioFingerprint(): Promise { return new Promise((resolve) => { try { - const AudioContext = window.AudioContext || (window as unknown as { webkitAudioContext: typeof window.AudioContext }).webkitAudioContext; + const AudioContext = + window.AudioContext || + (window as unknown as { webkitAudioContext: typeof window.AudioContext }) + .webkitAudioContext; if (!AudioContext) { resolve(''); return; @@ -112,8 +117,7 @@ function getAudioFingerprint(): Promise { }); } -interface BrowserSignals { - timestamp: number; +interface StaticSignals { userAgent: string; language: string; languages: string[]; @@ -133,7 +137,19 @@ interface BrowserSignals { audio: string; } -async function collectBrowserSignals(): Promise { +interface BrowserSignals extends StaticSignals { + timestamp: number; +} + +interface SignalsCache { + signals: StaticSignals; + collectedAt: number; +} + +let cache: SignalsCache | null = null; +let cachePromise: Promise | null = null; + +async function collectStaticSignals(): Promise { const [canvasRaw, webgl, audio] = await Promise.all([ Promise.resolve(getCanvasFingerprint()), Promise.resolve(getWebGLFingerprint()), @@ -143,7 +159,6 @@ async function collectBrowserSignals(): Promise { const canvas = canvasRaw ? await sha256(canvasRaw) : ''; return { - timestamp: Math.floor(Date.now() / 1000), userAgent: navigator.userAgent, language: navigator.language, languages: [...(navigator.languages || [])], @@ -164,8 +179,36 @@ async function collectBrowserSignals(): Promise { }; } +async function getCachedSignals(): Promise { + const now = Date.now(); + + if (cache && now - cache.collectedAt < CACHE_TTL_MS) { + return cache.signals; + } + + if (cachePromise) { + return cachePromise; + } + + cachePromise = collectStaticSignals(); + + try { + const signals = await cachePromise; + cache = { signals, collectedAt: now }; + return signals; + } finally { + cachePromise = null; + } +} + export async function generateFingerprintToken(): Promise { - const signals = await collectBrowserSignals(); + const staticSignals = await getCachedSignals(); + + const signals: BrowserSignals = { + ...staticSignals, + timestamp: Math.floor(Date.now() / 1000) + }; + const payload = JSON.stringify(signals); const encoded = btoa(payload); const signature = await hmacSha256(encoded, SECRET); From 266ee4a017ecd420d6383da6e7b71b3aac8efa08 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 4 Feb 2026 08:17:52 +0000 Subject: [PATCH 006/103] fix: restrict console access tracking to cloud environments only --- .../project-[region]-[project]/+layout.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 93a5df225..bbb433100 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -21,13 +21,15 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { const project = await sdk.forConsole.projects.get({ projectId: params.project }); project.region ??= 'default'; - // Track console access (fire-and-forget, backend has 6-day cooldown) - generateFingerprintToken() - .then((fingerprint) => { - sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; - return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); - }) - .catch(() => {}); + // Track console access for cloud only (fire-and-forget, backend has 6-day cooldown) + if (isCloud) { + generateFingerprintToken() + .then((fingerprint) => { + sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; + return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); + }) + .catch(() => {}); + } // fast path without a network call! let organization = (organizations as Models.OrganizationList)?.teams?.find( From 7c1cf1d659ace63472bfc0a4b20e4c6d3e3b0f67 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 12:08:39 +0530 Subject: [PATCH 007/103] remove: archiving logic. --- src/lib/components/archiveProject.svelte | 379 ------------------ .../billing/alerts/projectsLimit.svelte | 53 --- .../billing/alerts/selectProjectCloud.svelte | 180 --------- .../components/organizationUsageLimits.svelte | 34 +- src/lib/stores/billing.ts | 26 -- src/routes/(console)/+layout.svelte | 4 - .../organization-[organization]/+page.svelte | 73 +--- .../organization-[organization]/+page.ts | 78 +--- .../change-plan/+page.svelte | 2 +- 9 files changed, 44 insertions(+), 785 deletions(-) delete mode 100644 src/lib/components/archiveProject.svelte delete mode 100644 src/lib/components/billing/alerts/projectsLimit.svelte delete mode 100644 src/lib/components/billing/alerts/selectProjectCloud.svelte diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte deleted file mode 100644 index 8a324b395..000000000 --- a/src/lib/components/archiveProject.svelte +++ /dev/null @@ -1,379 +0,0 @@ - - -{#if projectsToArchive.length > 0} -
- - - {#if isPlanBelowPro} - These projects are archived and require a plan upgrade to restore access. - {:else} - These projects will be archived at the end of your billing cycle. - {/if} - - -
- - {#each projectsToArchive as project} - {@const platforms = filterPlatforms( - project.platforms.map((platform) => getPlatformInfo(platform.type)) - )} - {@const formatted = formatName(project.name)} - - - {project?.platforms?.length ? project?.platforms?.length : 'No'} apps - - {formatted} - -
- - - - handleUnarchiveProject(project)} - >Unarchive project - handleMigrateProject(project)} - >Migrate project -
- -
- handleDeleteProject(project)} - >Delete project -
-
-
-
- - {#each platforms.slice(0, 2) as platform} - {@const icon = getIconForPlatform(platform.icon)} - - - - {/each} - - {#if platforms.length > 2} - - {/if} - - - {#if isCloud && $regionsStore?.regions} - {@const region = findRegion(project)} - {region?.name} - {/if} - -
- {/each} -
- - -
-
-
-{/if} - - - -

Are you sure you want to unarchive {projectToUnarchive?.name}?

-

This will move the project back to your active projects list.

- - - - - - - -
- - - - - The archived project {projectToDelete?.name} will be deleted along with all - of its metadata, stats, and other resources. - This action is irreversible. - - - - - - - - - - - diff --git a/src/lib/components/billing/alerts/projectsLimit.svelte b/src/lib/components/billing/alerts/projectsLimit.svelte deleted file mode 100644 index 493e44510..000000000 --- a/src/lib/components/billing/alerts/projectsLimit.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - -{#if organizationId && $currentPlan && $currentPlan.projects > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname)} - - - Choose which projects to keep before {toLocaleDate( - $organization.billingNextInvoiceDate - )} or upgrade to Pro. Projects over the limit will be blocked after this date. - - - - - - -{/if} diff --git a/src/lib/components/billing/alerts/selectProjectCloud.svelte b/src/lib/components/billing/alerts/selectProjectCloud.svelte deleted file mode 100644 index a0084f97a..000000000 --- a/src/lib/components/billing/alerts/selectProjectCloud.svelte +++ /dev/null @@ -1,180 +0,0 @@ - - - - - Choose which {$currentPlan?.projects || 2} projects to keep. Projects over the limit will be - blocked after this date. - - - {#if loading} -
- - - Project Name - Created - - - {#each Array.from({ length: 5 }) as _} - - - - - - - - - {/each} - -
- {:else if projectsLoadingError} - {projectsLoadingError} - {:else} - {#if error} - {error} - {/if} - - - - Project Name - Created - - {#each projects as project} - - {project.name} - {toLocaleDateTime(project.$createdAt)} - - {/each} - - - {#if selectedProjects.length > $currentPlan?.projects} -
- You can only select {$currentPlan?.projects} projects. Please deselect others to continue. -
- {/if} - - {#if selectedProjects.length === $currentPlan?.projects} - {@const difference = projects.length - selectedProjects.length} - {@const messagePrefix = - difference > 1 ? `${difference} projects` : `${difference} project`} - - - {@html formatProjectsToArchive()} - will be archived. - - - {/if} - {/if} - - (showSelectProject = false)} - >Cancel - Save - -
- - diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index fc97c9b25..085abc596 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -31,45 +31,45 @@ const baseFreePlan = getBasePlanFromGroup(BillingPlanGroup.Starter); // Derived state using runes - let freePlanLimits = $derived({ + const freePlanLimits = $derived({ projects: baseFreePlan?.projects, members: getServiceLimit('members', null, baseFreePlan), storage: getServiceLimit('storage', null, baseFreePlan) }); // When preparing to downgrade to Free, enforce Free plan limit locally (2) - let allowedProjectsToKeep = $derived(freePlanLimits.projects); + const allowedProjectsToKeep = $derived(freePlanLimits.projects); - let currentUsage = $derived({ + const currentUsage = $derived({ projects: projects?.length || 0, members: members?.length || 0, storage: storageUsage || 0 }); - let storageUsageGB = $derived(storageUsage / (1024 * 1024 * 1024)); + const storageUsageGB = $derived(storageUsage / (1024 * 1024 * 1024)); - let isLimitExceeded = $derived({ + const isLimitExceeded = $derived({ projects: currentUsage.projects > freePlanLimits.projects, members: currentUsage.members > freePlanLimits.members, storage: storageUsageGB > freePlanLimits.storage }); - let excessUsage = $derived({ + const excessUsage = $derived({ projects: Math.max(0, currentUsage.projects), members: Math.max(0, currentUsage.members - freePlanLimits.members), storage: Math.max(0, storageUsageGB - freePlanLimits.storage) }); - // projects that would be archived with the current selection - let projectsToArchive = $derived( + // projects that would be deleted with the current selection + const projectsToDelete = $derived( projects.filter((project) => !selectedProjects.includes(project.$id)) ); - function formatProjectsToArchive(): string { + function formatProjectsToDelete(): string { let result = ''; - projectsToArchive.forEach((project, index) => { - const isLast = index === projectsToArchive.length - 1; - const isSecondLast = index === projectsToArchive.length - 2; + projectsToDelete.forEach((project, index) => { + const isLast = index === projectsToDelete.length - 1; + const isSecondLast = index === projectsToDelete.length - 2; result += `${index === 0 ? '' : ' '}${project.name}`; @@ -114,10 +114,12 @@ error = `You must select exactly ${allowedProjectsToKeep} projects to keep.`; return; } - // Keep selection locally; parent flow will apply after plan change + + // Keep selection locally; + // parent flow will apply after plan change showSelectProject = false; showSelectionReminder = false; - addNotification({ type: 'success', message: `Projects selected for archiving` }); + addNotification({ type: 'success', message: `Projects selected for deleting` }); } @@ -298,8 +300,8 @@ difference > 1 ? `${difference} projects` : `${difference} project`} - {formatProjectsToArchive()} will be archived + title={`${messagePrefix} will be deleted on ${toLocaleDate($organization.billingNextInvoiceDate)}`}> + {formatProjectsToDelete()} will be deleted {/if} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index baf663114..f7aa5eff2 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -368,32 +368,6 @@ export function calculateTrialDay(org: Models.Organization) { return days; } -export async function checkForProjectsLimit(org: Models.Organization, orgProjectCount?: number) { - if (!isCloud) return; - if (!org) return; - - const plan = await sdk.forConsole.organizations.getPlan({ - organizationId: org.$id - }); - if (!plan) return; - - if (!org.projects) return; - if (org.projects.length > 0) return; - - const projectCount = orgProjectCount; - if (projectCount === undefined) return; - - // not unlimited and current exceeds plan limits! - if (plan.projects > 0 && projectCount > plan.projects) { - headerAlert.add({ - id: 'projectsLimitReached', - component: ProjectsLimit, - show: true, - importance: 12 - }); - } -} - export async function checkForUsageLimit(organization: Models.Organization) { if ( organization?.status === teamStatusReadonly && diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 1b0230829..a74ccc087 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -18,7 +18,6 @@ checkForMarkedForDeletion, checkForMissingPaymentMethod, checkForNewDevUpgradePro, - checkForProjectsLimit, checkForUsageLimit, checkPaymentAuthorizationRequired, paymentExpired, @@ -296,9 +295,6 @@ if (currentOrganizationId === org.$id) return; if (isCloud) { currentOrganizationId = org.$id; - const orgProjectCount = - data.currentOrgId === org.$id ? data.allProjectsCount : undefined; - await checkForProjectsLimit(org, orgProjectCount); checkForEnterpriseTrial(org); await checkForUsageLimit(org); checkForMarkedForDeletion(org); diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 947cdedca..2d1fd8de3 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -23,7 +23,7 @@ import { onMount, type ComponentType } from 'svelte'; import { canWriteProjects } from '$lib/stores/roles'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; - import { Alert, Badge, Icon, Layout, Tag, Tooltip, Typography } from '@appwrite.io/pink-svelte'; + import { Alert, Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte'; import { isSmallViewport } from '$lib/stores/viewport'; import { IconAndroid, @@ -38,14 +38,11 @@ import { getPlatformInfo } from '$lib/helpers/platform'; import CreateProjectCloud from './createProjectCloud.svelte'; import { regions as regionsStore } from '$lib/stores/organization'; - import SelectProjectCloud from '$lib/components/billing/alerts/selectProjectCloud.svelte'; - import ArchiveProject from '$lib/components/archiveProject.svelte'; let { data }: PageProps = $props(); let showCreate = $state(false); let addOrganization = $state(false); - let showSelectProject = $state(false); let showCreateProjectCloud = $state(false); let freePlanAlertDismissed = $state(false); @@ -129,18 +126,7 @@ return project.status === 'archived'; } - const projectsToArchive = $derived( - (data.archivedProjectsPage ?? data.projects.projects).filter( - (project) => project.status === 'archived' - ) - ); - - const activeTotalOverall = $derived( - data?.activeTotalOverall ?? - data?.organization?.projects?.length ?? - data?.projects?.total ?? - 0 - ); + const activeProjectsTotal = $derived(data?.projects.total); function clearSearch() { searchQuery?.clearInput(); @@ -162,11 +148,6 @@ }); - - @@ -196,30 +177,7 @@ {/if} - {#if isCloud && data.currentPlan?.projects && data.currentPlan?.projects > 0 && data.organization.projects.length > 0 && $canWriteProjects && (projectsToArchive.length > 0 || data.projects.total > data.currentPlan.projects)} - {@const difference = projectsToArchive.length} - {@const messagePrefix = - difference !== 1 ? `${difference} projects are` : `${difference} project is`} - - Upgrade your plan to restore archived projects - - - - - {/if} - - {#if isCloud && data.currentPlan?.projects !== 0 && projectsToArchive.length === 0 && !freePlanAlertDismissed} + {#if isCloud && data.currentPlan?.projects !== 0 && activeProjectsTotal <= data.currentPlan.projects && !freePlanAlertDismissed} Your Free plan includes up to 2 projects and limited resources. Upgrade to unlock @@ -244,7 +202,7 @@ {#if data.projects.total > 0} {#each data.projects.projects as project} @@ -271,18 +229,6 @@ - - {#if isSetToArchive(project)} - { - event.preventDefault(); - showSelectProject = true; - }}>Set to archive - {/if} - - {#each platforms.slice(0, 2) as platform} {@const icon = getIconForPlatform(platform.icon)} - - - + total={activeProjectsTotal} /> diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts index fc4256b03..9a6c85023 100644 --- a/src/routes/(console)/organization-[organization]/+page.ts +++ b/src/routes/(console)/organization-[organization]/+page.ts @@ -5,12 +5,17 @@ import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load'; import { CARD_LIMIT, Dependencies } from '$lib/constants'; import type { PageLoad } from './$types'; import { redirect } from '@sveltejs/kit'; -import { base } from '$app/paths'; +import { resolve } from '$app/paths'; export const load: PageLoad = async ({ params, url, route, depends, parent }) => { const { scopes } = await parent(); if (!scopes.includes('projects.read') && scopes.includes('billing.read')) { - return redirect(301, `${base}/organization-${params.organization}/billing`); + return redirect( + 301, + resolve('/(console)/organization-[organization]/billing', { + organization: params.organization + }) + ); } depends(Dependencies.ORGANIZATION); @@ -20,76 +25,33 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) => const offset = pageToOffset(page, limit); const search = getSearch(url); - const archivedPageRaw = parseInt(url.searchParams.get('archivedPage') || '1', 10); - const archivedPage = - Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1; - const archivedOffset = pageToOffset(archivedPage, limit); - const searchQueries = search ? [Query.or([Query.search('search', search), Query.contains('labels', search)])] : []; - const commonQueries = [Query.equal('teamId', params.organization)]; const activeQueries = isCloud ? [Query.or([Query.equal('status', 'active'), Query.isNull('status')])] : []; - const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([ - sdk.forConsole.projects.list({ - queries: [ - Query.offset(offset), - Query.limit(limit), - Query.orderDesc(''), - ...commonQueries, - ...searchQueries, - ...activeQueries - ] - }), - isCloud - ? sdk.forConsole.projects.list({ - queries: [ - Query.offset(archivedOffset), - Query.limit(limit), - Query.orderDesc(''), - ...commonQueries, - ...searchQueries, - Query.equal('status', 'archived') - ] - }) - : Promise.resolve({ projects: [], total: 0 }), - sdk.forConsole.projects.list({ - queries: [...commonQueries, ...activeQueries, ...searchQueries] - }), - isCloud - ? sdk.forConsole.projects.list({ - queries: [...commonQueries, ...searchQueries, Query.equal('status', 'archived')] - }) - : Promise.resolve({ projects: [], total: 0 }) - ]); + const activeProjects = await sdk.forConsole.projects.list({ + queries: [ + ...searchQueries, + ...activeQueries, + Query.offset(offset), + Query.limit(limit), + Query.orderDesc(''), + Query.equal('teamId', params.organization) + ] + }); // set `default` if no region! for (const project of activeProjects.projects) { project.region ??= 'default'; } - if (isCloud) { - for (const project of archivedProjects.projects) { - project.region ??= 'default'; - } - } return { - offset, limit, - projects: { - ...activeProjects, - projects: activeProjects.projects, - total: activeTotal.total - }, - activeProjectsPage: activeProjects.projects, - archivedProjectsPage: archivedProjects.projects, - activeTotalOverall: activeTotal.total, - archivedTotalOverall: archivedTotal.total, - archivedOffset, - archivedPage, - search + offset, + search, + projects: activeProjects }; }; diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index 0e06f1016..d55a49bd7 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -171,7 +171,7 @@ paymentMethodId }); - // 2) If the target plan has a project limit, apply selected projects now + // 2) If the plan has a project limit, delete excess const targetProjectsLimit = selectedPlan?.projects ?? 0; if (targetProjectsLimit > 0 && usageLimitsComponent) { const selected = usageLimitsComponent.getSelectedProjects(); From ec17b61a9d3686b7d93cb1533b0143f301d90c72 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 12:16:01 +0530 Subject: [PATCH 008/103] remove: archiving logic. update: redirect to org page when the project is not active. --- .../organization-[organization]/+page.svelte | 21 +------------------ .../project-[region]-[project]/+layout.ts | 10 +++++++++ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 2d1fd8de3..afa38a5ee 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -7,7 +7,6 @@ import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system'; import { page } from '$app/state'; import { registerCommands } from '$lib/commandCenter'; - import { formatName as formatNameHelper } from '$lib/helpers/string'; import { CardContainer, Empty, @@ -24,7 +23,6 @@ import { canWriteProjects } from '$lib/stores/roles'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { Alert, Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte'; - import { isSmallViewport } from '$lib/stores/viewport'; import { IconAndroid, IconApple, @@ -120,12 +118,6 @@ return $regionsStore.regions.find((region) => region.$id === project.region); } - function isSetToArchive(project: Models.Project): boolean { - if (!isCloud) return false; - if (!project || !project.$id) return false; - return project.status === 'archived'; - } - const activeProjectsTotal = $derived(data?.projects.total); function clearSearch() { @@ -209,24 +201,13 @@ {@const platforms = filterPlatforms( project.platforms.map((platform) => getPlatformInfo(platform.type)) )} - {@const formatted = isSetToArchive(project) - ? formatNameHelper(project.name, isSmallViewport ? 19 : 25) - : project.name} {project?.platforms?.length ? project?.platforms?.length : 'No'} apps - - {formatted} - - {project.name} - - + {project.name} {#each platforms.slice(0, 2) as platform} diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 26683edda..01700f379 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -18,6 +18,16 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.PROJECT); const project = await sdk.forConsole.projects.get({ projectId: params.project }); + if (project.status !== 'active') { + // project isn't active, redirect back to organizations page + redirect( + 303, + resolve('/(console)/organization-[organization]', { + organization: project.teamId + }) + ); + } + project.region ??= 'default'; // fast path without a network call! From c6efbc7d501013a8f1ff7dbdef801aa4dff7f2a7 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 12:19:56 +0530 Subject: [PATCH 009/103] fix: tests. --- src/lib/stores/billing.ts | 1 - src/routes/(console)/+layout.svelte | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index f7aa5eff2..50e3a3071 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -31,7 +31,6 @@ import { user } from './user'; import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budgetLimitAlert.svelte'; import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte'; -import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte'; import EnterpriseTrial from '$routes/(console)/organization-[organization]/enterpriseTrial.svelte'; export const roles = [ diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index a74ccc087..c7a11414c 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -38,7 +38,7 @@ import { showSupportModal } from './wizard/support/store'; import { activeHeaderAlert, consoleVariables } from './store'; - import { base } from '$app/paths'; + import { base, resolve } from '$app/paths'; import { headerAlert } from '$lib/stores/headerAlert'; import { UsageRates } from '$lib/components/billing'; import { canSeeProjects } from '$lib/stores/roles'; @@ -53,11 +53,8 @@ IconSparkles, IconSwitchHorizontal } from '@appwrite.io/pink-icons-svelte'; - import type { LayoutData } from './$types'; import type { Models } from '@appwrite.io/console'; - export let data: LayoutData; - function kebabToSentenceCase(str: string) { return str .split('-') @@ -74,9 +71,7 @@ $: $registerCommands([ { label: 'Go to Projects', - callback: () => { - goto(base); - }, + callback: () => goto(resolve('/')), keys: ['g', 'p'], group: 'navigation', disabled: From a6d5d68f4911af8a8c4fa41f4efc98c3462a174c Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 12:47:23 +0530 Subject: [PATCH 010/103] fix: text. --- src/lib/components/organizationUsageLimits.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 085abc596..0ed896c77 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -223,9 +223,7 @@ {:else} - {formatNumber(currentUsage.members)} / {formatNumber( - freePlanLimits.members - )} + N/A {/if} From f866e26138c54a02a20e7360f40f2095883580ef Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 14:05:04 +0530 Subject: [PATCH 011/103] update: warnings. --- .../components/organizationUsageLimits.svelte | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 0ed896c77..e19106b2c 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -222,9 +222,7 @@ {:else} - - N/A - + N/A {/if} @@ -261,10 +259,19 @@ {#if showSelectProject} - Choose which {freePlanLimits.projects} projects to keep. Projects over the limit will be - blocked after your billing cycle ends on {toLocaleDate( - $organization.billingNextInvoiceDate - )}. + + + Choose which {freePlanLimits.projects} projects to keep. + + + All data associated with unselected projects, including databases, storage, + functions, and users, will be permanently deleted on {toLocaleDate( + $organization.billingNextInvoiceDate + )}. This action is irreversible. + + {#if error} @@ -292,14 +299,17 @@ {/each} + {#if selectedProjects.length === allowedProjectsToKeep} {@const difference = projects.length - selectedProjects.length} {@const messagePrefix = difference > 1 ? `${difference} projects` : `${difference} project`} - {formatProjectsToDelete()} will be deleted + status="error" + title={`${messagePrefix} will be permanently deleted on ${toLocaleDate($organization.billingNextInvoiceDate)}`}> + {formatProjectsToDelete()} and all associated data, including databases, + storage, functions, and users, will be permanently deleted. + This action is irreversible. {/if} From d684dd66df05157689019d9fa889b12236e96d23 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Feb 2026 14:31:08 +0530 Subject: [PATCH 012/103] update: message. --- .../components/organizationUsageLimits.svelte | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index e19106b2c..7d1257b26 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -260,16 +260,13 @@ - - Choose which {freePlanLimits.projects} projects to keep. - - All data associated with unselected projects, including databases, storage, - functions, and users, will be permanently deleted on {toLocaleDate( - $organization.billingNextInvoiceDate - )}. This action is irreversible. + status="warning" + title="Select the {freePlanLimits.projects} projects you want to keep"> + Projects not kept, including all associated data, will be permanently deleted + on {toLocaleDate($organization.billingNextInvoiceDate)} and cannot be recovered. + This action is irreversible. @@ -307,8 +304,8 @@ ${toLocaleDate($organization.billingNextInvoiceDate)}`}> - {formatProjectsToDelete()} and all associated data, including databases, - storage, functions, and users, will be permanently deleted. + {formatProjectsToDelete()} and all associated data, will be + permanently deleted. This action is irreversible. {/if} From 100a967f3438ecbfed0caac5fe02306f5cb7cb41 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 5 Feb 2026 13:14:14 +0000 Subject: [PATCH 013/103] feat: add paused project modal and update console access tracking logic --- bun.lock | 5 +- package.json | 2 +- .../project-[region]-[project]/+layout.svelte | 32 ++++++++ .../project-[region]-[project]/+layout.ts | 34 ++++++--- .../pausedProjectModal.svelte | 76 +++++++++++++++++++ 5 files changed, 136 insertions(+), 13 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte diff --git a/bun.lock b/bun.lock index 5c92cd04d..1308ed221 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@7dc3a8f", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@110a478", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@c1feb89", "@appwrite.io/pink-legacy": "^1.0.3", @@ -24,6 +24,7 @@ "dayjs": "^1.11.13", "deep-equal": "^2.2.3", "echarts": "^5.6.0", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@110a478", "ignore": "^6.0.2", "nanoid": "^5.1.5", "nanotar": "^0.1.1", @@ -107,7 +108,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@7dc3a8f", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@110a478", { "dependencies": { "bignumber.js": "9.0.0", "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 985efb2fa..75c0cecca 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@7dc3a8f", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@110a478", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@c1feb89", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index eeeff2dd7..5c55169cb 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -25,6 +25,34 @@ canWriteSites } from '$lib/stores/roles'; import CsvImportBox from '$lib/components/csvImportBox.svelte'; + import { currentPlan } from '$lib/stores/organization'; + import { isCloud } from '$lib/system'; + import PausedProjectModal from './pausedProjectModal.svelte'; + + /** + * Calculate if the project is paused based on console access date and plan's inactivity threshold. + */ + function isProjectPaused( + consoleAccessedAt: string | null | undefined, + projectInactivityDays: number | undefined + ): boolean { + if (!isCloud) return false; + if (!projectInactivityDays || projectInactivityDays <= 0) return false; + if (!consoleAccessedAt) return false; + + const lastAccess = new Date(consoleAccessedAt); + const now = new Date(); + const diffMs = now.getTime() - lastAccess.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + return diffDays >= projectInactivityDays; + } + + let showPausedModal: boolean; + $: showPausedModal = isProjectPaused( + ($project as { consoleAccessedAt?: string })?.consoleAccessedAt, + $currentPlan?.projectInactivityDays + ); onMount(() => { return realtime.forProject(page.params.region, ['project', 'console'], (response) => { @@ -114,6 +142,10 @@ +{#if isCloud} + +{/if} +
diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index bbb433100..568f6cf07 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -21,16 +21,6 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { const project = await sdk.forConsole.projects.get({ projectId: params.project }); project.region ??= 'default'; - // Track console access for cloud only (fire-and-forget, backend has 6-day cooldown) - if (isCloud) { - generateFingerprintToken() - .then((fingerprint) => { - sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; - return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); - }) - .catch(() => {}); - } - // fast path without a network call! let organization = (organizations as Models.OrganizationList)?.teams?.find( (org) => org.$id === project.teamId @@ -103,6 +93,30 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { plansInfo.set(organization.billingPlanId, organizationPlan); } + // Track console access for cloud only (fire-and-forget, backend has 6-day cooldown) + // Don't call if project is paused - user must explicitly resume via createConsoleAccess + if (isCloud) { + const projectInactivityDays = organizationPlan?.projectInactivityDays ?? 0; + const consoleAccessedAt = (project as { consoleAccessedAt?: string }).consoleAccessedAt; + + let isPaused = false; + if (projectInactivityDays > 0 && consoleAccessedAt) { + const lastAccess = new Date(consoleAccessedAt); + const now = new Date(); + const diffDays = Math.floor((now.getTime() - lastAccess.getTime()) / (1000 * 60 * 60 * 24)); + isPaused = diffDays >= projectInactivityDays; + } + + if (!isPaused) { + generateFingerprintToken() + .then((fingerprint) => { + sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; + return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); + }) + .catch(() => {}); + } + } + return { project, organization, diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte new file mode 100644 index 000000000..653f02631 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -0,0 +1,76 @@ + + + + + + This project has been paused due to inactivity on the free plan. + + + Your data is safe and will remain intact. Resume the project to continue using it. + + + {#if error} + (error = null)}> + {error} + + {/if} + + + + + + + + From a080abe54ec89c032eb71ce1a4ca04f3a4e4d4fa Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 8 Feb 2026 12:03:45 +0000 Subject: [PATCH 014/103] feat: update active project query to include paused status --- src/routes/(console)/organization-[organization]/+page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts index fc4256b03..72fff67db 100644 --- a/src/routes/(console)/organization-[organization]/+page.ts +++ b/src/routes/(console)/organization-[organization]/+page.ts @@ -30,7 +30,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) => : []; const commonQueries = [Query.equal('teamId', params.organization)]; const activeQueries = isCloud - ? [Query.or([Query.equal('status', 'active'), Query.isNull('status')])] + ? [Query.or([Query.equal('status', ['active', 'paused']), Query.isNull('status')])] : []; const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([ From 83bd6af87351610c229db88bfe9f9800f61a7d50 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 8 Feb 2026 12:07:30 +0000 Subject: [PATCH 015/103] feat: add paused project status indication and modal integration --- .../organization-[organization]/+page.svelte | 8 +++++- .../project-[region]-[project]/+layout.svelte | 25 +------------------ 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 947cdedca..faec27b84 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -29,6 +29,7 @@ IconAndroid, IconApple, IconCode, + IconExclamationCircle, IconFlutter, IconPlus, IconReact, @@ -272,7 +273,12 @@ - {#if isSetToArchive(project)} + {#if project.status === 'paused'} + + + Paused + + {:else if isSetToArchive(project)} = projectInactivityDays; - } - let showPausedModal: boolean; - $: showPausedModal = isProjectPaused( - ($project as { consoleAccessedAt?: string })?.consoleAccessedAt, - $currentPlan?.projectInactivityDays - ); + $: showPausedModal = isCloud && $project?.status === 'paused'; onMount(() => { return realtime.forProject(page.params.region, ['project', 'console'], (response) => { From 4148c69fc928ecae5ea452b59c78bb4cf0121fd5 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 8 Feb 2026 12:09:01 +0000 Subject: [PATCH 016/103] fix: update paused project modal message for clarity --- .../project-[region]-[project]/pausedProjectModal.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 653f02631..701bddd35 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -49,7 +49,7 @@ - This project has been paused due to inactivity on the free plan. + This project has been paused due to inactivity. Your data is safe and will remain intact. Resume the project to continue using it. From cb13e06b6d81d2c647ab181c81f57946c529c9fd Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Sun, 8 Feb 2026 12:24:45 +0000 Subject: [PATCH 017/103] feat: update paused project modal integration to use layout data --- .../(console)/project-[region]-[project]/+layout.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index f1d18cd07..65031a8fc 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -27,9 +27,9 @@ import CsvImportBox from '$lib/components/csvImportBox.svelte'; import { isCloud } from '$lib/system'; import PausedProjectModal from './pausedProjectModal.svelte'; + import type { LayoutData } from './$types'; - let showPausedModal: boolean; - $: showPausedModal = isCloud && $project?.status === 'paused'; + export let data: LayoutData; onMount(() => { return realtime.forProject(page.params.region, ['project', 'console'], (response) => { @@ -119,8 +119,8 @@ -{#if isCloud} - +{#if isCloud && data.project?.status === 'paused'} + {/if}
From 928a31bb11ab22f9df04ce202887d78cc6e1503a Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 11 Feb 2026 15:00:14 +0530 Subject: [PATCH 018/103] fix: make project limit alert program-aware --- .../(console)/organization-[organization]/+page.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 947cdedca..e10aa76c9 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -219,11 +219,11 @@ {/if} - {#if isCloud && data.currentPlan?.projects !== 0 && projectsToArchive.length === 0 && !freePlanAlertDismissed} + {#if isCloud && !data.program && data.currentPlan?.projects !== 0 && projectsToArchive.length === 0 && !freePlanAlertDismissed} Your Free plan includes up to 2 projects and limited resources. Upgrade to unlock - more capacity and features. + >Your Free plan includes up to {data.currentPlan?.projects} projects and limited resources. + Upgrade to unlock more capacity and features. From c94e6f1ecf82d343ef39ed5b5ba16f5737f03cad Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 12 Feb 2026 13:48:47 +0530 Subject: [PATCH 020/103] delete projects. --- .../organization-[organization]/change-plan/+page.svelte | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index d55a49bd7..ab32bd996 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -176,11 +176,12 @@ if (targetProjectsLimit > 0 && usageLimitsComponent) { const selected = usageLimitsComponent.getSelectedProjects(); if (selected?.length) { + const projectsDeletionPromise = selected.map((projectId) => { + return sdk.forConsole.projects.delete({ projectId }); + }); + try { - await sdk.forConsole.organizations.updateProjects({ - organizationId: data.organization.$id, - projects: selected - }); + await Promise.all(projectsDeletionPromise); } catch (projectError) { console.warn('Project selection failed after plan update:', projectError); } From 947f78ee268dfc1b5a29a1130f58fe9388525f3e Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 12 Feb 2026 14:29:39 +0530 Subject: [PATCH 021/103] fix: inverted logic. --- .../components/organizationUsageLimits.svelte | 24 ++++++++++--------- .../change-plan/+page.svelte | 8 +++---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 7d1257b26..af4f7d6ed 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -101,8 +101,8 @@ return isValid; } - export function getSelectedProjects(): string[] { - return selectedProjects.filter((id) => projects.some((p) => p.$id === id)); + export function getProjectsToDelete(): string[] { + return projectsToDelete.map((project) => project.$id); } function updateSelected() { @@ -119,7 +119,7 @@ // parent flow will apply after plan change showSelectProject = false; showSelectionReminder = false; - addNotification({ type: 'success', message: `Projects selected for deleting` }); + addNotification({ type: 'success', message: `Projects selected` }); } @@ -263,10 +263,10 @@ - Projects not kept, including all associated data, will be permanently deleted - on {toLocaleDate($organization.billingNextInvoiceDate)} and cannot be recovered. - This action is irreversible. + Projects not kept, including all associated data, will be + permanently deleted on {toLocaleDate( + $organization.billingNextInvoiceDate + )} and cannot be recovered. This action is irreversible. @@ -297,13 +297,13 @@
- {#if selectedProjects.length === allowedProjectsToKeep} + {#if selectedProjects.length && selectedProjects.length <= allowedProjectsToKeep} {@const difference = projects.length - selectedProjects.length} {@const messagePrefix = difference > 1 ? `${difference} projects` : `${difference} project`} ${toLocaleDate($organization.billingNextInvoiceDate)}`}> + title={`${messagePrefix} will be permanently deleted on ${toLocaleDate($organization.billingNextInvoiceDate)}`}> {formatProjectsToDelete()} and all associated data, will be permanently deleted. This action is irreversible. @@ -312,8 +312,10 @@ - +
{/if} diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index ddaa8af88..c0d59bf57 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -47,7 +47,7 @@ let showExitModal = false; let formComponent: Form; let usageLimitsComponent: - | { validateOrAlert: () => boolean; getSelectedProjects: () => string[] } + | { validateOrAlert: () => boolean; getProjectsToDelete: () => string[] } | undefined; let isSubmitting = writable(false); let collaborators: string[] = @@ -180,9 +180,9 @@ // 2) If the plan has a project limit, delete excess const targetProjectsLimit = selectedPlan?.projects ?? 0; if (targetProjectsLimit > 0 && usageLimitsComponent) { - const selected = usageLimitsComponent.getSelectedProjects(); - if (selected?.length) { - const projectsDeletionPromise = selected.map((projectId) => { + const projectsToDelete = usageLimitsComponent.getProjectsToDelete(); + if (projectsToDelete?.length) { + const projectsDeletionPromise = projectsToDelete.map((projectId) => { return sdk.forConsole.projects.delete({ projectId }); }); From 2f317447fbc1117e5b4b242ef3678145d35b7d5e Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 12 Feb 2026 18:27:18 +0530 Subject: [PATCH 022/103] feat: auto-fill detected env variables for sites/functions --- .../variables}/createVariableModal.svelte | 5 +- .../variables}/deleteVariableModal.svelte | 0 .../variables/environmentVariables.svelte | 260 ++++++++++++++++++ .../variables/importVariablesModal.svelte} | 0 .../variables}/secretVariableModal.svelte | 0 .../variables}/updateVariableModal.svelte | 4 +- .../variables}/variableEditorModal.svelte | 8 +- .../repository-[repository]/+page.svelte | 49 +++- .../configuration.svelte | 11 + .../sites/create-site/configuration.svelte | 234 +--------------- .../repository-[repository]/+page.svelte | 48 ++++ 11 files changed, 380 insertions(+), 239 deletions(-) rename src/{routes/(console)/project-[region]-[project]/sites/create-site => lib/components/variables}/createVariableModal.svelte (97%) rename src/{routes/(console)/project-[region]-[project]/sites/create-site => lib/components/variables}/deleteVariableModal.svelte (100%) create mode 100644 src/lib/components/variables/environmentVariables.svelte rename src/{routes/(console)/project-[region]-[project]/sites/create-site/importSiteVariablesModal.svelte => lib/components/variables/importVariablesModal.svelte} (100%) rename src/{routes/(console)/project-[region]-[project]/sites/create-site => lib/components/variables}/secretVariableModal.svelte (100%) rename src/{routes/(console)/project-[region]-[project]/sites/create-site => lib/components/variables}/updateVariableModal.svelte (94%) rename src/{routes/(console)/project-[region]-[project]/sites/create-site => lib/components/variables}/variableEditorModal.svelte (96%) diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/createVariableModal.svelte b/src/lib/components/variables/createVariableModal.svelte similarity index 97% rename from src/routes/(console)/project-[region]-[project]/sites/create-site/createVariableModal.svelte rename to src/lib/components/variables/createVariableModal.svelte index 8dc28a966..3f7817ecf 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/createVariableModal.svelte +++ b/src/lib/components/variables/createVariableModal.svelte @@ -11,6 +11,7 @@ export let show = false; export let variables: Partial[]; + export let productLabel = 'site'; let newVariables: Partial[] = [{ key: '', value: '' }]; let secret = false; @@ -65,8 +66,8 @@ - Set the environment variables or secret that will be passed to your site. Global variables - can be set in project settings + import { Empty, Paginator } from '$lib/components'; + import { Button } from '$lib/elements/forms'; + import { + ActionMenu, + Accordion, + Badge, + InteractiveText, + Icon, + Layout, + Popover, + Skeleton, + Table, + Tooltip, + Button as PinkButton + } from '@appwrite.io/pink-svelte'; + import { + IconDotsHorizontal, + IconCode, + IconUpload, + IconPlus, + IconTrash, + IconEyeOff, + IconPencil + } from '@appwrite.io/pink-icons-svelte'; + import type { Models } from '@appwrite.io/console'; + import VariableEditorModal from './variableEditorModal.svelte'; + import SecretVariableModal from './secretVariableModal.svelte'; + import ImportVariablesModal from './importVariablesModal.svelte'; + import CreateVariableModal from './createVariableModal.svelte'; + import DeleteVariableModal from './deleteVariableModal.svelte'; + import UpdateVariableModal from './updateVariableModal.svelte'; + import { Click, trackEvent } from '$lib/actions/analytics'; + + export let variables: Partial[] = []; + export let productLabel = 'site'; + export let docsLink = + 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables'; + export let analyticsSource = 'site_configuration'; + export let analyticsCreateSource = 'site_settings'; + export let isLoading = false; + + let showEditorModal = false; + let showImportModal = false; + let showSecretModal = false; + let showCreate = false; + let showUpdate = false; + let showDelete = false; + + let currentVariable: Partial; + + $: createSource = analyticsCreateSource || analyticsSource; + + + + + Set up environment variables to securely manage keys and settings for your project. + + + + + + + {#if variables?.length} + + {/if} + + + {#if isLoading && !variables?.length} + + + Key + Value + + + {#each Array(3) as _} + + + + + + + + + + + + {/each} + + {:else if variables?.length} + + {#snippet children(paginatedItems)} + + + Key + Value + + + {#each paginatedItems as variable} + + {variable.key} + + +
+ {#if variable.secret} + + + + This value is secret, you cannot see its + value. + + + {:else} + + {/if} +
+
+ +
+ + { + e.preventDefault(); + toggle(e); + }}> + + + + + + {#if !variable?.secret} + { + toggle(e); + currentVariable = variable; + showUpdate = true; + }}> + Update + + {/if} + {#if !variable?.secret} + { + toggle(e); + currentVariable = variable; + showSecretModal = true; + }}> + Secret + + {/if} + { + toggle(e); + currentVariable = variable; + showDelete = true; + }}> + Delete + + + + +
+
+
+ {/each} +
+ {/snippet} +
+ {:else} + (showCreate = true)}>Create variables to get started + {/if} +
+
+
+ +{#if showEditorModal} + +{/if} + +{#if showSecretModal} + +{/if} + +{#if showImportModal} + +{/if} + +{#if showCreate} + +{/if} +{#if showUpdate} + +{/if} + +{#if showDelete} + +{/if} diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/importSiteVariablesModal.svelte b/src/lib/components/variables/importVariablesModal.svelte similarity index 100% rename from src/routes/(console)/project-[region]-[project]/sites/create-site/importSiteVariablesModal.svelte rename to src/lib/components/variables/importVariablesModal.svelte diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/secretVariableModal.svelte b/src/lib/components/variables/secretVariableModal.svelte similarity index 100% rename from src/routes/(console)/project-[region]-[project]/sites/create-site/secretVariableModal.svelte rename to src/lib/components/variables/secretVariableModal.svelte diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/updateVariableModal.svelte b/src/lib/components/variables/updateVariableModal.svelte similarity index 94% rename from src/routes/(console)/project-[region]-[project]/sites/create-site/updateVariableModal.svelte rename to src/lib/components/variables/updateVariableModal.svelte index c678007f9..bf48d8b62 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/updateVariableModal.svelte +++ b/src/lib/components/variables/updateVariableModal.svelte @@ -11,6 +11,7 @@ export let show = false; export let selectedVar: Partial; export let variables: Partial[]; + export let productLabel = 'site'; let pair = { $id: selectedVar?.$id, @@ -40,7 +41,8 @@ - Update the environment variable for your site. Global variables can be set in project settings[]; + export let docsLink = + 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables'; const editableVariables = variables.filter((variable) => !variable.secret); const secretVariables = variables.filter((variable) => variable.secret); @@ -122,11 +124,7 @@ {#if secretVariables?.length > 0} {secretVariables.length} secret variables are hidden from the editor. Their values will - remain unchanged. Learn more. + remain unchanged. Learn more. {/if} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index 9a06f70dd..a9233a368 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -59,6 +59,45 @@ let detectingRuntime = true; + type DetectedVariable = { + key?: string; + name?: string; + value?: string; + secret?: boolean; + }; + + function normalizeDetectedVariables(detected: DetectedVariable[] = []) { + const normalized: Partial[] = []; + detected.forEach((variable) => { + const key = variable.key ?? variable.name; + if (!key) { + return; + } + normalized.push({ + key, + value: variable.value ?? '', + secret: variable.secret ?? false + }); + }); + return normalized; + } + + function mergeVariables( + existing: Partial[], + detected: Partial[] + ) { + const map = new Map(existing.map((variable) => [variable.key, variable])); + detected.forEach((variable) => { + if (!variable.key) { + return; + } + if (!map.has(variable.key)) { + map.set(variable.key, variable); + } + }); + return Array.from(map.values()); + } + onMount(async () => { installation.set(data.installation); repository.set(data.repository); @@ -82,6 +121,10 @@ entrypoint = detections.entrypoint; buildCommand = detections.commands; runtime = detections.runtime as Runtime; + const detectedVariables = normalizeDetectedVariables(detections?.variables); + if (detectedVariables.length) { + variables = mergeVariables(variables, detectedVariables); + } trackEvent(Submit.FrameworkDetect, { runtime, source: 'repository' }); } catch (error) { @@ -189,7 +232,11 @@ installationId={data.installation.$id} repositoryId={data.repository.id} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/configuration.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/configuration.svelte index 8f3091f39..90f413113 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/configuration.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/configuration.svelte @@ -3,9 +3,13 @@ import { Link } from '$lib/elements'; import { InputText } from '$lib/elements/forms'; import { Accordion, Fieldset, Layout } from '@appwrite.io/pink-svelte'; + import type { Models } from '@appwrite.io/console'; + import EnvironmentVariables from '$lib/components/variables/environmentVariables.svelte'; export let buildCommand = ''; export let roles: string[] = []; + export let variables: Partial[] = []; + export let isVariablesLoading = false;
@@ -31,5 +35,12 @@ +
diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte index dc5d0a140..97ddee3da 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte @@ -1,38 +1,10 @@ @@ -257,65 +241,85 @@ {#if showSelectProject} - - - - - Projects not kept, including all associated data, will be - permanently deleted on {toLocaleDate( - $organization.billingNextInvoiceDate - )} and cannot be recovered. This action is irreversible. - - - + {@const requiredToDelete = currentUsage.projects - allowedProjectsToKeep} + + + The Free plan lets you keep {allowedProjectsToKeep} projects. Select projects you want to + permanently delete. + {#if error} {error} + {:else} + + The selected projects and all associated data will be permanently deleted and cannot + be recovered. + {/if} -
- - - Project Name - Created - - {#each projects as project} - - {project.name} - - {toLocaleDateTime(project.$createdAt)} - - - {/each} - -
+ + + Select {requiredToDelete} project{requiredToDelete !== 1 ? 's' : ''} to delete - {#if selectedProjects.length && selectedProjects.length <= allowedProjectsToKeep} - {@const difference = projects.length - selectedProjects.length} - {@const messagePrefix = - difference > 1 ? `${difference} projects` : `${difference} project`} - - {formatProjectsToDelete()} and all associated data, will be - permanently deleted. - This action is irreversible. - + + + +
+ + + Project Name + Created + + {#each projects as project} + {@const isRowSelected = selectedProjectsToDelete.includes(project.$id)} + {@const shouldDisable = + !isRowSelected && selectedProjectsToDelete.length >= requiredToDelete} + + {project.name} + + {toLocaleDateTime(project.$createdAt)} + + + {/each} + +
+
+ + {#if selectedProjectsToDelete.length >= requiredToDelete} + {/if} + danger + submissionLoader + forceShowLoader={isDeletingProjects} + disabled={selectedProjectsToDelete.length < requiredToDelete || + !hasConfirmedSelection}>Delete projects
{/if} @@ -361,4 +365,14 @@ min-width: 96px; } } + + .controlled-selection :global([role='rowheader']) { + pointer-events: none; + + :global([role='cell']) { + opacity: 0.5; + cursor: not-allowed; + color: var(--fgcolor-neutral-secondary); + } + } diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index c0d59bf57..af7c00c68 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -46,9 +46,6 @@ let previousPage: string = resolve('/'); let showExitModal = false; let formComponent: Form; - let usageLimitsComponent: - | { validateOrAlert: () => boolean; getProjectsToDelete: () => string[] } - | undefined; let isSubmitting = writable(false); let collaborators: string[] = data?.members?.memberships @@ -134,16 +131,6 @@ async function handleSubmit() { if (isDowngrade) { - // If target plan has a non-zero project limit, ensure selection made - const targetProjectsLimit = selectedPlan?.projects ?? 0; - const shouldShowProjectSelector = - targetProjectsLimit > 0 && allProjects.projects.length > targetProjectsLimit; - - if (shouldShowProjectSelector && usageLimitsComponent?.validateOrAlert) { - const ok = usageLimitsComponent.validateOrAlert(); - if (!ok) return; - } - await downgrade(); } else if (isUpgrade) { await upgrade(); @@ -177,23 +164,6 @@ paymentMethodId }); - // 2) If the plan has a project limit, delete excess - const targetProjectsLimit = selectedPlan?.projects ?? 0; - if (targetProjectsLimit > 0 && usageLimitsComponent) { - const projectsToDelete = usageLimitsComponent.getProjectsToDelete(); - if (projectsToDelete?.length) { - const projectsDeletionPromise = projectsToDelete.map((projectId) => { - return sdk.forConsole.projects.delete({ projectId }); - }); - - try { - await Promise.all(projectsDeletionPromise); - } catch (projectError) { - console.warn('Project selection failed after plan update:', projectError); - } - } - } - await Promise.all([trackDowngradeFeedback(), invalidate(Dependencies.ORGANIZATION)]); await goto(previousPage); @@ -402,7 +372,6 @@ {/if} Date: Fri, 13 Feb 2026 13:22:47 +0530 Subject: [PATCH 026/103] update: in-place update. --- .../components/organizationUsageLimits.svelte | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 44b1d38de..133af9d0f 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -12,6 +12,8 @@ import { BillingPlanGroup, type Models } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { addNotification } from '$lib/stores/notifications'; + import { invalidate } from '$app/navigation'; + import { Dependencies } from '$lib/constants'; const { projects = [], @@ -30,6 +32,7 @@ let hasConfirmedSelection = $state(false); let isDeletingProjects = $state(false); + let deletedProjectIds = $state>(new Set()); let selectedProjectsToDelete = $state>([]); const baseFreePlan = getBasePlanFromGroup(BillingPlanGroup.Starter); @@ -43,8 +46,12 @@ // When preparing to downgrade to Free, enforce Free plan limit locally (2) const allowedProjectsToKeep = $derived(freePlanLimits.projects); + const filteredProjects = $derived( + projects.filter((project) => !deletedProjectIds.has(project.$id)) + ); + const currentUsage = $derived({ - projects: projects?.length || 0, + projects: filteredProjects?.length || 0, members: members?.length || 0, storage: storageUsage || 0 }); @@ -86,18 +93,46 @@ } if (selectedProjectsToDelete?.length) { - const projectsDeletionPromise = selectedProjectsToDelete.map((projectId) => { - return sdk.forConsole.projects.delete({ projectId }); - }); + const projectsDeletionPromises = selectedProjectsToDelete.map((projectId) => ({ + projectId, + promise: sdk.forConsole.projects.delete({ projectId }) + })); try { - await Promise.all(projectsDeletionPromise); - addNotification({ - type: 'success', - message: 'Selected projects were deleted' + const results = await Promise.allSettled( + projectsDeletionPromises.map((p) => p.promise) + ); + + const failed: string[] = []; + const successfullyDeleted: string[] = []; + + results.forEach((result, index) => { + const projectId = projectsDeletionPromises[index].projectId; + if (result.status === 'fulfilled') { + successfullyDeleted.push(projectId); + } else { + failed.push(projectId); + } }); - showSelectProject = false; - showSelectionReminder = false; + + if (successfullyDeleted.length > 0) { + deletedProjectIds = new Set([...deletedProjectIds, ...successfullyDeleted]); + await invalidate(Dependencies.ORGANIZATION); + + addNotification({ + type: 'success', + message: `${successfullyDeleted.length} project${successfullyDeleted.length !== 1 ? 's' : ''} deleted successfully` + }); + } + + if (failed.length > 0) { + error = `Failed to delete ${failed.length} project${failed.length !== 1 ? 's' : ''}`; + } else { + showSelectProject = false; + selectedProjectsToDelete = []; + hasConfirmedSelection = false; + showSelectionReminder = false; + } } catch (exception) { error = exception.message; } finally { From 8d79c51c530c4801c9dafec991a9403e7fff1f82 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 13 Feb 2026 14:13:57 +0530 Subject: [PATCH 027/103] update: harder checks. --- .../change-plan/+page.svelte | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index af7c00c68..df367c9d8 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -129,6 +129,13 @@ return paymentMethods; } + function hasExcessProjectsForFreePlan(): boolean { + const freeBasePlan = getBasePlanFromGroup(BillingPlanGroup.Starter); + const freePlanProjectLimit = freeBasePlan?.projects ?? 2; + const currentProjectCount = allProjects?.projects?.length ?? 0; + return currentProjectCount > freePlanProjectLimit; + } + async function handleSubmit() { if (isDowngrade) { await downgrade(); @@ -156,6 +163,18 @@ } async function downgrade() { + if (selectedPlan.group === BillingPlanGroup.Starter && hasExcessProjectsForFreePlan()) { + const freeBasePlan = getBasePlanFromGroup(BillingPlanGroup.Starter); + const freePlanProjectLimit = freeBasePlan?.projects ?? 2; + const currentProjectCount = allProjects?.projects?.length ?? 0; + + addNotification({ + type: 'error', + message: `Please delete ${currentProjectCount - freePlanProjectLimit} project${currentProjectCount - freePlanProjectLimit !== 1 ? 's' : ''} before downgrading` + }); + return; + } + try { // 1) update the plan first await sdk.forConsole.organizations.updatePlan({ @@ -290,9 +309,20 @@ $: isUpgrade = selectedPlan.order > $currentPlan?.order; $: isDowngrade = selectedPlan.order < $currentPlan?.order; - $: isButtonDisabled = - $organization?.billingPlanId === selectedPlan.$id || - (isDowngrade && selectedPlan.group === BillingPlanGroup.Starter && data.hasFreeOrgs); + + // Check if projects exceed Free plan limit when downgrading + $: isButtonDisabled = (() => { + if ($organization?.billingPlanId === selectedPlan.$id) return true; + if (isDowngrade && selectedPlan.group === BillingPlanGroup.Starter && data.hasFreeOrgs) + return true; + + // Check for excess projects when downgrading to Free plan + return ( + isDowngrade && + selectedPlan.group === BillingPlanGroup.Starter && + hasExcessProjectsForFreePlan() + ); + })(); From 520c491cd4867c7defff61c1085f1470c17d6f58 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 13 Feb 2026 08:52:24 +0000 Subject: [PATCH 028/103] fix: ensure fingerprint header is removed after console access creation --- src/routes/(console)/project-[region]-[project]/+layout.ts | 5 ++++- .../project-[region]-[project]/pausedProjectModal.svelte | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 568f6cf07..3918af3d9 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -113,7 +113,10 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); }) - .catch(() => {}); + .catch(() => {}) + .finally(() => { + delete sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint']; + }); } } diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 701bddd35..5450dd13d 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -22,7 +22,11 @@ const fingerprint = await generateFingerprintToken(); sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; - await sdk.forConsole.projects.createConsoleAccess({ projectId }); + try { + await sdk.forConsole.projects.createConsoleAccess({ projectId }); + } finally { + delete sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint']; + } addNotification({ type: 'success', From e04dfa3682b5258abf3b55de9edc3baf511d72e9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Fri, 13 Feb 2026 09:35:44 +0000 Subject: [PATCH 029/103] fixes and sdk update --- bun.lock | 4 +- package.json | 2 +- src/lib/helpers/fingerprint.ts | 70 +++++++++---------- .../pausedProjectModal.svelte | 13 ++-- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/bun.lock b/bun.lock index 65b6269c4..c57726177 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e264dec", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@2f43d8c", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -107,7 +107,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@e264dec", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@2f43d8c", { "dependencies": { "bignumber.js": "9.0.0", "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 e1b391569..5f332ba5d 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@e264dec", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@2f43d8c", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/lib/helpers/fingerprint.ts b/src/lib/helpers/fingerprint.ts index 4a0ba3988..5058ea38e 100644 --- a/src/lib/helpers/fingerprint.ts +++ b/src/lib/helpers/fingerprint.ts @@ -69,52 +69,46 @@ function getWebGLFingerprint(): string { } } -function getAudioFingerprint(): Promise { - return new Promise((resolve) => { - try { - const AudioContext = - window.AudioContext || - (window as unknown as { webkitAudioContext: typeof window.AudioContext }) - .webkitAudioContext; - if (!AudioContext) { - resolve(''); - return; - } +async function getAudioFingerprint(): Promise { + try { + const OfflineCtx = + window.OfflineAudioContext || + (window as unknown as { webkitOfflineAudioContext: typeof OfflineAudioContext }) + .webkitOfflineAudioContext; + if (!OfflineCtx) return ''; - const context = new AudioContext(); - const oscillator = context.createOscillator(); - const analyser = context.createAnalyser(); - const gain = context.createGain(); - const processor = context.createScriptProcessor(4096, 1, 1); + const sampleRate = 44100; + const length = 4096; + const context = new OfflineCtx(1, length, sampleRate); - gain.gain.value = 0; - oscillator.type = 'triangle'; - oscillator.frequency.value = 10000; + const oscillator = context.createOscillator(); + oscillator.type = 'triangle'; + oscillator.frequency.value = 10000; - oscillator.connect(analyser); - analyser.connect(processor); - processor.connect(gain); - gain.connect(context.destination); + const compressor = context.createDynamicsCompressor(); + compressor.threshold.value = -50; + compressor.knee.value = 40; + compressor.ratio.value = 12; + compressor.attack.value = 0; + compressor.release.value = 0.25; - oscillator.start(0); + oscillator.connect(compressor); + compressor.connect(context.destination); - const dataArray = new Float32Array(analyser.frequencyBinCount); - analyser.getFloatFrequencyData(dataArray); + oscillator.start(0); - let sum = 0; - for (let i = 0; i < dataArray.length; i++) { - sum += Math.abs(dataArray[i]); - } + const buffer = await context.startRendering(); + const samples = buffer.getChannelData(0); - oscillator.stop(); - processor.disconnect(); - context.close(); - - resolve(sum.toString()); - } catch { - resolve(''); + let sum = 0; + for (let i = 0; i < samples.length; i++) { + sum += Math.abs(samples[i]); } - }); + + return sum.toString(); + } catch { + return ''; + } } interface StaticSignals { diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 5450dd13d..6ff7e8aae 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -8,11 +8,16 @@ import { generateFingerprintToken } from '$lib/helpers/fingerprint'; import { Alert, Layout, Modal, Typography } from '@appwrite.io/pink-svelte'; - export let show = false; - export let projectId: string; + let { + show = $bindable(false), + projectId + }: { + show: boolean; + projectId: string; + } = $props(); - let loading = false; - let error: string | null = null; + let loading = $state(false); + let error: string | null = $state(null); async function handleResume() { loading = true; From 53a993f0cb9d9b06e717ee7f8687808e33d8ab51 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 13 Feb 2026 15:14:30 +0530 Subject: [PATCH 030/103] update: harder checks. --- .../components/organizationUsageLimits.svelte | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 133af9d0f..90df40665 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -1,5 +1,5 @@ @@ -403,7 +402,7 @@ {/if} From b84ecd309b5ab556956ea140cbe0e2a3efe33485 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 13 Feb 2026 16:21:23 +0530 Subject: [PATCH 032/103] fix: icon. fix: wrong format on event, temp patch. --- src/lib/elements/forms/inputNumber.svelte | 12 ++++++++++++ .../table-[table]/columns/+page.svelte | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/elements/forms/inputNumber.svelte b/src/lib/elements/forms/inputNumber.svelte index 0a465e505..a19f2d7ce 100644 --- a/src/lib/elements/forms/inputNumber.svelte +++ b/src/lib/elements/forms/inputNumber.svelte @@ -20,6 +20,18 @@ let error: string; + // TODO: Remove this once Pink Svelte is fixed + $: if (value !== null && typeof value === 'object' && 'target' in (value as object)) { + const event = value as Event; + const target = event.target as HTMLInputElement; + if (target?.value !== undefined) { + const parsedValue = target.value === '' ? null : Number(target.value); + value = Number.isNaN(parsedValue) ? null : parsedValue; + } else { + value = null; + } + } + const handleInvalid = (event: Event & { currentTarget: EventTarget & HTMLInputElement }) => { event.preventDefault(); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte index e300431a3..b6b58e878 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte @@ -393,7 +393,11 @@ {#each updatedColumnsForSheet as column, index (column.key)} {@const isId = column.key === '$id'} - {@const option = columnOptions.find((option) => option.type === column.type)} + {@const option = columnOptions.find( + (option) => + option.type === column.type && + option.format === ('format' in column ? column.format : undefined) + )} {@const isSelectable = column['system'] || column.type === 'relationship' ? 'disabled' : true} From f8930b0c9be47f8a01457530ef94f2fae9332274 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 13 Feb 2026 17:00:37 +0530 Subject: [PATCH 033/103] fixed in pink --- bun.lock | 2 ++ package.json | 2 ++ src/lib/elements/forms/inputNumber.svelte | 12 ------------ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/bun.lock b/bun.lock index 3eb57c887..73540e820 100644 --- a/bun.lock +++ b/bun.lock @@ -11,6 +11,7 @@ "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", "@faker-js/faker": "^9.9.0", "@plausible-analytics/tracker": "^0.4.4", "@popperjs/core": "^2.11.8", @@ -24,6 +25,7 @@ "dayjs": "^1.11.13", "deep-equal": "^2.2.3", "echarts": "^5.6.0", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "ignore": "^6.0.2", "nanoid": "^5.1.5", "nanotar": "^0.1.1", diff --git a/package.json b/package.json index 913f11548..e78d7eba8 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,10 @@ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", "@faker-js/faker": "^9.9.0", "@plausible-analytics/tracker": "^0.4.4", "@popperjs/core": "^2.11.8", diff --git a/src/lib/elements/forms/inputNumber.svelte b/src/lib/elements/forms/inputNumber.svelte index a19f2d7ce..0a465e505 100644 --- a/src/lib/elements/forms/inputNumber.svelte +++ b/src/lib/elements/forms/inputNumber.svelte @@ -20,18 +20,6 @@ let error: string; - // TODO: Remove this once Pink Svelte is fixed - $: if (value !== null && typeof value === 'object' && 'target' in (value as object)) { - const event = value as Event; - const target = event.target as HTMLInputElement; - if (target?.value !== undefined) { - const parsedValue = target.value === '' ? null : Number(target.value); - value = Number.isNaN(parsedValue) ? null : parsedValue; - } else { - value = null; - } - } - const handleInvalid = (event: Event & { currentTarget: EventTarget & HTMLInputElement }) => { event.preventDefault(); From 6ac671e21e7f3a55c61d07606cfff243aeb431b4 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 13 Feb 2026 17:08:01 +0530 Subject: [PATCH 034/103] dupliucate package --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index e78d7eba8..913f11548 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,8 @@ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", "@faker-js/faker": "^9.9.0", "@plausible-analytics/tracker": "^0.4.4", "@popperjs/core": "^2.11.8", From 73da136384e1e2e411c68b9654d0402b1e08d682 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 13 Feb 2026 17:29:35 +0530 Subject: [PATCH 035/103] addressed comments --- .../variables/createVariableModal.svelte | 36 ++++++--- .../variables/environmentVariables.svelte | 80 ++++++++++--------- .../configuration.svelte | 1 - 3 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/lib/components/variables/createVariableModal.svelte b/src/lib/components/variables/createVariableModal.svelte index 3f7817ecf..06b73ea54 100644 --- a/src/lib/components/variables/createVariableModal.svelte +++ b/src/lib/components/variables/createVariableModal.svelte @@ -9,13 +9,29 @@ import { page } from '$app/state'; import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte'; - export let show = false; - export let variables: Partial[]; - export let productLabel = 'site'; + export type ProductLabel = 'site' | 'function'; - let newVariables: Partial[] = [{ key: '', value: '' }]; - let secret = false; - let error = ''; + let { + show = $bindable(false), + variables = $bindable(), + productLabel = 'site' + }: { + show: boolean; + variables: Partial[]; + productLabel?: ProductLabel; + } = $props(); + + let newVariables = $state[]>([{ key: '', value: '' }]); + let secret = $state(false); + let error = $state(''); + + $effect(() => { + if (!show) { + newVariables = [{ key: '', value: '' }]; + secret = false; + error = ''; + } + }); function handleVariable() { try { @@ -55,11 +71,9 @@ function removeVariable(index: number) { if (newVariables.length === 1) { - newVariables[0].key = ''; - newVariables[0].value = ''; + newVariables = [{ key: '', value: '' }]; } else { - newVariables.splice(index, 1); - newVariables = [...newVariables]; + newVariables = newVariables.filter((_, i) => i !== index); } } @@ -96,7 +110,7 @@ type="button" size="s" disabled={newVariables.length === 1 && !pair.key && !pair.value} - on:click={() => removeVariable(i)}> + onclick={() => removeVariable(i)}> diff --git a/src/lib/components/variables/environmentVariables.svelte b/src/lib/components/variables/environmentVariables.svelte index 01a856a8c..98d2584bf 100644 --- a/src/lib/components/variables/environmentVariables.svelte +++ b/src/lib/components/variables/environmentVariables.svelte @@ -27,29 +27,46 @@ import VariableEditorModal from './variableEditorModal.svelte'; import SecretVariableModal from './secretVariableModal.svelte'; import ImportVariablesModal from './importVariablesModal.svelte'; - import CreateVariableModal from './createVariableModal.svelte'; + import CreateVariableModal, { type ProductLabel } from './createVariableModal.svelte'; import DeleteVariableModal from './deleteVariableModal.svelte'; import UpdateVariableModal from './updateVariableModal.svelte'; import { Click, trackEvent } from '$lib/actions/analytics'; - export let variables: Partial[] = []; - export let productLabel = 'site'; - export let docsLink = - 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables'; - export let analyticsSource = 'site_configuration'; - export let analyticsCreateSource = 'site_settings'; - export let isLoading = false; + const DOCS_LINKS: Record = { + site: 'https://appwrite.io/docs/products/sites/develop#accessing-environment-variables', + function: 'https://appwrite.io/docs/products/functions/develop#environment-variables' + }; - let showEditorModal = false; - let showImportModal = false; - let showSecretModal = false; - let showCreate = false; - let showUpdate = false; - let showDelete = false; + let { + variables = $bindable([]), + productLabel = 'site', + analyticsSource = 'site_configuration', + analyticsCreateSource = 'site_settings', + isLoading = false + }: { + variables: Partial[]; + productLabel?: ProductLabel; + analyticsSource?: string; + analyticsCreateSource?: string; + isLoading?: boolean; + } = $props(); - let currentVariable: Partial; + let showEditorModal = $state(false); + let showImportModal = $state(false); + let showSecretModal = $state(false); + let showCreate = $state(false); + let showUpdate = $state(false); + let showDelete = $state(false); + let currentVariable = $state>(undefined); - $: createSource = analyticsCreateSource || analyticsSource; + const createSource = $derived(analyticsCreateSource || analyticsSource); + const docsLink = $derived(DOCS_LINKS[productLabel]); + + const tableColumns = [ + { id: 'key', width: { min: 300 } }, + { id: 'value', width: { min: 280 } }, + { id: 'actions', width: 40 } + ]; @@ -61,7 +78,7 @@ From 0181088b5aa724c7e8a0dfa1f583f62d97825490 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 16 Feb 2026 14:47:50 +0530 Subject: [PATCH 045/103] svelte icon fix --- src/lib/components/git/selectRootModal.svelte | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 51d468be6..57725da09 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -50,6 +50,14 @@ let hasChanges = $derived(currentPath !== initialPath); + const iconAliases = new Map([ + ['svelte-kit', 'svelte'], + ['sveltekit', 'svelte'], + ['svelte_kit', 'svelte'], + ['sveltejs', 'svelte'], + ['other', 'empty'] + ]); + function normalizePath(path: string): string { if (!path || path === './' || path === '/') return '/'; const trimmed = path.replace(/^\.\//, '').replace(/^\/+/, '').replace(/\/$/, ''); @@ -66,6 +74,13 @@ treeVersion += 1; } + function resolveIconUrl(rawIconName: string | null | undefined): string | null { + if (!rawIconName) return null; + const normalized = rawIconName.toLowerCase(); + const iconName = iconAliases.get(normalized) ?? normalized; + return $iconPath(iconName, 'color'); + } + async function detectRuntimeOrFramework(path: string): Promise { try { const detection = await sdk @@ -82,7 +97,7 @@ product === 'sites' ? detection.framework : (detection as unknown as Models.DetectionRuntime).runtime; - return iconName ? $iconPath(iconName, 'color') : null; + return resolveIconUrl(iconName); } catch (err) { return null; } From ff73a2ca30f023fd2530489ae32f62961f114a54 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 17 Feb 2026 07:25:15 +0000 Subject: [PATCH 046/103] fix: update @appwrite.io/console dependency version in package.json and bun.lock --- bun.lock | 6 ++---- package.json | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/bun.lock b/bun.lock index dd345a451..91d1e2db3 100644 --- a/bun.lock +++ b/bun.lock @@ -6,13 +6,11 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@2f43d8c", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e64d5ed", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", "@faker-js/faker": "^9.9.0", "@plausible-analytics/tracker": "^0.4.4", "@popperjs/core": "^2.11.8", @@ -109,7 +107,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@2f43d8c", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@e64d5ed", { "dependencies": { "bignumber.js": "9.0.0", "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 3a5acf0ec..2520faac1 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@2f43d8c", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e64d5ed", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -38,8 +38,6 @@ "dayjs": "^1.11.13", "deep-equal": "^2.2.3", "echarts": "^5.6.0", - "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf", - "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf", "ignore": "^6.0.2", "nanoid": "^5.1.5", "nanotar": "^0.1.1", From e3fa83361ef91f0b6d9f34e3d303bdba1b33bc59 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 17 Feb 2026 07:42:35 +0000 Subject: [PATCH 047/103] feat: add ProjectResume enum for tracking project resume events --- src/lib/actions/analytics.ts | 1 + .../project-[region]-[project]/pausedProjectModal.svelte | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 305bf33d9..023e2fbee 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -253,6 +253,7 @@ export enum Submit { ProjectUpdateLabels = 'submit_project_update_labels', ProjectService = 'submit_project_service', ProjectUpdateSMTP = 'submit_project_update_smtp', + ProjectResume = 'submit_project_resume', MemberCreate = 'submit_member_create', MemberDelete = 'submit_member_delete', MembershipUpdate = 'submit_membership_update', diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 6ff7e8aae..902868cbe 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -4,9 +4,10 @@ import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { addNotification } from '$lib/stores/notifications'; - import { trackError } from '$lib/actions/analytics'; + import { Submit, trackError } from '$lib/actions/analytics'; import { generateFingerprintToken } from '$lib/helpers/fingerprint'; import { Alert, Layout, Modal, Typography } from '@appwrite.io/pink-svelte'; + import { Status } from '@appwrite.io/console'; let { show = $bindable(false), @@ -28,7 +29,7 @@ sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; try { - await sdk.forConsole.projects.createConsoleAccess({ projectId }); + await sdk.forConsole.projects.updateStatus({ projectId, status: Status.Active }); } finally { delete sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint']; } @@ -48,7 +49,7 @@ ? String((e as { message: string }).message) : 'Failed to resume project. Please try again.'; error = message; - trackError(e, 'resume_paused_project'); + trackError(e, Submit.ProjectResume); } finally { loading = false; } From 878975a3578847b1579e394e9283c8bd6f5c4150 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 17 Feb 2026 07:55:00 +0000 Subject: [PATCH 048/103] fix: format code for better readability in layout and pausedProjectModal components --- .../(console)/project-[region]-[project]/+layout.ts | 8 ++++++-- .../project-[region]-[project]/pausedProjectModal.svelte | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 60d3efa8f..9f45e3e74 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -113,7 +113,9 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { if (projectInactivityDays > 0 && consoleAccessedAt) { const lastAccess = new Date(consoleAccessedAt); const now = new Date(); - const diffDays = Math.floor((now.getTime() - lastAccess.getTime()) / (1000 * 60 * 60 * 24)); + const diffDays = Math.floor( + (now.getTime() - lastAccess.getTime()) / (1000 * 60 * 60 * 24) + ); isPaused = diffDays >= projectInactivityDays; } @@ -121,7 +123,9 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { generateFingerprintToken() .then((fingerprint) => { sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint'] = fingerprint; - return sdk.forConsole.projects.updateConsoleAccess({ projectId: params.project }); + return sdk.forConsole.projects.updateConsoleAccess({ + projectId: params.project + }); }) .catch(() => {}) .finally(() => { diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 902868cbe..d6e88e666 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -58,9 +58,7 @@ - - This project has been paused due to inactivity. - + This project has been paused due to inactivity. Your data is safe and will remain intact. Resume the project to continue using it. From 73f69c54ef3b5a1df99ef652659b6c5a59b25662 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 17 Feb 2026 10:48:21 +0000 Subject: [PATCH 049/103] fix status check --- src/routes/(console)/project-[region]-[project]/+layout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 9f45e3e74..9d297a116 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -8,7 +8,7 @@ import { get } from 'svelte/store'; import { headerAlert } from '$lib/stores/headerAlert'; import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte'; import { loadAvailableRegions } from '$routes/(console)/regions'; -import { type Models, Platform } from '@appwrite.io/console'; +import { type Models, Platform, Status } from '@appwrite.io/console'; import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; import { generateFingerprintToken } from '$lib/helpers/fingerprint'; @@ -19,7 +19,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.PROJECT); const project = await sdk.forConsole.projects.get({ projectId: params.project }); - if (project.status !== 'active') { + if (project.status !== 'active' && project.status !== 'paused') { // project isn't active, redirect back to organizations page redirect( 303, From d35a4035fb3cd076f9f204c5330454ca97f33849 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 17 Feb 2026 10:58:41 +0000 Subject: [PATCH 050/103] remove unused dependency --- src/routes/(console)/project-[region]-[project]/+layout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 9d297a116..858f9625a 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -8,7 +8,7 @@ import { get } from 'svelte/store'; import { headerAlert } from '$lib/stores/headerAlert'; import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte'; import { loadAvailableRegions } from '$routes/(console)/regions'; -import { type Models, Platform, Status } from '@appwrite.io/console'; +import { type Models, Platform } from '@appwrite.io/console'; import { redirect } from '@sveltejs/kit'; import { resolve } from '$app/paths'; import { generateFingerprintToken } from '$lib/helpers/fingerprint'; From 0f4de44762d59fba038feb6b745c52aaff9187cd Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 17 Feb 2026 17:59:45 +0530 Subject: [PATCH 051/103] =?UTF-8?q?fix:=20Disable=20billing=20plan=20actio?= =?UTF-8?q?ns=20during=20upgrade=20with=20tooltip=20on=20hover=C3=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../billing/planSummary.svelte | 112 +++++++++--------- .../billing/planSummaryOld.svelte | 98 ++++++++------- 2 files changed, 110 insertions(+), 100 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index afe13b517..fe91a83e6 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -14,7 +14,8 @@ Icon, Layout, Divider, - Badge + Badge, + Tooltip } from '@appwrite.io/pink-svelte'; import { humanFileSize } from '$lib/helpers/sizeConvertion'; import { formatNum } from '$lib/helpers/string'; @@ -72,6 +73,7 @@ const baseAmount = $derived(currentAggregation?.amount ?? currentPlan?.price ?? 0); const creditsApplied = $derived(Math.min(baseAmount, availableCredit ?? 0)); const totalAmount = $derived(Math.max(baseAmount - creditsApplied, 0)); + const isUpgrading = $derived($organization?.status === 'upgrading'); function formatHumanSize(bytes: number): string { const size = humanFileSize(bytes || 0); @@ -588,62 +590,62 @@
- {#if !currentPlan.requiresPaymentMethod} - - {#if !currentPlan?.usagePerProject} - - {/if} + + +
+ {#if !currentPlan.requiresPaymentMethod} + + {:else if $organization?.billingPlanDowngrade !== null} + + {:else} + + {/if} +
+ + Your payment is still being processed, check with your payment + provider. + +
+ {#if !currentPlan?.usagePerProject} -
- {:else} - - {#if $organization?.billingPlanDowngrade !== null} - - {:else} - - {/if} - {#if !currentPlan?.usagePerProject} - - {/if} - - {/if} + {/if} +
{/key} diff --git a/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte b/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte index 0d4c8b132..f1fd80d7e 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte @@ -31,6 +31,7 @@ const isTrial = new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 && $organization?.billingTrialDays; /* number of trial days. */ + $: isUpgrading = $organization?.status === 'upgrading'; const extraUsage = currentInvoice ? currentInvoice.amount - currentPlan?.price : 0; @@ -41,9 +42,11 @@ A breakdown of your estimated upcoming payment for the current billing period. Totals displayed exclude accumulated credits and applicable taxes. -

- Due at: {toLocaleDate($organization?.billingNextInvoiceDate)} -

+ +

+ Due at: {toLocaleDate($organization?.billingNextInvoiceDate)} +

+
@@ -165,50 +168,55 @@
- {#if !currentPlan.requiresPaymentMethod} -
- {#if !currentPlan?.usagePerProject} - - {/if} +
+ +
+ {#if !currentPlan.requiresPaymentMethod} + + {:else if $organization?.billingPlanDowngrade !== null} + + {:else} + + {/if} +
+ + Your payment is still being processed, check with your payment provider. + +
+ {#if !currentPlan?.usagePerProject} -
- {:else} -
- {#if $organization?.billingPlanDowngrade !== null} - - {:else} - - {/if} - {#if !currentPlan?.usagePerProject} - - {/if} -
- {/if} + {/if} +
{/if} From 645f0586cb520c9735ddbcd42414ee2497fa2af8 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 17 Feb 2026 18:08:33 +0530 Subject: [PATCH 052/103] format --- .../organization-[organization]/billing/planSummary.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index fe91a83e6..fa8e582d1 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -633,8 +633,7 @@ {/if}
- Your payment is still being processed, check with your payment - provider. + Your payment is still being processed, check with your payment provider. {#if !currentPlan?.usagePerProject} From ada7b56b2065d46c51597757253eacf22827a959 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 17 Feb 2026 18:19:54 +0530 Subject: [PATCH 053/103] fix - legacy string col not appearing --- .../table-[table]/columns/edit.svelte | 25 +++++++++++++------ .../table-[table]/columns/store.ts | 4 ++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte index 7d6874215..8be3ef018 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte @@ -9,7 +9,7 @@ import deepEqual from 'deep-equal'; import { addNotification } from '$lib/stores/notifications'; import { type Columns, columnsOrder, databaseColumnSheetOptions } from '../store'; - import { columnOptions, type Option } from './store'; + import { columnOptions, STRING_COLUMN_NAME, type Option } from './store'; import { onMount } from 'svelte'; import { Layout } from '@appwrite.io/pink-svelte'; import { preferences } from '$lib/stores/preferences'; @@ -34,16 +34,25 @@ } }); - $: option = columnOptions.find((option) => { - if (selectedColumn) { - if ('format' in selectedColumn && selectedColumn.format) { - return option?.format === selectedColumn?.format; - } else { - return option?.type === selectedColumn?.type; - } + $: option = columnOptions.find((opt) => { + if (!selectedColumn) return false; + + // format match when present + if ('format' in selectedColumn && selectedColumn.format) { + return opt?.format === selectedColumn.format; } + + // Legacy string columns (no format) + if (selectedColumn.type === 'string') { + return opt.name === STRING_COLUMN_NAME; + } + + // Fallback: match by type + return opt.type === selectedColumn.type; }) as Option; + $: console.log({ option, selectedColumn }); + export async function submit() { try { await option.update(databaseId, tableId, selectedColumn, originalKey); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store.ts index 0b94d185d..b81064c2d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store.ts @@ -85,6 +85,8 @@ export type Option = { icon: ComponentType; }; +export const STRING_COLUMN_NAME = 'String (deprecated)'; + export const columnOptions: Option[] = [ { name: 'Text', @@ -235,7 +237,7 @@ export const columnOptions: Option[] = [ icon: IconRelationship }, { - name: 'String (deprecated)', + name: STRING_COLUMN_NAME, sentenceName: 'string', component: String, type: 'string', From ccb24ba4fe4c312ea2f51a6e213a1b34c07453b7 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Tue, 17 Feb 2026 18:27:20 +0530 Subject: [PATCH 054/103] removed console log --- .../database-[database]/table-[table]/columns/edit.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte index 8be3ef018..72db9ba82 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/edit.svelte @@ -51,8 +51,6 @@ return opt.type === selectedColumn.type; }) as Option; - $: console.log({ option, selectedColumn }); - export async function submit() { try { await option.update(databaseId, tableId, selectedColumn, originalKey); From bff7bc0e620f8070f392ab985013288f112aed30 Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:45:18 +0530 Subject: [PATCH 055/103] Change CNAME for functions custom domains (#2863) --- src/lib/components/domains/recordTable.svelte | 2 -- .../domains/add-domain/verify-[domain]/+page.svelte | 4 ++-- .../function-[function]/domains/retryDomainModal.svelte | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/components/domains/recordTable.svelte b/src/lib/components/domains/recordTable.svelte index 98b9a1480..fdba7438e 100644 --- a/src/lib/components/domains/recordTable.svelte +++ b/src/lib/components/domains/recordTable.svelte @@ -54,8 +54,6 @@ case 'cname': if (service === 'sites') { return $regionalConsoleVariables._APP_DOMAIN_SITES; - } else if (service === 'functions') { - return $regionalConsoleVariables._APP_DOMAIN_FUNCTIONS; } else { return $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME; } diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/verify-[domain]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/verify-[domain]/+page.svelte index 07f3c49d4..7701ebd8d 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/verify-[domain]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/verify-[domain]/+page.svelte @@ -29,8 +29,8 @@ const ruleId = page.url.searchParams.get('rule'); const showCNAMETab = $derived( - Boolean($regionalConsoleVariables._APP_DOMAIN_FUNCTIONS) && - $regionalConsoleVariables._APP_DOMAIN_FUNCTIONS !== 'localhost' + Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME) && + $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME !== 'localhost' ); const showATab = $derived( !isCloud && diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/retryDomainModal.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/retryDomainModal.svelte index bc90344d6..02a09d4c4 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/retryDomainModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/retryDomainModal.svelte @@ -26,8 +26,8 @@ } = $props(); const showCNAMETab = $derived( - Boolean($regionalConsoleVariables._APP_DOMAIN_FUNCTIONS) && - $regionalConsoleVariables._APP_DOMAIN_FUNCTIONS !== 'localhost' + Boolean($regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME) && + $regionalConsoleVariables._APP_DOMAIN_TARGET_CNAME !== 'localhost' ); const showATab = $derived( !isCloud && From 96fbfdca7c8000917fbec73469e30a0f2be6c886 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 18 Feb 2026 02:21:55 +0530 Subject: [PATCH 056/103] fix: members invite button disabled --- .../organization-[organization]/members/+page.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/members/+page.svelte b/src/routes/(console)/organization-[organization]/members/+page.svelte index ca9969cc0..cdd4c9851 100644 --- a/src/routes/(console)/organization-[organization]/members/+page.svelte +++ b/src/routes/(console)/organization-[organization]/members/+page.svelte @@ -6,7 +6,7 @@ import { base } from '$app/paths'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import Upgrade from '$lib/components/roles/upgrade.svelte'; - import { getRoleLabel } from '$lib/stores/billing'; + import { getServiceLimit, readOnly, getRoleLabel } from '$lib/stores/billing'; import { addNotification } from '$lib/stores/notifications'; import { currentPlan, newMemberModal, organization } from '$lib/stores/organization'; import { isOwner } from '$lib/stores/roles'; @@ -14,7 +14,7 @@ import type { Models } from '@appwrite.io/console'; import Delete from '../deleteMember.svelte'; import Edit from './edit.svelte'; - import { isCloud } from '$lib/system'; + import { isCloud, GRACE_PERIOD_OVERRIDE } from '$lib/system'; import { IconDotsHorizontal, IconInfo, @@ -45,8 +45,10 @@ // Calculate if button should be disabled and tooltip should show $: memberCount = data.organizationMembers?.total ?? 0; $: supportsMembers = $organization?.billingPlanDetails?.addons?.seats; - $: isFreeWithMembers = !supportsMembers && memberCount >= 1; - $: isButtonDisabled = isCloud ? isFreeWithMembers : false; + $: limit = getServiceLimit('members', null, $currentPlan) || Infinity; + $: isLimited = limit !== 0 && limit < Infinity; + $: isButtonDisabled = + isCloud && (($readOnly && !GRACE_PERIOD_OVERRIDE) || (isLimited && memberCount >= limit)); const resend = async (member: Models.Membership) => { try { From 1405f4231c988a38c06e903241e239f4f0440bfe Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 18 Feb 2026 02:45:07 +0530 Subject: [PATCH 057/103] fix: templates not being used for custom smtp --- .../project-[region]-[project]/auth/templates/store.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts b/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts index d02f9de08..7a95351fc 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts @@ -51,13 +51,13 @@ export const templates = [ component: EmailVerificationTemplate }, { - key: EmailTemplateType.Magicsession, + key: 'magicSession' as EmailTemplateType, title: 'Magic URL', description: 'Send an email to users that sign in with a magic URL.', component: EmailMagicUrlTemplate }, { - key: EmailTemplateType.Otpsession, + key: 'otpSession' as EmailTemplateType, title: 'OTP session', description: 'Send an email to users that sign in with a email OTP.', component: EmailOtpSessionTemplate @@ -75,13 +75,13 @@ export const templates = [ component: EmailInviteTemplate }, { - key: EmailTemplateType.Mfachallenge, + key: 'mfaChallenge' as EmailTemplateType, title: '2FA verification', description: 'Send a two-factor authentication email to a user.', component: Email2FaTemplate }, { - key: EmailTemplateType.Sessionalert, + key: 'sessionAlert' as EmailTemplateType, title: 'Session alert', description: 'Send an email to users when a new session is created.', component: EmailSessionAlertTemplate, From e4f32ec69153c68d007a21568efa38edd61b1711 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 18 Feb 2026 01:22:31 +0000 Subject: [PATCH 058/103] update publish keys --- .github/workflows/publish.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b492a6b80..4036ac982 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -45,6 +45,7 @@ jobs: "PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false" "PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}" "PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY }}" + "PUBLIC_CONSOLE_FINGERPRINT_KEY=${{ secrets.PUBLIC_CONSOLE_FINGERPRINT_KEY }}" "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" "SENTRY_RELEASE=${{ github.event.release.tag_name }}" publish-cloud-stage: @@ -87,6 +88,7 @@ jobs: "PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false" "PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}" "PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}" + "PUBLIC_CONSOLE_FINGERPRINT_KEY=${{ secrets.PUBLIC_CONSOLE_FINGERPRINT_KEY_STAGE }}" publish-self-hosted: runs-on: ubuntu-latest steps: @@ -166,4 +168,5 @@ jobs: "PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=false" "PUBLIC_CONSOLE_FEATURE_FLAGS=" "PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}" + "PUBLIC_CONSOLE_FINGERPRINT_KEY=${{ secrets.PUBLIC_CONSOLE_FINGERPRINT_KEY_STAGE }}" "PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}" From 52eecc967a6b3d1ef75bfdc4a68857fdd44a5591 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 18 Feb 2026 02:37:55 +0000 Subject: [PATCH 059/103] add PUBLIC_CONSOLE_FINGERPRINT_KEY to Dockerfile and update fingerprint logic --- Dockerfile | 2 ++ src/lib/helpers/fingerprint.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index 788db3b62..691652e7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ ARG PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ARG PUBLIC_APPWRITE_ENDPOINT ARG PUBLIC_GROWTH_ENDPOINT ARG PUBLIC_STRIPE_KEY +ARG PUBLIC_CONSOLE_FINGERPRINT_KEY ARG SENTRY_AUTH_TOKEN ARG SENTRY_RELEASE @@ -33,6 +34,7 @@ ENV PUBLIC_APPWRITE_MULTI_REGION=$PUBLIC_APPWRITE_MULTI_REGION ENV PUBLIC_CONSOLE_EMAIL_VERIFICATION=$PUBLIC_CONSOLE_EMAIL_VERIFICATION ENV PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=$PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY +ENV PUBLIC_CONSOLE_FINGERPRINT_KEY=$PUBLIC_CONSOLE_FINGERPRINT_KEY ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN ENV SENTRY_RELEASE=$SENTRY_RELEASE ENV NODE_OPTIONS=--max_old_space_size=8192 diff --git a/src/lib/helpers/fingerprint.ts b/src/lib/helpers/fingerprint.ts index 5058ea38e..9ecf76ed7 100644 --- a/src/lib/helpers/fingerprint.ts +++ b/src/lib/helpers/fingerprint.ts @@ -205,6 +205,11 @@ export async function generateFingerprintToken(): Promise { const payload = JSON.stringify(signals); const encoded = btoa(payload); + + if (!SECRET) { + return encoded; + } + const signature = await hmacSha256(encoded, SECRET); return `${encoded}.${signature}`; From 3c0ca007511187c8ba95c06c9c8cf420d8cb295a Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 18 Feb 2026 20:48:28 +0530 Subject: [PATCH 060/103] upgrade --- bun.lock | 6 ++---- package.json | 4 +--- .../project-[region]-[project]/auth/templates/store.ts | 8 ++++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index 8d4edefe9..959ff1923 100644 --- a/bun.lock +++ b/bun.lock @@ -6,13 +6,11 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c6f60aa", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@df765cc", "@faker-js/faker": "^9.9.0", "@plausible-analytics/tracker": "^0.4.4", "@popperjs/core": "^2.11.8", @@ -109,7 +107,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@c6f60aa", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", { "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 9a6e6c964..1ef46fbbe 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@c6f60aa", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -38,8 +38,6 @@ "dayjs": "^1.11.13", "deep-equal": "^2.2.3", "echarts": "^5.6.0", - "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@a4067bf", - "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@a4067bf", "ignore": "^6.0.2", "nanoid": "^5.1.5", "nanotar": "^0.1.1", diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts b/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts index 7a95351fc..9ae9f838b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/store.ts @@ -51,13 +51,13 @@ export const templates = [ component: EmailVerificationTemplate }, { - key: 'magicSession' as EmailTemplateType, + key: EmailTemplateType.MagicSession, title: 'Magic URL', description: 'Send an email to users that sign in with a magic URL.', component: EmailMagicUrlTemplate }, { - key: 'otpSession' as EmailTemplateType, + key: EmailTemplateType.OtpSession, title: 'OTP session', description: 'Send an email to users that sign in with a email OTP.', component: EmailOtpSessionTemplate @@ -75,13 +75,13 @@ export const templates = [ component: EmailInviteTemplate }, { - key: 'mfaChallenge' as EmailTemplateType, + key: EmailTemplateType.MfaChallenge, title: '2FA verification', description: 'Send a two-factor authentication email to a user.', component: Email2FaTemplate }, { - key: 'sessionAlert' as EmailTemplateType, + key: EmailTemplateType.SessionAlert, title: 'Session alert', description: 'Send an email to users when a new session is created.', component: EmailSessionAlertTemplate, From 2aaa34be4ece1d8481ca01dfaaf66972010615ef Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 18 Feb 2026 21:00:29 +0530 Subject: [PATCH 061/103] fix in some more places --- .../auth/templates/email2FATemplate.svelte | 4 ++-- .../auth/templates/emailMagicUrlTemplate.svelte | 4 ++-- .../auth/templates/emailOtpSessionTemplate.svelte | 4 ++-- .../auth/templates/emailSessionAlertTemplate.svelte | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte index 932bd7e2c..0cea45a33 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte @@ -23,12 +23,12 @@ try { const template = await loadEmailTemplate( project.$id, - EmailTemplateType.Mfachallenge, + EmailTemplateType.MfaChallenge, locale ); emailTemplate.set(template); $baseEmailTemplate = { ...$emailTemplate }; - trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Mfachallenge }); + trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MfaChallenge }); } catch (error) { trackError(error, Submit.EmailChangeLocale); addNotification({ diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte index ac962841c..62c79c9d3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte @@ -23,12 +23,12 @@ try { const template = await loadEmailTemplate( project.$id, - EmailTemplateType.Magicsession, + EmailTemplateType.MagicSession, locale ); emailTemplate.set(template); $baseEmailTemplate = { ...$emailTemplate }; - trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Magicsession }); + trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.MagicSession }); } catch (error) { trackError(error, Submit.EmailChangeLocale); addNotification({ diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte index 1ec2f24bd..76bb1ef5d 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte @@ -23,12 +23,12 @@ try { const template = await loadEmailTemplate( project.$id, - EmailTemplateType.Otpsession, + EmailTemplateType.OtpSession, locale ); emailTemplate.set(template); $baseEmailTemplate = { ...$emailTemplate }; - trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Otpsession }); + trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.OtpSession }); } catch (error) { trackError(error, Submit.EmailChangeLocale); addNotification({ diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte index ecd1cbb28..e8b8f49de 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte @@ -23,12 +23,12 @@ try { const template = await loadEmailTemplate( project.$id, - EmailTemplateType.Sessionalert, + EmailTemplateType.SessionAlert, locale ); emailTemplate.set(template); $baseEmailTemplate = { ...$emailTemplate }; - trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Sessionalert }); + trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.SessionAlert }); } catch (error) { trackError(error, Submit.EmailChangeLocale); addNotification({ From 7f31579759f8720958af8ed92122844766ab5685 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 19 Feb 2026 18:44:27 +0530 Subject: [PATCH 062/103] some more chnages --- src/lib/components/git/DirectoryItem.svelte | 245 ++++++++++++++++++ src/lib/components/git/DirectoryPicker.svelte | 116 +++++++++ src/lib/components/git/selectRootModal.svelte | 178 ++++++++----- 3 files changed, 480 insertions(+), 59 deletions(-) create mode 100644 src/lib/components/git/DirectoryItem.svelte create mode 100644 src/lib/components/git/DirectoryPicker.svelte diff --git a/src/lib/components/git/DirectoryItem.svelte b/src/lib/components/git/DirectoryItem.svelte new file mode 100644 index 000000000..68512940a --- /dev/null +++ b/src/lib/components/git/DirectoryItem.svelte @@ -0,0 +1,245 @@ + + +{#each directories as { title, fileCount, fullPath, thumbnailUrl, thumbnailIcon, thumbnailHtml, children, hasChildren: explicitHasChildren, showThumbnail = true, loading = false }, i} + {@const hasChildren = explicitHasChildren ?? !!children?.length} + {@const __MELTUI_BUILDER_1__ = $group({ id: fullPath })} + {@const __MELTUI_BUILDER_0__ = $item({ + id: fullPath, + hasChildren + })} + +
+ + + {#if children} +
+ +
+ {/if} +
+{/each} + + diff --git a/src/lib/components/git/DirectoryPicker.svelte b/src/lib/components/git/DirectoryPicker.svelte new file mode 100644 index 000000000..bbc5bced9 --- /dev/null +++ b/src/lib/components/git/DirectoryPicker.svelte @@ -0,0 +1,116 @@ + + + + +
+ {#if isLoading} +
+ Loading directory data... +
+ {:else} + + {/if} +
+ + diff --git a/src/lib/components/git/selectRootModal.svelte b/src/lib/components/git/selectRootModal.svelte index 57725da09..bd2ee17fd 100644 --- a/src/lib/components/git/selectRootModal.svelte +++ b/src/lib/components/git/selectRootModal.svelte @@ -6,7 +6,7 @@ import { sdk } from '$lib/stores/sdk'; import { installation, repository } from '$lib/stores/vcs'; import { VCSDetectionType, type Models } from '@appwrite.io/console'; - import { DirectoryPicker } from '@appwrite.io/pink-svelte'; + import DirectoryPicker from '$lib/components/git/DirectoryPicker.svelte'; import { writable } from 'svelte/store'; type Directory = { @@ -15,6 +15,7 @@ fileCount?: number; thumbnailUrl?: string; children?: Directory[]; + hasChildren?: boolean; loading?: boolean; }; @@ -33,20 +34,24 @@ let isLoading = $state(true); let directories = $state([ { - title: 'Root', + title: 'Repository (root)', fullPath: '/', - fileCount: 0, + fileCount: undefined, thumbnailUrl: $iconPath('empty', 'grayscale'), children: [], + hasChildren: true, loading: false } ]); let currentPath = $state('/'); let expandedStore = writable([]); let initialized = $state(false); - let treeVersion = $state(0); let initialPath = $state('/'); const inFlightPaths = new Set(); + const contentsCache = new Map< + string, + { fileCount: number; directories: Array<{ name: string }> } + >(); let hasChanges = $derived(currentPath !== initialPath); @@ -70,10 +75,6 @@ return `./${normalized.slice(1)}`; } - function bumpTreeVersion() { - treeVersion += 1; - } - function resolveIconUrl(rawIconName: string | null | undefined): string | null { if (!rawIconName) return null; const normalized = rawIconName.toLowerCase(); @@ -103,34 +104,103 @@ } } + async function fetchContents(path: string) { + const cached = contentsCache.get(path); + if (cached) return cached; + + const content = await sdk + .forProject(page.params.region, page.params.project) + .vcs.getRepositoryContents({ + installationId: $installation.$id, + providerRepositoryId: $repository.id, + providerRootDirectory: toProviderPath(path), + providerReference: branch + }); + + const fileCount = content.contents?.length ?? 0; + const directories = content.contents + .filter((e) => e.isDirectory) + .map((dir) => ({ name: dir.name })); + + const result = { fileCount, directories }; + contentsCache.set(path, result); + return result; + } + + function ensureChildren(path: string, directories: Array<{ name: string }>) { + const targetDir = getDirByPath(path); + if (!targetDir) return; + + if (directories.length === 0) { + targetDir.hasChildren = false; + targetDir.children = []; + return; + } + + const existingByTitle = new Map( + (targetDir.children ?? []).map((child) => [child.title, child]) + ); + targetDir.children = directories.map((dir) => { + const fullPath = path === '/' ? `/${dir.name}` : `${path}/${dir.name}`; + const existing = existingByTitle.get(dir.name); + if (existing) { + existing.fullPath = fullPath; + existing.hasChildren = true; + existing.loading = existing.loading ?? false; + existing.thumbnailUrl = existing.thumbnailUrl ?? $iconPath('empty', 'grayscale'); + existing.children = existing.children ?? []; + return existing; + } + return { + title: dir.name, + fullPath, + fileCount: undefined, + thumbnailUrl: $iconPath('empty', 'grayscale'), + children: [], + hasChildren: true, + loading: false + }; + }); + } + + async function prefetchPath(path: string) { + const normalized = normalizePath(path); + const segments = normalized.split('/').filter((s) => s !== ''); + const pathsToLoad = ['/']; + let currentPath = '/'; + + for (const segment of segments) { + currentPath = currentPath === '/' ? `/${segment}` : `${currentPath}/${segment}`; + pathsToLoad.push(currentPath); + } + + for (const pathToLoad of pathsToLoad) { + const { fileCount, directories } = await fetchContents(pathToLoad); + const targetDir = getDirByPath(pathToLoad); + if (targetDir) { + targetDir.fileCount = fileCount; + } + ensureChildren(pathToLoad, directories); + } + } + $effect(() => { if (!isLoading) return; (async () => { try { - const content = await sdk - .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - providerRootDirectory: './', - providerReference: branch - }); + const content = await fetchContents('/'); + + const repoTitle = $repository?.name + ? `${$repository.name} (root)` + : 'Repository (root)'; directories[0] = { ...directories[0], - fileCount: content.contents?.length ?? 0, - children: content.contents - .filter((e) => e.isDirectory) - .map((dir) => ({ - title: dir.name, - fullPath: `/${dir.name}`, - fileCount: undefined, - // set logo for root directories - thumbnailUrl: $iconPath('empty', 'grayscale'), - loading: false - })) + title: repoTitle, + fileCount: content.fileCount }; + ensureChildren('/', content.directories); const detectedIcon = await detectRuntimeOrFramework('/'); if (detectedIcon) { @@ -139,7 +209,7 @@ isLoading = false; expandedStore.update((exp) => [...new Set([...exp, '/'])]); - bumpTreeVersion(); + prefetchPath(rootDir || '/'); } catch (error) { console.error('Failed to load root directory:', error); isLoading = false; @@ -163,24 +233,20 @@ const targetDir = getDirByPath(path); if (!targetDir || targetDir.fileCount !== undefined) return; + if (!targetDir.children) { + targetDir.children = []; + } + if (inFlightPaths.has(path)) return; inFlightPaths.add(path); targetDir.loading = true; try { - const content = await sdk - .forProject(page.params.region, page.params.project) - .vcs.getRepositoryContents({ - installationId: $installation.$id, - providerRepositoryId: $repository.id, - providerRootDirectory: toProviderPath(path), - providerReference: branch - }); - - const fileCount = content.contents?.length ?? 0; - const contentDirectories = content.contents.filter((e) => e.isDirectory); + const { fileCount, directories: contentDirectories } = await fetchContents(path); if (contentDirectories.length === 0) { + targetDir.hasChildren = false; + targetDir.children = []; expandedStore.update((exp) => [...new Set([...exp, path])]); return; } @@ -193,14 +259,7 @@ targetDir.thumbnailUrl = detectedIcon; } - const nextChildren = contentDirectories.map((dir) => ({ - title: dir.name, - fullPath: path === '/' ? `/${dir.name}` : `${path}/${dir.name}`, - fileCount: undefined, - thumbnailUrl: $iconPath('empty', 'grayscale') - })); - targetDir.children = nextChildren; - bumpTreeVersion(); + ensureChildren(path, contentDirectories); expandedStore.update((exp) => [...new Set([...exp, path])]); } catch (error) { @@ -235,7 +294,8 @@ fullPath: currentPath, fileCount: undefined, thumbnailUrl: $iconPath('empty', 'grayscale'), - children: [] + children: [], + hasChildren: true }; currentDir.children = [...currentDir.children, nextDir]; } @@ -244,10 +304,11 @@ } expandedStore.update((exp) => [...new Set([...exp, ...pathsToExpand])]); - bumpTreeVersion(); + // ensure each segment loads in order so deeper children appear for (const pathToLoad of pathsToExpand) { - loadPath(pathToLoad); + // eslint-disable-next-line no-await-in-loop + await loadPath(pathToLoad); } currentPath = normalized; @@ -286,15 +347,14 @@ Select the directory where your site code is located using the menu below. - {#key treeVersion} - - {/key} + From aecdfd5b4629f8e4d4f6936f14eeb772b4721e46 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Thu, 19 Feb 2026 19:11:09 +0530 Subject: [PATCH 063/103] svelte 5 --- src/lib/components/git/DirectoryItem.svelte | 108 +++++++++--------- src/lib/components/git/DirectoryPicker.svelte | 48 +++++--- src/lib/components/git/selectRootModal.svelte | 58 ++++++++-- src/lib/components/git/types.ts | 14 +++ 4 files changed, 152 insertions(+), 76 deletions(-) create mode 100644 src/lib/components/git/types.ts diff --git a/src/lib/components/git/DirectoryItem.svelte b/src/lib/components/git/DirectoryItem.svelte index 68512940a..baf706601 100644 --- a/src/lib/components/git/DirectoryItem.svelte +++ b/src/lib/components/git/DirectoryItem.svelte @@ -3,33 +3,41 @@ import type { createTreeView } from '@melt-ui/svelte'; import { IconChevronRight } from '@appwrite.io/pink-icons-svelte'; import { Icon, Layout, Selector, Spinner, Typography } from '@appwrite.io/pink-svelte'; + import DirectoryItemSelf from './DirectoryItem.svelte'; - export let directories: Array<{ - title: string; - fileCount?: number; - fullPath: string; - thumbnailUrl?: string; - thumbnailIcon?: typeof Icon; - thumbnailHtml?: string; - children?: typeof directories; - hasChildren?: boolean; - showThumbnail?: boolean; - loading?: boolean; - }>; - export let level = 0; - export let containerWidth: number | undefined; - export let selectedPath: string | undefined; - export let onSelect: - | ((detail: { title: string; fullPath: string; hasChildren: boolean }) => void) - | undefined; + let { + directories, + level = 0, + containerWidth, + selectedPath, + onSelect + }: { + directories: Array<{ + title: string; + fileCount?: number; + fullPath: string; + thumbnailUrl?: string; + thumbnailIcon?: typeof Icon; + thumbnailHtml?: string; + children?: typeof directories; + hasChildren?: boolean; + showThumbnail?: boolean; + loading?: boolean; + }>; + level?: number; + containerWidth?: number; + selectedPath?: string; + onSelect?: (detail: { title: string; fullPath: string; hasChildren: boolean }) => void; + } = $props(); const Radio = Selector.Radio; - let radioInputs: Array = []; - let value: string | undefined; - let thumbnailStates: Array<{ loading: boolean; error: boolean }> = []; + let radioInputs = $state>([]); + let value = $state(undefined); + let thumbnailStates = $state>([]); - $: if (directories) { + $effect(() => { + if (!directories) return; if (thumbnailStates.length < directories.length) { thumbnailStates = [ ...thumbnailStates, @@ -41,7 +49,7 @@ } else if (thumbnailStates.length > directories.length) { thumbnailStates = thumbnailStates.slice(0, directories.length); } - } + }); function handleThumbnailLoad(index: number) { if (!thumbnailStates[index]) return; @@ -62,40 +70,41 @@ const paddingLeftStyle = `padding-left: ${32 * level + 8}px`; - $: if (selectedPath && directories?.length) { - const idx = directories.findIndex((d) => d.fullPath === selectedPath); - if (idx !== -1 && radioInputs[idx]) { - radioInputs[idx].checked = true; + $effect(() => { + if (selectedPath && directories?.length) { + const idx = directories.findIndex((d) => d.fullPath === selectedPath); + if (idx !== -1 && radioInputs[idx]) { + radioInputs[idx].checked = true; + } } - } + }); {#each directories as { title, fileCount, fullPath, thumbnailUrl, thumbnailIcon, thumbnailHtml, children, hasChildren: explicitHasChildren, showThumbnail = true, loading = false }, i} {@const hasChildren = explicitHasChildren ?? !!children?.length} {@const __MELTUI_BUILDER_1__ = $group({ id: fullPath })} {@const __MELTUI_BUILDER_0__ = $item({ - id: fullPath, - hasChildren - })} + id: fullPath, + hasChildren + })}
From 5d4a44a38d1f927d009bb77c9fe7f8b8fe01e080 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 20 Feb 2026 15:13:59 +0530 Subject: [PATCH 070/103] fix domains table lint --- src/lib/components/domains/recordTable.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/components/domains/recordTable.svelte b/src/lib/components/domains/recordTable.svelte index 118b8149a..fdba7438e 100644 --- a/src/lib/components/domains/recordTable.svelte +++ b/src/lib/components/domains/recordTable.svelte @@ -140,14 +140,16 @@ {#if variant === 'cname' && !subdomain} {#if isCloud} - Since is an apex domain, CNAME - record is only supported by certain providers. If yours doesn't, please verify using + Since is an apex domain, + CNAME record is only supported by certain providers. If yours doesn't, please verify + using nameservers instead. {:else if aTabVisible || aaaaTabVisible} - Since is an apex domain, CNAME - record is only supported by certain providers. If yours doesn't, please verify using + Since is an apex domain, + CNAME record is only supported by certain providers. If yours doesn't, please verify + using {#if aTabVisible} A record {#if aaaaTabVisible} From d5221ca4fd34a177330c1a1a3986e2a5022fd0f8 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 20 Feb 2026 19:09:52 +0530 Subject: [PATCH 071/103] fix(functions/sites): trigger runtime and framework detection on rootdir change --- .../create-function/repository-[repository]/+page.svelte | 6 ++++++ .../repositories/repository-[repository]/+page.svelte | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index 0f3fbd874..20dcb484c 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -60,6 +60,12 @@ let detectingRuntime = true; + let prevRootDir = rootDir; + $: if (rootDir !== prevRootDir) { + prevRootDir = rootDir; + detectRuntime(); + } + onMount(async () => { installation.set(data.installation); repository.set(data.repository); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte index 4547095b6..155503e0a 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte @@ -50,6 +50,12 @@ let domainIsValid = true; let isVariablesLoading = true; + let prevRootDir = rootDir; + $: if (rootDir !== prevRootDir) { + prevRootDir = rootDir; + detectFramework(); + } + onMount(async () => { installation.set(data.installation); repository.set(data.repository); From 040f68f7945599283605c6965bf4528a7e1c40a2 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 23 Feb 2026 16:07:29 +0530 Subject: [PATCH 072/103] fix: tooltips and Correct relationship text on many side --- .../databases/+page.svelte | 4 +- .../table-[table]/columns/relationship.svelte | 45 ++++++++++++------- .../functions/+page.svelte | 4 +- .../overview/platforms/action.svelte | 6 ++- .../settings/webhooks/+page.svelte | 6 ++- .../storage/+page.svelte | 6 ++- 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte index be153060d..6140f5e2e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte @@ -72,7 +72,9 @@
- You have reached the maximum number of databases for your plan. +
+ You have reached the maximum number of databases for your plan. +
{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte index 9c1fe7e61..90dbd865c 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte @@ -229,23 +229,36 @@ {#if data.relationType} + {@const isManySide = + data.side === 'child' && ['oneToMany', 'manyToOne'].includes(data.relationType)}
-

- {camelize(currentTable.name)} can contain {[ - 'oneToOne', - 'manyToOne' - ].includes(data.relationType) - ? 'one' - : 'many'} - {camelize(data.key)} -

-

- {camelize(data.key)} - can belong to {['oneToOne', 'oneToMany'].includes(data.relationType) - ? 'one' - : 'many'} - {camelize(currentTable.name)} -

+ {#if isManySide} +

+ {camelize(currentTable.name)} can belong to one + {camelize(data.key)} +

+

+ {camelize(data.key)} can have many + {camelize(currentTable.name)} +

+ {:else} +

+ {camelize(currentTable.name)} can contain {[ + 'oneToOne', + 'manyToOne' + ].includes(data.relationType) + ? 'one' + : 'many'} + {camelize(data.key)} +

+

+ {camelize(data.key)} + can belong to {['oneToOne', 'oneToMany'].includes(data.relationType) + ? 'one' + : 'many'} + {camelize(currentTable.name)} +

+ {/if}
{/if} diff --git a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte index 6d8177c1e..bcf39b756 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte @@ -109,7 +109,9 @@ - You have reached the maximum number of functions for your plan. +
+ You have reached the maximum number of functions for your plan. +
diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte index 233742854..f08d2f0cc 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte @@ -20,7 +20,7 @@ {#if $canWritePlatforms} {#if isLimited} - +
- You have reached the maximum number of platforms for your plan in a project. +
+ You have reached the maximum number of platforms for your plan in a project. +
{:else} diff --git a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte index da34dceff..4a2f324ae 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte @@ -39,7 +39,7 @@ {#if $canWriteWebhooks} - +
- You have reached the maximum number of webhooks for your plan. +
+ You have reached the maximum number of webhooks for your plan. +
{/if} diff --git a/src/routes/(console)/project-[region]-[project]/storage/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/+page.svelte index 42a1faac2..aa629e200 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/+page.svelte @@ -44,7 +44,7 @@ view={data.view} searchPlaceholder="Search by name or ID"> {#if $canWriteBuckets} - +
- You have reached the maximum number of buckets for your plan. +
+ You have reached the maximum number of buckets for your plan. +
{/if} From 15da4e746acb9f2ff08dad97dfafd5fbc84f6b9f Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 23 Feb 2026 16:56:33 +0530 Subject: [PATCH 073/103] fix coderabbit suggestion --- .../table-[table]/columns/relationship.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte index 90dbd865c..df2a540af 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/relationship.svelte @@ -229,8 +229,7 @@ {#if data.relationType} - {@const isManySide = - data.side === 'child' && ['oneToMany', 'manyToOne'].includes(data.relationType)} + {@const isManySide = data.side === 'child' && data.relationType === 'oneToMany'}
{#if isManySide}

From 33c435bfef1c1faec690043ba5ca6f4a05766c70 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 24 Feb 2026 14:45:47 +0530 Subject: [PATCH 074/103] fix(billing): treat missing seats addon as supported --- src/routes/(console)/apply-credit/+page.svelte | 2 +- .../(console)/organization-[organization]/header.svelte | 2 +- .../usage/[[invoice]]/totalMembers.svelte | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index e2ad497d5..e4bdd6a83 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -316,7 +316,7 @@ {/if} - {#if selectedOrgId && !selectedOrg?.billingPlanDetails.addons.seats.supported} + {#if selectedOrgId && !(selectedOrg?.billingPlanDetails?.addons?.seats?.supported ?? true)} {#if selectedOrgId === newOrgId}

- {!organization?.billingPlanDetails.addons.seats.supported + {!(organization?.billingPlanDetails?.addons?.seats?.supported ?? true) ? 'Upgrade to add more members' : `You've reached the members limit for the ${ organization?.billingPlanDetails.name diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte index 302bdfcd1..cfae43f12 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte @@ -12,14 +12,14 @@ $: total = members?.total ?? 0; $: organizationMembersSupported = - !$organization?.billingPlanDetails.addons.seats.supported; /* false on free */ + $organization?.billingPlanDetails?.addons?.seats?.supported ?? true; /* false on free */ Members The number of members in your organization. - {#if !organizationMembersSupported} + {#if organizationMembersSupported}
From d9fb2fbc07deefcb051d2b80151e9e12cb80f434 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:58:37 +0530 Subject: [PATCH 075/103] Update src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../usage/[[invoice]]/totalMembers.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte index cfae43f12..a3cf22abf 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte @@ -12,7 +12,7 @@ $: total = members?.total ?? 0; $: organizationMembersSupported = - $organization?.billingPlanDetails?.addons?.seats?.supported ?? true; /* false on free */ + $organization?.billingPlanDetails?.addons?.seats?.supported ?? true; /* true on paid plans */ From 440dc325e329188a30d4f8c89b0eaff2cde87f3a Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 24 Feb 2026 15:25:07 +0530 Subject: [PATCH 076/103] format --- .../(console)/organization-[organization]/header.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index 72cb123a0..d80dfb063 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -151,7 +151,10 @@
- {!(organization?.billingPlanDetails?.addons?.seats?.supported ?? true) + {!( + organization?.billingPlanDetails?.addons?.seats?.supported ?? + true + ) ? 'Upgrade to add more members' : `You've reached the members limit for the ${ organization?.billingPlanDetails.name From 8ef237bdfad949bb2a847802287a35b59ce75a26 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 24 Feb 2026 15:29:28 +0530 Subject: [PATCH 077/103] format again --- .../usage/[[invoice]]/totalMembers.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte index a3cf22abf..e63977881 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte @@ -12,7 +12,8 @@ $: total = members?.total ?? 0; $: organizationMembersSupported = - $organization?.billingPlanDetails?.addons?.seats?.supported ?? true; /* true on paid plans */ + $organization?.billingPlanDetails?.addons?.seats?.supported ?? + true; /* true on paid plans */ From ceb2b4051d1c260fc216fe625cc183faea32d504 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 25 Feb 2026 12:01:03 +0530 Subject: [PATCH 078/103] fix: domains searching --- .../(console)/organization-[organization]/domains/+page.svelte | 2 +- .../(console)/organization-[organization]/domains/+page.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/domains/+page.svelte b/src/routes/(console)/organization-[organization]/domains/+page.svelte index 477786e8f..650c287b5 100644 --- a/src/routes/(console)/organization-[organization]/domains/+page.svelte +++ b/src/routes/(console)/organization-[organization]/domains/+page.svelte @@ -178,7 +178,7 @@ limit={data.limit} offset={data.offset} total={data.domains.total} /> - {:else if data?.query} + {:else if data?.query || data?.search} + + Protect column against data leaks for best privacy compliance. Encrypted + columns cannot be queried. + + + + + + Available on Pro plan. Upgrade + to enable encrypted columns. + + + + +
+ + + Encryption can only be set when creating the column. + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/longtext.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/longtext.svelte index 8087541af..92580dcf5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/longtext.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/longtext.svelte @@ -17,7 +17,8 @@ key, required: data.required, xdefault: data.default, - array: data.array + array: data.array, + encrypt: data.encrypt }); } export async function updateLongtext( @@ -42,17 +43,21 @@ + min={data.encrypt ? 150 : 1} + max={16383} + helper={data.encrypt + ? 'Encrypted varchar columns require a minimum size of 150.' + : undefined} /> {#if !editing} @@ -173,3 +185,7 @@ {disabled} bind:array={data.array} bind:required={data.required} /> + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/createColumn.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/createColumn.svelte index bddf43762..ba23a58a5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/createColumn.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/createColumn.svelte @@ -47,6 +47,7 @@ required: column?.required ?? false, array: column?.array ?? false, default: column?.default ?? null, + encrypt: (column as { encrypt?: boolean })?.encrypt ?? false, ...column } as Partial); @@ -60,7 +61,8 @@ data = { required: false, array: false, - default: null + default: null, + encrypt: false }; /* default to text */ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index f4f7e46b7..9026e1776 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -16,8 +16,7 @@ buildWildcardColumnsQuery, isRelationship, isRelationshipToMany, - isSpatialType, - isString + isSpatialType, isTextType } from './rows/store'; import { columns, @@ -1072,7 +1071,7 @@ {@const isEmptyArray = formatted === 'Empty'} {@const isDatetimeAttribute = rowColumn.type === 'datetime'} {@const isEncryptedAttribute = - isString(rowColumn) && rowColumn.encrypt} + isTextType(rowColumn) && 'encrypt' in rowColumn && rowColumn.encrypt} {#if isDatetimeAttribute} Timestamp From 5525372e45e2fe880af10317187df11e14244399 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Wed, 25 Feb 2026 18:36:57 +0530 Subject: [PATCH 081/103] handle non-string user/team prefs to prevent Preferences UI crash --- src/lib/helpers/prefs.ts | 17 ++++++++++++----- .../auth/teams/team-[team]/updatePrefs.svelte | 10 +++++----- .../auth/user-[user]/updatePrefs.svelte | 4 +++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/lib/helpers/prefs.ts b/src/lib/helpers/prefs.ts index 1a1451a4b..850ae28c0 100644 --- a/src/lib/helpers/prefs.ts +++ b/src/lib/helpers/prefs.ts @@ -1,12 +1,19 @@ export type PrefRow = { key: string; value: string }; -export function normalizePrefs(entries: [string, string][] | PrefRow[]): [string, string][] { +function stringTrim(s: unknown): string { + return String(s ?? '').trim(); +} + +export function normalizePrefs( + entries: [string, unknown][] | PrefRow[] | { key: unknown; value: unknown }[] +): [string, string][] { return entries .map((item): [string, string] => - Array.isArray(item) ? [item[0], item[1]] : [item.key, item.value] + Array.isArray(item) + ? [String(item[0] ?? ''), String(item[1] ?? '')] + : [String(item.key ?? ''), String(item.value ?? '')] ) - - .filter(([k, v]) => k.trim() && v.trim()) + .filter(([k, v]) => stringTrim(k).length > 0 && stringTrim(v).length > 0) .sort(([a], [b]) => a.localeCompare(b)); } @@ -23,5 +30,5 @@ export function isAddDisabled(prefs: PrefRow[] | null): boolean { } export function sanitizePrefs(prefs: PrefRow[]) { - return prefs.filter((p) => p.key.trim() && p.value.trim()); + return prefs.filter((p) => stringTrim(p.key).length > 0 && stringTrim(p.value).length > 0); } diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/updatePrefs.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/updatePrefs.svelte index 3320b31e4..b38435249 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/updatePrefs.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/updatePrefs.svelte @@ -22,9 +22,7 @@ $: if (prefs) { const currentNormalized = normalizePrefs(prefs); - const originalNormalized = normalizePrefs( - Object.entries(($team?.prefs ?? {}) as Record) - ); + const originalNormalized = normalizePrefs(Object.entries($team?.prefs ?? {})); arePrefsDisabled = deepEqual(currentNormalized, originalNormalized); } @@ -33,10 +31,12 @@ let arePrefsDisabled = true; onMount(async () => { - const entries = Object.entries(($team?.prefs ?? {}) as Record); + const entries = Object.entries($team?.prefs ?? {}); prefs = entries.length > 0 - ? entries.map(([key, value]) => createPrefRow(key, value)) + ? entries.map(([key, value]) => + createPrefRow(String(key ?? ''), String(value ?? '')) + ) : [createPrefRow()]; }); diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte index c3f6c54aa..932583f94 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte @@ -34,7 +34,9 @@ const entries = Object.entries($user?.prefs ?? {}); prefs = entries.length > 0 - ? entries.map(([key, value]) => createPrefRow(key, value)) + ? entries.map(([key, value]) => + createPrefRow(String(key ?? ''), String(value ?? '')) + ) : [createPrefRow()]; }); From 657366d1016590077467e9d07f29bd97ae2e1c5e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 26 Feb 2026 04:38:25 +0000 Subject: [PATCH 082/103] update console --- bun.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bun.lock b/bun.lock index 55ab67dc1..72b338490 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@e64d5ed", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@8e7decc", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -108,7 +108,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@e64d5ed", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@8e7decc", { "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 d26f1a671..57e3e7a94 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@e64d5ed", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@8e7decc", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", From 736e6675fc617f2bd52e611db7c595217cb8a168 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 26 Feb 2026 04:50:13 +0000 Subject: [PATCH 083/103] fix: enhance fingerprint token generation and improve error handling in console access updates --- src/lib/helpers/fingerprint.ts | 7 ++++++- .../project-[region]-[project]/+layout.svelte | 2 +- .../project-[region]-[project]/+layout.ts | 2 +- .../pausedProjectModal.svelte | 18 ++++++++++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/lib/helpers/fingerprint.ts b/src/lib/helpers/fingerprint.ts index 7c330786b..f34935ad0 100644 --- a/src/lib/helpers/fingerprint.ts +++ b/src/lib/helpers/fingerprint.ts @@ -206,7 +206,12 @@ export async function generateFingerprintToken(): Promise { }; const payload = JSON.stringify(signals); - const encoded = btoa(unescape(encodeURIComponent(payload))); + const bytes = new TextEncoder().encode(payload); + let binary = ''; + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } + const encoded = btoa(binary); if (!SECRET) { return encoded; diff --git a/src/routes/(console)/project-[region]-[project]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/+layout.svelte index 58c825912..ffe92bdca 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/+layout.svelte @@ -120,7 +120,7 @@ {#if isCloud && data.project?.status === 'paused'} - + {/if}
diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 8263d56ac..cadc48ef9 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -118,7 +118,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { projectId: params.project }); }) - .catch(() => {}) + .catch((e) => console.error('Failed to update console access:', e)) .finally(() => { delete sdk.forConsole.client.headers['X-Appwrite-Console-Fingerprint']; }); diff --git a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte index 143cacce8..f4ad0a765 100644 --- a/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/pausedProjectModal.svelte @@ -1,7 +1,8 @@ From 1649d9b7e90c56505ccf5e3c26f35a2ed1628b18 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:20:02 +0530 Subject: [PATCH 092/103] Update src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/encryptCheckbox.svelte Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../table-[table]/columns/encryptCheckbox.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/encryptCheckbox.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/encryptCheckbox.svelte index 32a55c211..f70652ae3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/encryptCheckbox.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/encryptCheckbox.svelte @@ -47,7 +47,7 @@ + From b2e3cb6f77731bf158aac351147823c143c27e66 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Mar 2026 11:27:10 +0530 Subject: [PATCH 101/103] fix: lock. --- bun.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 73cff5894..f05001d1f 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9f06c85", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c91a6f3", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -121,7 +121,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@9f06c85", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@c91a6f3", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], From b8913a5c73090d335f6dbdf2d0df59cec23e0e9d Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Mar 2026 11:36:43 +0530 Subject: [PATCH 102/103] patch: `immutable` library temporarily! --- bun.lock | 3 ++- package.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index f05001d1f..3c46fd60c 100644 --- a/bun.lock +++ b/bun.lock @@ -89,6 +89,7 @@ }, }, "overrides": { + "immutable": "^5.1.5", "minimatch": "10.2.3", "vite": "npm:rolldown-vite@latest", }, @@ -973,7 +974,7 @@ "ignore": ["ignore@6.0.2", "", {}, "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A=="], - "immutable": ["immutable@5.1.4", "", {}, "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="], + "immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], diff --git a/package.json b/package.json index bb29e2b97..432f69cd5 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ }, "overrides": { "vite": "npm:rolldown-vite@latest", - "minimatch": "10.2.3" + "minimatch": "10.2.3", + "immutable": "^5.1.5" } } From 6bc49577ca6f8d0280759f86d86de7e83134e073 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 5 Mar 2026 14:45:05 +0530 Subject: [PATCH 103/103] fix: migration type issues. --- bun.lock | 4 +- package.json | 2 +- src/lib/stores/migration.ts | 95 +++++++++---------- src/lib/stores/sdk.ts | 2 +- .../(migration-wizard)/resource-form.svelte | 35 ++++--- .../(migration-wizard)/wizard.svelte | 4 +- .../migrations/(import)/wizard.svelte | 27 ++++-- 7 files changed, 95 insertions(+), 74 deletions(-) diff --git a/bun.lock b/bun.lock index 3c46fd60c..b3a79ed60 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@c91a6f3", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@13c8c34", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", @@ -122,7 +122,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@c91a6f3", { "dependencies": { "bignumber.js": "9.0.0", "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@13c8c34", { "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 432f69cd5..e996bbaf5 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@c91a6f3", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@13c8c34", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@df765cc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/lib/stores/migration.ts b/src/lib/stores/migration.ts index 534cddc6a..4d1205a72 100644 --- a/src/lib/stores/migration.ts +++ b/src/lib/stores/migration.ts @@ -1,5 +1,10 @@ import { writable } from 'svelte/store'; -import { Resources } from '@appwrite.io/console'; +import { + AppwriteMigrationResource, + FirebaseMigrationResource, + NHostMigrationResource, + SupabaseMigrationResource +} from '@appwrite.io/console'; import { includesAll } from '$lib/helpers/array'; const initialFormData = { @@ -51,66 +56,47 @@ export const ResourcesFriendly = { row: { singular: 'Row', plural: 'Rows' } }; -// @todo: @itznotabug - check if other resources are correct and work fine! -export const providerResources: Record = { - appwrite: Object.values(Resources), - supabase: [ - Resources.User, - Resources.Database, - Resources.Collection, - Resources.Attribute, - Resources.Index, - Resources.Document, - Resources.Bucket, - Resources.File - ], - nhost: [ - Resources.User, - Resources.Database, - Resources.Collection, - Resources.Attribute, - Resources.Index, - Resources.Document, - Resources.Bucket, - Resources.File - ], - firebase: [ - Resources.User, - Resources.Database, - Resources.Collection, - Resources.Attribute, - Resources.Document, - Resources.Bucket, - Resources.File - ] +export const MigrationResources = AppwriteMigrationResource; + +export type MigrationResource = + | AppwriteMigrationResource + | FirebaseMigrationResource + | SupabaseMigrationResource + | NHostMigrationResource; + +export const providerResources: Record = { + appwrite: Object.values(AppwriteMigrationResource), + supabase: Object.values(SupabaseMigrationResource), + nhost: Object.values(NHostMigrationResource), + firebase: Object.values(FirebaseMigrationResource) }; export const migrationFormToResources = ( formData: MigrationFormData, provider: Provider -): Resources[] => { - const resources: Resources[] = []; - const addResource = (resource: Resources) => { +): MigrationResource[] => { + const resources: MigrationResource[] = []; + const addResource = (resource: MigrationResource) => { if (providerResources[provider].includes(resource)) { resources.push(resource); } }; if (formData.users.root) { - addResource(Resources.User); + addResource(MigrationResources.User); } if (formData.databases.root) { - addResource(Resources.Database); - addResource(Resources.Table); - addResource(Resources.Column); - addResource(Resources.Index); + addResource(MigrationResources.Database); + addResource(MigrationResources.Table); + addResource(MigrationResources.Column); + addResource(MigrationResources.Index); } if (formData.databases.rows) { - addResource(Resources.Row); + addResource(MigrationResources.Row); } if (formData.storage.root) { - addResource(Resources.Bucket); - addResource(Resources.File); + addResource(MigrationResources.Bucket); + addResource(MigrationResources.File); } return resources; @@ -137,18 +123,29 @@ export const isVersionAtLeast = (version: string, atLeast: string) => { return compareVersions(version, atLeast) >= 0; }; -export const resourcesToMigrationForm = (resources: Resources[]): MigrationFormData => { +export const resourcesToMigrationForm = (resources: MigrationResource[]): MigrationFormData => { const formData = { ...initialFormData }; - if (resources.includes(Resources.User)) { + if (resources.includes(MigrationResources.User)) { formData.users.root = true; } - if (resources.includes(Resources.Database)) { + if (resources.includes(MigrationResources.Database)) { formData.databases.root = true; } - if (includesAll(resources, [Resources.Table, Resources.Column, Resources.Row] as Resources[])) { + if ( + includesAll(resources, [ + MigrationResources.Table, + MigrationResources.Column, + MigrationResources.Row + ] as MigrationResource[]) + ) { formData.databases.rows = true; } - if (includesAll(resources, [Resources.Bucket, Resources.File] as Resources[])) { + if ( + includesAll(resources, [ + MigrationResources.Bucket, + MigrationResources.File + ] as MigrationResource[]) + ) { formData.storage.root = true; } diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index de2570b2b..e6c0cdcc6 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -26,7 +26,7 @@ import { DocumentsDB, Realtime, Organizations, - VectorDB, + VectorDB } from '@appwrite.io/console'; import { Sources } from '$lib/sdk/sources'; import { diff --git a/src/routes/(console)/(migration-wizard)/resource-form.svelte b/src/routes/(console)/(migration-wizard)/resource-form.svelte index 54e3f23ba..acc2efcec 100644 --- a/src/routes/(console)/(migration-wizard)/resource-form.svelte +++ b/src/routes/(console)/(migration-wizard)/resource-form.svelte @@ -7,11 +7,18 @@ createMigrationProviderStore, type MigrationFormData, providerResources, - resourcesToMigrationForm + resourcesToMigrationForm, + MigrationResources } from '$lib/stores/migration'; import { Button } from '$lib/elements/forms'; import { wizard } from '$lib/stores/wizard'; - import { Resources, type Models } from '@appwrite.io/console'; + import { + type Models, + AppwriteMigrationResource, + FirebaseMigrationResource, + NHostMigrationResource, + SupabaseMigrationResource + } from '@appwrite.io/console'; import type { sdk } from '$lib/stores/sdk'; import ImportReport from '$routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte'; @@ -46,7 +53,7 @@ switch ($provider.provider) { case 'appwrite': report = await projectSdk.migrations.getAppwriteReport({ - resources: providerResources.appwrite, + resources: providerResources.appwrite as AppwriteMigrationResource[], endpoint: $provider.endpoint, projectID: $provider.projectID, key: $provider.apiKey @@ -54,7 +61,7 @@ break; case 'supabase': report = await projectSdk.migrations.getSupabaseReport({ - resources: providerResources.supabase, + resources: providerResources.supabase as SupabaseMigrationResource[], endpoint: $provider.endpoint, apiKey: $provider.apiKey, databaseHost: $provider.host, @@ -65,13 +72,13 @@ break; case 'firebase': report = await projectSdk.migrations.getFirebaseReport({ - resources: providerResources.firebase, + resources: providerResources.firebase as FirebaseMigrationResource[], serviceAccount: $provider.serviceAccount }); break; case 'nhost': report = await projectSdk.migrations.getNHostReport({ - resources: providerResources.nhost, + resources: providerResources.nhost as NHostMigrationResource[], subdomain: $provider.subdomain, region: $provider.region, adminSecret: $provider.adminSecret, @@ -100,13 +107,19 @@ } if (groupKey === 'storage') { - return resources.includes(Resources.Bucket) && resources.includes(Resources.File); + return ( + resources.includes(MigrationResources.Bucket) && + resources.includes(MigrationResources.File) + ); } - // Map groupKey to Resources enum - const groupToResource: Record = { - users: Resources.User, - databases: Resources.Database + // Map groupKey to MigrationResources enum + const groupToResource: Record< + string, + (typeof MigrationResources)[keyof typeof MigrationResources] + > = { + users: MigrationResources.User, + databases: MigrationResources.Database }; const resource = groupToResource[groupKey]; return resource ? resources.includes(resource) : false; diff --git a/src/routes/(console)/(migration-wizard)/wizard.svelte b/src/routes/(console)/(migration-wizard)/wizard.svelte index 1b63e87c6..13430e898 100644 --- a/src/routes/(console)/(migration-wizard)/wizard.svelte +++ b/src/routes/(console)/(migration-wizard)/wizard.svelte @@ -6,7 +6,7 @@ import ResourceForm from './resource-form.svelte'; import { requestedMigration } from '$routes/store'; import { formData, provider, selectedProject, selectedRegion } from '.'; - import { ID, type Models, Query } from '@appwrite.io/console'; + import { ID, type Models, Query, AppwriteMigrationResource } from '@appwrite.io/console'; import { InputSelect, InputText } from '$lib/elements/forms'; import { Button, @@ -114,7 +114,7 @@ try { await projectSdkInstance.migrations.createAppwriteMigration({ - resources, + resources: resources as AppwriteMigrationResource[], endpoint: $provider.endpoint, projectId: $provider.projectID, apiKey: $provider.apiKey diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/wizard.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/wizard.svelte index a5eac4dbc..4dfd64719 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/wizard.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/wizard.svelte @@ -7,6 +7,12 @@ import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import { migrationFormToResources, type Provider } from '$lib/stores/migration'; + import { + AppwriteMigrationResource, + FirebaseMigrationResource, + NHostMigrationResource, + SupabaseMigrationResource + } from '@appwrite.io/console'; import { started } from '../stores'; import { showMigrationBox } from '$lib/components/migrationBox.svelte'; import { addNotification } from '$lib/stores/notifications'; @@ -45,7 +51,7 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createAppwriteMigration({ - resources, + resources: resources as AppwriteMigrationResource[], endpoint: $provider.endpoint, projectId: $provider.projectID, apiKey: $provider.apiKey @@ -58,7 +64,7 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createSupabaseMigration({ - resources, + resources: resources as SupabaseMigrationResource[], endpoint: $provider.endpoint, apiKey: $provider.apiKey, databaseHost: $provider.host, @@ -73,7 +79,7 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createFirebaseMigration({ - resources, + resources: resources as FirebaseMigrationResource[], serviceAccount: $provider.serviceAccount }); await invalidate(Dependencies.MIGRATIONS); @@ -83,7 +89,7 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createNHostMigration({ - resources, + resources: resources as NHostMigrationResource[], subdomain: $provider.subdomain, region: $provider.region, adminSecret: $provider.adminSecret, @@ -206,7 +212,8 @@ Project settings are not imported + >Project settings are not imported + You will need to set service and project settings manually. @@ -218,7 +225,8 @@ Keep your organization plan's limits in mind + >Keep your organization plan's limits in mind + Make sure to have enough storage in your organization plan when importing files. @@ -256,9 +264,12 @@ variant="secondary" on:click={() => { showExitModal = true; - }}>Cancel + }} + >Cancel + Create + >Create +