Skip to content

fix: add unsupported attribute error #1095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 27, 2025
13 changes: 12 additions & 1 deletion templates/cli/lib/commands/init.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const { cliConfig, success, log, hint, error, actionRunner, commandDescriptions
const { accountGet } = require("./account");
const { sitesListTemplates } = require("./sites");
const { sdkForConsole } = require("../sdks");
const { isCloud } = require('../utils');

const initResources = async () => {
const actions = {
Expand Down Expand Up @@ -95,6 +96,7 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
}

if (answers.start === 'new') {
localConfig.clear();
response = await projectsCreate({
projectId: answers.id,
name: answers.project,
Expand All @@ -103,8 +105,17 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
})

localConfig.setProject(response['$id']);

if (answers.region) {
const endpoint = `https://${answers.region}.cloud.appwrite.io/v1`;
localConfig.setEndpoint(endpoint);
}
} else {
localConfig.setProject(answers.project);
localConfig.setProject(answers.project["$id"]);
if(isCloud()) {
const endpoint = `https://${answers.project["region"]}.cloud.appwrite.io/v1`;
localConfig.setEndpoint(endpoint);
}
}

success(`Project successfully ${answers.start === 'existing' ? 'linked' : 'created'}. Details are now stored in appwrite.json file.`);
Expand Down
47 changes: 39 additions & 8 deletions templates/cli/lib/commands/push.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ const createAttribute = (databaseId, collectionId, attribute) => {
onDelete: attribute.onDelete,
parseOutput: false
})
default:
throw new Error(`Unsupported attribute type: ${attribute.type}`);
}
}

Expand Down Expand Up @@ -685,6 +687,8 @@ const updateAttribute = (databaseId, collectionId, attribute) => {
onDelete: attribute.onDelete,
parseOutput: false
})
default:
throw new Error(`Unsupported attribute type: ${attribute.type}`);
}
}
const deleteAttribute = async (collection, attribute, isIndex = false) => {
Expand All @@ -708,6 +712,33 @@ const deleteAttribute = async (collection, attribute, isIndex = false) => {
});
}

const isEqual = (a, b) => {
if (a === b) return true;

if (a && b && typeof a === 'object' && typeof b === 'object') {
if (a.constructor && a.constructor.name === 'BigNumber' &&
b.constructor && b.constructor.name === 'BigNumber') {
return a.eq(b);
}

if (typeof a.equals === 'function') {
return a.equals(b);
}

if (typeof a.eq === 'function') {
return a.eq(b);
}
}

if (typeof a === 'number' && typeof b === 'number') {
if (isNaN(a) && isNaN(b)) return true;
if (!isFinite(a) && !isFinite(b)) return a === b;
return Math.abs(a - b) < Number.EPSILON;
}

return false;
};

const compareAttribute = (remote, local, reason, key) => {
if (isEmpty(remote) && isEmpty(local)) {
return reason;
Expand All @@ -718,7 +749,7 @@ const compareAttribute = (remote, local, reason, key) => {
const bol = reason === '' ? '' : '\n';
reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;
}
} else if (remote !== local) {
} else if (!isEqual(remote, local)) {
const bol = reason === '' ? '' : '\n';
reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;
}
Expand All @@ -733,16 +764,16 @@ const compareAttribute = (remote, local, reason, key) => {
* @param remote
* @param local
* @param collection
* @param recraeting when true will check only non-changeable keys
* @param recreating when true will check only non-changeable keys
* @returns {undefined|{reason: string, action: *, attribute, key: string}}
*/
const checkAttributeChanges = (remote, local, collection, recraeting = true) => {
const checkAttributeChanges = (remote, local, collection, recreating = true) => {
if (local === undefined) {
return undefined;
}

const keyName = `${chalk.yellow(local.key)} in ${collection.name} (${collection['$id']})`;
const action = chalk.cyan(recraeting ? 'recreating' : 'changing');
const action = chalk.cyan(recreating ? 'recreating' : 'changing');
let reason = '';
let attribute = remote;

Expand All @@ -752,17 +783,17 @@ const checkAttributeChanges = (remote, local, collection, recraeting = true) =>
}

if (changeableKeys.includes(key)) {
if (!recraeting) {
reason += compareAttribute(remote[key], local[key], reason, key)
if (!recreating) {
reason = compareAttribute(remote[key], local[key], reason, key)
Comment on lines -756 to +787
Copy link
Contributor

Choose a reason for hiding this comment

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

don't we want to concat so we keep all the reasons?

Copy link
Member Author

Choose a reason for hiding this comment

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

we are already concatenating the reasons here -

reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;
}
} else if (!isEqual(remote, local)) {
const bol = reason === '' ? '' : '\n';
reason += `${bol}${key} changed from ${chalk.red(remote)} to ${chalk.green(local)}`;

concatenating here as well leads to double concat, as shown in the pr screenshot

}
continue;
}

if (!recraeting) {
if (!recreating) {
continue;
}

reason += compareAttribute(remote[key], local[key], reason, key)
reason = compareAttribute(remote[key], local[key], reason, key)
}

return reason === '' ? undefined : { key: keyName, attribute, reason, action };
Expand Down
8 changes: 8 additions & 0 deletions templates/cli/lib/config.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ class Local extends Config {
return _path.dirname(this.path)
}

getEndpoint() {
return this.get('endpoint') || '';
}

setEndpoint(endpoint) {
this.set('endpoint', endpoint);
}

getSites() {
if (!this.has("sites")) {
return [];
Expand Down
33 changes: 28 additions & 5 deletions templates/cli/lib/questions.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { validateRequired } = require("./validations");
const { paginate } = require('./paginate');
const { isPortTaken } = require('./utils');
const { databasesList } = require('./commands/databases');
const { checkDeployConditions } = require('./utils');
const { checkDeployConditions, isCloud } = require('./utils');
const JSONbig = require("json-bigint")({ storeAsString: false });
const { sitesListFrameworks, sitesListSpecifications, sitesList } = require('./commands/sites');

Expand Down Expand Up @@ -154,8 +154,7 @@ const questionsInitProject = [
message: "Choose your organization",
choices: async () => {
let client = await sdkForConsole(true);
const hostname = new URL(client.endpoint).hostname;
const { teams } = hostname.endsWith('appwrite.io')
const { teams } = isCloud()
? await paginate(organizationsList, { parseOutput: false, sdk: client }, 100, 'teams')
: await paginate(teamsList, { parseOutput: false, sdk: client }, 100, 'teams');

Expand Down Expand Up @@ -203,17 +202,41 @@ const questionsInitProject = [
let choices = projects.map((project) => {
return {
name: `${project.name} (${project['$id']})`,
value: project['$id']
value: {
"$id": project['$id'],
"region": project.region || ''
}
}
})

if (choices.length == 0) {
if (choices.length === 0) {
throw new Error("No projects found. Please create a new project.")
}

return choices;
},
when: (answer) => answer.start === 'existing'
},
{
type: "list",
name: "region",
message: "Select your Appwrite Cloud region",
choices: async () => {
let client = await sdkForConsole(true);
let response = await client.call("GET", "/console/regions");
let regions = response.regions || [];
if (!regions.length) {
throw new Error("No regions found. Please check your network or Appwrite Cloud availability.");
}
return regions.filter(region => !region.disabled).map(region => ({
name: `${region.name} (${region.$id})`,
value: region.$id
}));
},
when: (answer) => {
if (answer.start === 'existing') return false;
return isCloud();
}
}
];
const questionsInitProjectAutopull = [
Expand Down
2 changes: 1 addition & 1 deletion templates/cli/lib/sdks.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const sdkForConsole = async (requiresAuth = true) => {

const sdkForProject = async () => {
let client = new Client();
let endpoint = globalConfig.getEndpoint();
let endpoint = localConfig.getEndpoint() || globalConfig.getEndpoint();
let project = localConfig.getProject().projectId ? localConfig.getProject().projectId : globalConfig.getProject();
let key = globalConfig.getKey();
let cookie = globalConfig.getCookie()
Expand Down
2 changes: 1 addition & 1 deletion templates/cli/lib/type-generation/attribute.js.twig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const AttributeType = {
STRING: "string",
INTEGER: "integer",
FLOAT: "float",
FLOAT: "double",
BOOLEAN: "boolean",
DATETIME: "datetime",
EMAIL: "email",
Expand Down
9 changes: 8 additions & 1 deletion templates/cli/lib/utils.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,17 @@ function getUsersPath(action, ids) {
return path;
}

function isCloud() {
const endpoint = globalConfig.getEndpoint() || "https://cloud.appwrite.io/v1";
const hostname = new URL(endpoint).hostname;
return hostname.endsWith('appwrite.io');
}

module.exports = {
getAllFiles,
isPortTaken,
systemHasCommand,
checkDeployConditions,
showConsoleLink
showConsoleLink,
isCloud
};