Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions src/lib/stores/uploader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { Client, Functions, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import { getApiEndpoint } from '$lib/stores/sdk';
import { page } from '$app/state';
Expand Down Expand Up @@ -32,6 +32,13 @@ const temporarySites = (region: string, projectId: string) => {
return new Sites(clientProject);
};

const temporaryFunctions = (region: string, projectId: string) => {
const clientProject = new Client().setMode('admin');
const endpoint = getApiEndpoint(region);
clientProject.setEndpoint(endpoint).setProject(projectId);
return new Functions(clientProject);
};

const createUploader = () => {
const { subscribe, set, update } = writable<Uploader>({
isOpen: false,
Expand Down Expand Up @@ -110,7 +117,19 @@ const createUploader = () => {
newFile.status = 'success';
updateFile(newFile.$id, newFile);
},
uploadSiteDeployment: async (siteId: string, code: File) => {
uploadSiteDeployment: async ({
siteId,
code,
buildCommand,
installCommand,
outputDirectory
}: {
siteId: string;
code: File;
buildCommand?: string;
installCommand?: string;
outputDirectory?: string;
}) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: siteId,
Expand All @@ -132,6 +151,49 @@ const createUploader = () => {
siteId,
code,
activate: true,
buildCommand,
installCommand,
outputDirectory,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
newDeployment.status = progress.progress === 100 ? 'success' : 'pending';
updateFile(progress.$id, newDeployment);
}
});
newDeployment.$id = uploadedFile.$id;
newDeployment.progress = 100;
newDeployment.status = 'success';
updateFile(newDeployment.$id, newDeployment);
Comment on lines +157 to +167
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Consider: Potential race between onProgress and final status update.

Lines 158-161 set status: 'success' when progress === 100, then lines 164-167 set it again after the promise resolves. If an error occurs between progress=100 and promise resolution, the status may incorrectly show 'success'.

However, if createDeployment throws on failure, the try-catch in the caller will handle it (though there's no try-catch here). Consider whether progress=100 truly indicates success or just upload completion.

Check if the SDK's createDeployment can fail after progress === 100:


🌐 Web query:

Does Appwrite's Sites.createDeployment or Functions.createDeployment throw errors after the upload progress reaches 100%, or does progress=100 guarantee success?

💡 Result:

Short answer: No — progress = 100 only means the upload finished from the client side. The deployment/build stage runs afterwards and can still fail. Appwrite exposes separate deployment/build status (ready/failed/in‑progress) and error codes for build failures, and you must check the deployment status (or API response) after upload to confirm success. [1][2][3]

Sources:

  • Functions createDeployment (shows upload/deployment flow and build lifecycle). [1]
  • Sites createVCS/createDeployment docs (deployment build can be activated/cancelled and has its own status). [2]
  • Appwrite response/error codes (functions/build-related error codes such as build_in_progress / build_not_ready / failures). [3]

Fix premature success status update when progress reaches 100%.

The concern is valid: setting status: 'success' at line 160 when progress === 100 is incorrect. Appwrite's upload progress only indicates client-side upload completion; the deployment/build runs separately and can still fail afterwards. Remove the premature status: 'success' assignment from the onProgress handler (line 160). Only set status: 'success' after confirming the deployment actually succeeded—either by checking the returned deployment object's status field or by verifying the API response indicates a ready/successful state.

🤖 Prompt for AI Agents
In src/lib/stores/uploader.ts around lines 157 to 167, remove the premature
assignment that sets newDeployment.status = 'success' inside the onProgress
handler; keep updating newDeployment.$id and newDeployment.progress there and
call updateFile(progress.$id, newDeployment) but do not mark success based on
progress === 100. Instead, determine success only after the upload/deploy
response returns a confirmed success state (e.g., inspect the returned
uploadedFile or deployment object status/ready flag) and set
newDeployment.status = 'success' and call updateFile(...) at that point; if the
deployment fails, set an appropriate error/failed status based on the API
response.

},
uploadFunctionDeployment: async ({
functionId,
code,
}: {
functionId: string;
code: File;
}) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: functionId,
name: code.name,
size: code.size,
progress: 0,
status: 'pending'
};
update((n) => {
n.isOpen = true;
n.isCollapsed = false;
n.files.unshift(newDeployment);
return n;
});
const uploadedFile = await temporaryFunctions(
page.params.region,
page.params.project
).createDeployment({
functionId,
code,
activate: true,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
<style>
.layout-level-progress-bars {
gap: 1rem;
z-index: 2;
z-index: 100;
display: flex;
flex-direction: column;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import { isCloud } from '$lib/system';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { currentPlan } from '$lib/stores/organization';
import { uploader } from '$lib/stores/uploader';
export let data;
Expand Down Expand Up @@ -61,8 +62,10 @@
let specification = specificationOptions[0].value;
async function create() {
let func: Models.Function | null = null;
try {
const func = await sdk
func = await sdk
.forProject(page.params.region, page.params.project)
.functions.create({
functionId: id || ID.unique(),
Expand Down Expand Up @@ -92,25 +95,37 @@
);
await Promise.all(promises);
await sdk
.forProject(page.params.region, page.params.project)
.functions.createDeployment({
functionId: func.$id,
code: files[0],
activate: true
});
const promise = uploader.uploadFunctionDeployment({
functionId: func.$id,
code: files[0],
});
addNotification({
message: 'Deployment upload started',
type: 'success'
});
trackEvent(Submit.FunctionCreate, {
source: 'repository',
runtime: runtime
});
Comment on lines 105 to 108
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Analytics source seems wrong for manual upload

This page is “manual” but tracks { source: 'repository' }. Suggest ‘manual’ for consistency.

-            trackEvent(Submit.FunctionCreate, {
-                source: 'repository',
-                runtime: runtime
-            });
+            trackEvent(Submit.FunctionCreate, {
+                source: 'manual',
+                runtime
+            });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
trackEvent(Submit.FunctionCreate, {
source: 'repository',
runtime: runtime
});
trackEvent(Submit.FunctionCreate, {
source: 'manual',
runtime
});
🤖 Prompt for AI Agents
In
src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte
around lines 108 to 111, the analytics call uses source: 'repository' but this
is the manual upload page; change the tracked source value to 'manual' so the
trackEvent call reads source: 'manual' (preserve the rest of the payload and
surrounding code).

await goto(
`${base}/project-${page.params.region}-${page.params.project}/functions/function-${func.$id}`
);
await promise;
const upload = $uploader.files.find((f) => f.resourceId === func.$id);
if (upload?.status === 'success') {
const deploymentId = upload.$id;
await goto(
`${base}/project-${page.params.region}-${page.params.project}/functions/function-${func.$id}/deployment-${deploymentId}`
);
}
invalidate(Dependencies.FUNCTION);
} catch (e) {
const upload = $uploader.files.find((f) => f.resourceId === func?.$id);
if (upload) {
uploader.removeFromQueue(upload.$id);
}
addNotification({
type: 'error',
message: e.message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
import { Button } from '$lib/elements/forms';
import { InvalidFileType, removeFile } from '$lib/helpers/files';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte';
import { func } from '../store';
import { page } from '$app/state';
import { consoleVariables } from '$routes/(console)/store';
import { currentPlan } from '$lib/stores/organization';
import { isCloud } from '$lib/system';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { uploader } from '$lib/stores/uploader';
export let show = false;
Expand All @@ -30,16 +29,13 @@
async function create() {
try {
await sdk
.forProject(page.params.region, page.params.project)
.functions.createDeployment({
functionId: $func.$id,
code: files[0],
activate: true
});
await invalidate(Dependencies.DEPLOYMENTS);
files = undefined;
uploader.uploadFunctionDeployment({
functionId: $func.$id,
code: files[0],
})
show = false;
files = undefined;
await invalidate(Dependencies.DEPLOYMENTS);
trackEvent(Submit.DeploymentCreate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make upload non‑blocking but safe; fix success message; add semicolon

  • Without await, use .catch to capture failures for analytics.
  • Add missing semicolon to satisfy Prettier/ASI.
  • The success toast says “Deployment created successfully” before the upload completes; change to “Deployment upload started” or await completion.
-            uploader.uploadFunctionDeployment({
-                functionId: $func.$id,
-                code: files[0],
-            })
+            void uploader
+                .uploadFunctionDeployment({
+                    functionId: $func.$id,
+                    code: files[0],
+                })
+                .catch((e) => trackError(e, Submit.DeploymentCreate));
+            ;
             show = false;
             files = undefined;
             await invalidate(Dependencies.DEPLOYMENTS);
-            trackEvent(Submit.DeploymentCreate);
+            trackEvent(Submit.DeploymentCreate);
             addNotification({
                 type: 'success',
-                message: 'Deployment created successfully'
+                message: 'Deployment upload started'
             });

Also applies to: 41-43

🤖 Prompt for AI Agents
In
src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte
around lines 32-39 and 41-43, make the upload non-blocking but safe: call
uploader.uploadFunctionDeployment(...) without await, attach a .catch(...) to
capture/log/track failures for analytics, and avoid showing a success toast
before completion by changing the message to "Deployment upload started"; also
add the missing semicolons to satisfy Prettier/ASI. Ensure you still clear
show/files and await invalidate(Dependencies.DEPLOYMENTS) (or otherwise refresh
UI), and duplicate the same non-blocking + .catch + toast + semicolon changes
for the other occurrence on lines 41-43.

addNotification({
type: 'success',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { isCloud } from '$lib/system';
import { currentPlan } from '$lib/stores/organization';
import Domain from '../domain.svelte';
import { uploader } from '$lib/stores/uploader';

export let data;
let showExitModal = false;
Expand Down Expand Up @@ -50,6 +51,8 @@
$: readableMaxSize = humanFileSize(maxSize);

async function create() {
let site: Models.Site | null = null;

try {
if (!domainIsValid) {
addNotification({
Expand All @@ -63,7 +66,7 @@
const buildRuntime = Object.values(BuildRuntime).find(
(f) => f === framework.buildRuntime
);
let site = await sdk.forProject(page.params.region, page.params.project).sites.create({
site = await sdk.forProject(page.params.region, page.params.project).sites.create({
siteId: id || ID.unique(),
name,
framework: fr,
Expand All @@ -90,26 +93,37 @@
);
await Promise.all(promises);

const deployment = await sdk
.forProject(page.params.region, page.params.project)
.sites.createDeployment({
siteId: site.$id,
code: files[0],
activate: true,
installCommand: installCommand || undefined,
buildCommand: buildCommand || undefined,
outputDirectory: outputDirectory || undefined
});
const promise = uploader.uploadSiteDeployment({
siteId: site.$id,
code: files[0],
buildCommand: buildCommand || undefined,
installCommand: installCommand || undefined,
outputDirectory: outputDirectory || undefined
});

addNotification({
message: 'Deployment upload started',
type: 'success'
});
trackEvent(Submit.SiteCreate, {
source: 'manual',
framework: framework.key
});

await goto(
`${base}/project-${page.params.region}-${page.params.project}/sites/create-site/deploying?site=${site.$id}&deployment=${deployment.$id}`
);
await promise;
const upload = $uploader.files.find((f) => f.resourceId === site.$id);

if (upload?.status === 'success') {
const deploymentId = upload.$id;
await goto(
`${base}/project-${page.params.region}-${page.params.project}/sites/create-site/deploying?site=${site.$id}&deployment=${deploymentId}`
);
}
} catch (e) {
const upload = $uploader.files.find((f) => f.resourceId === site?.$id);
if (upload) {
uploader.removeFromQueue(upload.$id);
}
addNotification({
type: 'error',
message: e.message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { currentPlan } from '$lib/stores/organization';
import { consoleVariables } from '$routes/(console)/store';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';

export let show = false;
export let site: Models.Site;
Expand All @@ -29,15 +30,20 @@

async function createDeployment() {
try {
uploader.uploadSiteDeployment(site.$id, files[0]);
uploader.uploadSiteDeployment({
siteId: site.$id,
code: files[0],
});
show = false;
invalidate(Dependencies.DEPLOYMENTS);
trackEvent(Submit.DeploymentCreate);
addNotification({
message: 'Deployment upload started',
type: 'success'
});
} catch (e) {
error = e.message;
trackError(e, Submit.DeploymentCreate);
}
}

Expand Down
Loading