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
2 changes: 1 addition & 1 deletion templates/cli/lib/commands/generic.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const ID = require("../id");
const { questionsLogin, questionsLogout, questionsListFactors, questionsMfaChallenge } = require("../questions");
const { accountUpdateMfaChallenge, accountCreateMfaChallenge, accountGet, accountCreateEmailPasswordSession, accountDeleteSession } = require("./account");

const DEFAULT_ENDPOINT = 'https://cloud.appwrite.io/v1';
const DEFAULT_ENDPOINT = '{{ spec.endpoint }}';

const loginCommand = async ({ email, password, endpoint, mfa, code }) => {
const oldCurrent = globalConfig.getCurrentSession();
Expand Down
12 changes: 11 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 @@ -94,6 +95,9 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
}
}

localConfig.clear(); // Clear the config to avoid any conflicts
const url = new URL("{{ spec.endpoint }}");

if (answers.start === 'new') {
response = await projectsCreate({
projectId: answers.id,
Expand All @@ -103,8 +107,14 @@ const initProject = async ({ organizationId, projectId, projectName } = {}) => {
})

localConfig.setProject(response['$id']);
if (answers.region) {
localConfig.setEndpoint(`https://${answers.region}.${url.host}${url.pathname}`);
}
} else {
localConfig.setProject(answers.project);
localConfig.setProject(answers.project["$id"]);
if(isCloud()) {
localConfig.setEndpoint(`https://${answers.project["region"]}.${url.host}${url.pathname}`);
}
}

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
4 changes: 2 additions & 2 deletions templates/cli/lib/parser.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { description } = require('../package.json');
const { globalConfig } = require("./config.js");
const os = require('os');
const Client = require("./client");
const { isCloud } = require("./utils");

const cliConfig = {
verbose: false,
Expand Down Expand Up @@ -111,7 +112,6 @@ const parseError = (err) => {
(async () => {
let appwriteVersion = 'unknown';
const endpoint = globalConfig.getEndpoint();
const isCloud = endpoint.includes('cloud.appwrite.io') ? 'Yes' : 'No';

try {
const client = new Client().setEndpoint(endpoint);
Expand All @@ -122,7 +122,7 @@ const parseError = (err) => {

const version = '{{ sdk.version }}';
const stepsToReproduce = `Running \`appwrite ${cliConfig.reportData.data.args.join(' ')}\``;
const yourEnvironment = `CLI version: ${version}\nOperation System: ${os.type()}\nAppwrite version: ${appwriteVersion}\nIs Cloud: ${isCloud}`;
const yourEnvironment = `CLI version: ${version}\nOperation System: ${os.type()}\nAppwrite version: ${appwriteVersion}\nIs Cloud: ${isCloud()}`;

const stack = '```\n' + err.stack + '\n```';

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
2 changes: 1 addition & 1 deletion templates/cli/lib/type-generation/languages/dart.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class <%= toPascalCase(collection.name) %> {
(map['<%= attribute.key %>'] as List<dynamic>?)?.map((e) => <%- toPascalCase(attribute.key) %>.values.firstWhere((element) => element.name == e)).toList()<% if (!attribute.required) { %> ?? []<% } -%>
<% } else { -%>
<% if (!attribute.required) { -%>
map['<%= attribute.key %>'] != null ? <%- toPascalCase(attribute.key) %>.values.where((e) => e.name == map['<%= attribute.key %>']).firstOrNull() : null<% } else { -%>
map['<%= attribute.key %>'] != null ? <%- toPascalCase(attribute.key) %>.values.where((e) => e.name == map['<%= attribute.key %>']).firstOrNull : null<% } else { -%>
<%- toPascalCase(attribute.key) %>.values.firstWhere((e) => e.name == map['<%= attribute.key %>'])<% } -%>
<% } -%>
<% } else { -%>
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() || "{{ spec.endpoint }}";
const hostname = new URL(endpoint).hostname;
return hostname.endsWith('appwrite.io');
}

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