Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@de65a99",
"@appwrite.io/console": "github:appwrite/sdk-for-console#migration-resource-enum-fix",
Copy link
Contributor

Choose a reason for hiding this comment

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

consider replacing with versioned release before merging to main

This GitHub branch reference is fine for development/testing, but should be replaced with a versioned release (via pkg.vc or npm) for production stability.

"@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",
Expand Down
121 changes: 70 additions & 51 deletions src/lib/stores/migration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
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';

type MigrationResource = AppwriteMigrationResource | FirebaseMigrationResource | NHostMigrationResource | SupabaseMigrationResource;

const initialFormData = {
users: {
root: false,
Expand All @@ -13,11 +20,14 @@ const initialFormData = {
},
functions: {
root: false,
env: false,
inactive: false
},
storage: {
root: false
},
sites: {
root: false,
inactive: false
}
};

Expand Down Expand Up @@ -48,69 +58,55 @@ export const ResourcesFriendly = {
table: { singular: 'Table', plural: 'Tables' },
index: { singular: 'Index', plural: 'Indexes' },
column: { singular: 'Column', plural: 'Columns' },
row: { singular: 'Row', plural: 'Rows' }
row: { singular: 'Row', plural: 'Rows' },
site: { singular: 'Site', plural: 'Sites' },
'site-deployment': { singular: 'Site Deployment', plural: 'Site Deployments' },
'site-variable': { singular: 'Site Variable', plural: 'Site Variables' }
};

// @todo: @itznotabug - check if other resources are correct and work fine!
export const providerResources: Record<Provider, Resources[]> = {
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 providerResources: Record<Provider, MigrationResource[]> = {
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(AppwriteMigrationResource.User);
}
if (formData.databases.root) {
addResource(Resources.Database);
addResource(Resources.Table);
addResource(Resources.Column);
addResource(Resources.Index);
addResource(AppwriteMigrationResource.Database);
addResource(AppwriteMigrationResource.Table);
addResource(AppwriteMigrationResource.Column);
addResource(AppwriteMigrationResource.Index);
}
if (formData.databases.rows) {
addResource(Resources.Row);
addResource(AppwriteMigrationResource.Row);
}
if (formData.storage.root) {
addResource(Resources.Bucket);
addResource(Resources.File);
addResource(AppwriteMigrationResource.Bucket);
addResource(AppwriteMigrationResource.File);
}
if (formData.functions.root) {
addResource(AppwriteMigrationResource.Function);
addResource(AppwriteMigrationResource.Environmentvariable);
addResource(AppwriteMigrationResource.Deployment);
}
Comment on lines +101 to +105
Copy link
Contributor

Choose a reason for hiding this comment

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

formData.functions.inactive checkbox is never checked - inactive deployments always included

The UI shows an "Include inactive deployments" checkbox for functions, but this code always adds Deployment when functions.root is true, regardless of the inactive checkbox state.

Either remove the checkbox from the UI or add conditional logic:

Suggested change
if (formData.functions.root) {
addResource(AppwriteMigrationResource.Function);
addResource(AppwriteMigrationResource.Environmentvariable);
addResource(AppwriteMigrationResource.Deployment);
}
if (formData.functions.root) {
addResource(AppwriteMigrationResource.Function);
addResource(AppwriteMigrationResource.Environmentvariable);
if (formData.functions.inactive) {
addResource(AppwriteMigrationResource.Deployment);
}
}

if (formData.sites.root) {
addResource(AppwriteMigrationResource.Site);
addResource(AppwriteMigrationResource.Sitevariable);
addResource(AppwriteMigrationResource.Sitedeployment);
}
Comment on lines 77 to 110
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

Respect functions/sites env toggles when building resources.

Right now functions.env and sites.env are ignored, so environment variables are always included when the root toggle is on. This breaks user intent.

🐛 Proposed fix
     if (formData.functions.root) {
         addResource(AppwriteMigrationResource.Function);
-        addResource(AppwriteMigrationResource.EnvironmentVariable);
     }
+    if (formData.functions.env) {
+        addResource(AppwriteMigrationResource.EnvironmentVariable);
+    }
     if (formData.functions.inactive) {
         addResource(AppwriteMigrationResource.Deployment);
     }
     if (formData.sites.root) {
         addResource(AppwriteMigrationResource.Site);
-        addResource(AppwriteMigrationResource.SiteVariable);
     }
+    if (formData.sites.env) {
+        addResource(AppwriteMigrationResource.SiteVariable);
+    }
     if (formData.sites.inactive) {
         addResource(AppwriteMigrationResource.SiteDeployment);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/stores/migration.ts` around lines 79 - 116, The code always adds
environment variable resources when functions.root or sites.root is true,
ignoring the specific env toggles; update the resource-building logic so
MigrationResource.EnvironmentVariable is only added when both
formData.functions.root and formData.functions.env are true (refer to
formData.functions and MigrationResource.EnvironmentVariable), and likewise only
add MigrationResource.SiteVariable when both formData.sites.root and
formData.sites.env are true (refer to formData.sites and
MigrationResource.SiteVariable); keep the existing
providerResources[provider].includes(...) guard and use the same
addResource(...) helper.

Comment on lines 85 to 110
Copy link
Contributor

Choose a reason for hiding this comment

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

hardcodes AppwriteMigrationResource enum for all providers - only works if all provider enums share identical values

While the addResource helper filters via providerResources[provider].includes(resource), this assumes AppwriteMigrationResource.User === FirebaseMigrationResource.User etc. If the SDK defines distinct enum instances per provider, this will fail.

Safer approach: use provider-specific enums or string literals:

Suggested change
if (formData.users.root) {
addResource(Resources.User);
addResource(AppwriteMigrationResource.User);
}
if (formData.databases.root) {
addResource(Resources.Database);
addResource(Resources.Table);
addResource(Resources.Column);
addResource(Resources.Index);
addResource(AppwriteMigrationResource.Database);
addResource(AppwriteMigrationResource.Table);
addResource(AppwriteMigrationResource.Column);
addResource(AppwriteMigrationResource.Index);
}
if (formData.databases.rows) {
addResource(Resources.Row);
addResource(AppwriteMigrationResource.Row);
}
if (formData.storage.root) {
addResource(Resources.Bucket);
addResource(Resources.File);
addResource(AppwriteMigrationResource.Bucket);
addResource(AppwriteMigrationResource.File);
}
if (formData.functions.root) {
addResource(AppwriteMigrationResource.Function);
addResource(AppwriteMigrationResource.Environmentvariable);
}
if (formData.functions.inactive) {
addResource(AppwriteMigrationResource.Deployment);
}
if (formData.sites.root) {
addResource(AppwriteMigrationResource.Site);
addResource(AppwriteMigrationResource.Sitevariable);
}
if (formData.sites.inactive) {
addResource(AppwriteMigrationResource.Sitedeployment);
}
if (formData.users.root) {
addResource('user' as MigrationResource);
}
if (formData.databases.root) {
addResource('database' as MigrationResource);
addResource('table' as MigrationResource);
addResource('column' as MigrationResource);
addResource('index' as MigrationResource);
}
if (formData.databases.rows) {
addResource('row' as MigrationResource);
}
if (formData.storage.root) {
addResource('bucket' as MigrationResource);
addResource('file' as MigrationResource);
}
if (formData.functions.root) {
addResource('function' as MigrationResource);
addResource('environment-variable' as MigrationResource);
}
if (formData.functions.inactive) {
addResource('deployment' as MigrationResource);
}
if (formData.sites.root) {
addResource('site' as MigrationResource);
addResource('site-variable' as MigrationResource);
}
if (formData.sites.inactive) {
addResource('site-deployment' as MigrationResource);
}

Comment on lines +106 to 110
Copy link
Contributor

Choose a reason for hiding this comment

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

formData.sites.inactive checkbox is never checked - inactive deployments always included

Same issue as functions - the UI shows an "Include inactive deployments" checkbox for sites, but this code always adds Sitedeployment regardless of the checkbox state.

Suggested change
if (formData.sites.root) {
addResource(AppwriteMigrationResource.Site);
addResource(AppwriteMigrationResource.Sitevariable);
addResource(AppwriteMigrationResource.Sitedeployment);
}
if (formData.sites.root) {
addResource(AppwriteMigrationResource.Site);
addResource(AppwriteMigrationResource.Sitevariable);
if (formData.sites.inactive) {
addResource(AppwriteMigrationResource.Sitedeployment);
}
}


return resources;
Comment on lines 74 to 112
Copy link
Contributor

Choose a reason for hiding this comment

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

hardcodes AppwriteMigrationResource enum values for all providers

Suggested change
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(AppwriteMigrationResource.User);
}
if (formData.databases.root) {
addResource(Resources.Database);
addResource(Resources.Table);
addResource(Resources.Column);
addResource(Resources.Index);
addResource(AppwriteMigrationResource.Database);
addResource(AppwriteMigrationResource.Table);
addResource(AppwriteMigrationResource.Column);
addResource(AppwriteMigrationResource.Index);
}
if (formData.databases.rows) {
addResource(Resources.Row);
addResource(AppwriteMigrationResource.Row);
}
if (formData.storage.root) {
addResource(Resources.Bucket);
addResource(Resources.File);
addResource(AppwriteMigrationResource.Bucket);
addResource(AppwriteMigrationResource.File);
}
if (formData.functions.root) {
addResource(AppwriteMigrationResource.Function);
addResource(AppwriteMigrationResource.EnvironmentVariable);
}
if (formData.functions.inactive) {
addResource(AppwriteMigrationResource.Deployment);
}
if (formData.sites.root) {
addResource(AppwriteMigrationResource.Site);
addResource(AppwriteMigrationResource.SiteVariable);
}
if (formData.sites.inactive) {
addResource(AppwriteMigrationResource.SiteDeployment);
}
return resources;
export const migrationFormToResources = (
formData: MigrationFormData,
provider: Provider
): MigrationResource[] => {
const resources: MigrationResource[] = [];
const providerEnum = {
appwrite: AppwriteMigrationResource,
firebase: FirebaseMigrationResource,
nhost: NHostMigrationResource,
supabase: SupabaseMigrationResource
}[provider];
const addResource = (resource: MigrationResource) => {
if (providerResources[provider].includes(resource)) {
resources.push(resource);
}
};
if (formData.users.root) {
addResource(providerEnum.User);
}
if (formData.databases.root) {
addResource(providerEnum.Database);
addResource(providerEnum.Table);
addResource(providerEnum.Column);
addResource(providerEnum.Index);
}
if (formData.databases.rows) {
addResource(providerEnum.Row);
}
if (formData.storage.root) {
addResource(providerEnum.Bucket);
addResource(providerEnum.File);
}
if (formData.functions.root) {
addResource(providerEnum.Function);
addResource(providerEnum.EnvironmentVariable);
}
if (formData.functions.inactive) {
addResource(providerEnum.Deployment);
}
if (formData.sites.root) {
addResource(providerEnum.Site);
addResource(providerEnum.SiteVariable);
}
if (formData.sites.inactive) {
addResource(providerEnum.SiteDeployment);
}
return resources;
};

Expand All @@ -137,20 +133,43 @@ 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(AppwriteMigrationResource.User)) {
formData.users.root = true;
}
if (resources.includes(Resources.Database)) {
if (resources.includes(AppwriteMigrationResource.Database)) {
formData.databases.root = true;
}
if (includesAll(resources, [Resources.Table, Resources.Column, Resources.Row] as Resources[])) {
if (
includesAll(resources, [
AppwriteMigrationResource.Table,
AppwriteMigrationResource.Column,
AppwriteMigrationResource.Row
] as MigrationResource[])
) {
formData.databases.rows = true;
}
if (includesAll(resources, [Resources.Bucket, Resources.File] as Resources[])) {
if (
includesAll(resources, [
AppwriteMigrationResource.Bucket,
AppwriteMigrationResource.File
] as MigrationResource[])
) {
formData.storage.root = true;
}
if (resources.includes(AppwriteMigrationResource.Function)) {
formData.functions.root = true;
}
if (resources.includes(AppwriteMigrationResource.Deployment)) {
formData.functions.inactive = true;
}
if (resources.includes(AppwriteMigrationResource.Site)) {
formData.sites.root = true;
}
if (resources.includes(AppwriteMigrationResource.Sitedeployment)) {
formData.sites.inactive = true;
}
Comment on lines +138 to +172
Copy link
Contributor

Choose a reason for hiding this comment

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

same hardcoded enum issue - assumes all provider enums have identical references

The resources array could contain Firebase/Supabase/NHost enum values, but code only checks AppwriteMigrationResource values.


return formData;
Comment on lines 136 to 174
Copy link
Contributor

Choose a reason for hiding this comment

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

same issue - only checks AppwriteMigrationResource enum values when the input could be from any provider

The function should determine which provider enum to use based on the actual enum values in the input array, or accept a provider parameter.

};
Expand Down
26 changes: 16 additions & 10 deletions src/routes/(console)/(migration-wizard)/resource-form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
} 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 { AppwriteMigrationResource, type Models } from '@appwrite.io/console';
import type { sdk } from '$lib/stores/sdk';
import ImportReport from '$routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte';

Expand Down Expand Up @@ -94,19 +94,24 @@
$: resources = providerResources[$provider.provider];

const shouldRenderGroup = (groupKey: string): boolean => {
if (groupKey === 'storage') {
return (
resources.includes(AppwriteMigrationResource.Bucket) &&
resources.includes(AppwriteMigrationResource.File)
);
}

if (groupKey === 'functions') {
// Functions not in SDK Resources enum, skip
return false;
return resources.includes(AppwriteMigrationResource.Function);
}

if (groupKey === 'storage') {
return resources.includes(Resources.Bucket) && resources.includes(Resources.File);
if (groupKey === 'sites') {
return resources.includes(AppwriteMigrationResource.Site);
}

// Map groupKey to Resources enum
const groupToResource: Record<string, Resources> = {
users: Resources.User,
databases: Resources.Database
const groupToResource: Record<string, string> = {
users: AppwriteMigrationResource.User,
databases: AppwriteMigrationResource.Database
};
const resource = groupToResource[groupKey];
return resource ? resources.includes(resource) : false;
Expand All @@ -122,7 +127,8 @@
users: 'user',
databases: 'database',
functions: 'function',
storage: 'bucket'
storage: 'bucket',
sites: 'site'
};
return map[groupKey] || groupKey;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
databases: { root: 'Databases', rows: 'Include rows' },
functions: {
root: 'Functions',
env: 'Include environment variables',
inactive: 'Include inactive deployments'
},
storage: { root: 'Storage' }
storage: { root: 'Storage' },
sites: {
root: 'Sites',
inactive: 'Include inactive deployments'
}
};

const descriptionMap = {
Expand All @@ -40,7 +43,10 @@
},
functions: {
root: 'Import all functions and their active deployment',
env: 'Import all environment variables',
inactive: 'Import all deployments that are not currently active'
},
sites: {
root: 'Import all sites and their active deployment',
inactive: 'Import all deployments that are not currently active'
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export async function load({ depends, params }) {
Query.or([
Query.equal('destination', ['Appwrite', 'Firebase', 'NHost', 'Supabase']),
Query.isNull('destination')
])
]),
Query.orderDesc('$createdAt')
]
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@
<div>
<span class="u-capitalize"
>{total(Object.values(entityCounter)) > 1
? ResourcesFriendly[entity].plural
: ResourcesFriendly[entity].singular}</span>
? (ResourcesFriendly[entity]?.plural ?? entity)
: (ResourcesFriendly[entity]?.singular ?? entity)}</span>

<Tag size="xs" selected>{totalItems(entityCounter)}</Tag>
</div>
Expand Down