appwrite-cli 13.6.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.github/workflows/ci.yml +66 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +2 -2
  4. package/cli.ts +3 -3
  5. package/dist/bundle-win-arm64.mjs +608 -310
  6. package/dist/cli.cjs +608 -310
  7. package/dist/index.cjs +354 -183
  8. package/dist/index.js +354 -183
  9. package/dist/lib/commands/config-validations.d.ts +1 -1
  10. package/dist/lib/commands/config-validations.d.ts.map +1 -1
  11. package/dist/lib/commands/errors.d.ts +4 -4
  12. package/dist/lib/commands/errors.d.ts.map +1 -1
  13. package/dist/lib/commands/generate.d.ts +2 -0
  14. package/dist/lib/commands/generate.d.ts.map +1 -1
  15. package/dist/lib/commands/generators/base.d.ts +25 -2
  16. package/dist/lib/commands/generators/base.d.ts.map +1 -1
  17. package/dist/lib/commands/generators/index.d.ts +1 -1
  18. package/dist/lib/commands/generators/index.d.ts.map +1 -1
  19. package/dist/lib/commands/generators/typescript/databases.d.ts +2 -2
  20. package/dist/lib/commands/generators/typescript/databases.d.ts.map +1 -1
  21. package/dist/lib/commands/generic.d.ts.map +1 -1
  22. package/dist/lib/commands/init.d.ts.map +1 -1
  23. package/dist/lib/commands/run.d.ts.map +1 -1
  24. package/dist/lib/commands/types.d.ts.map +1 -1
  25. package/dist/lib/commands/update.d.ts.map +1 -1
  26. package/dist/lib/commands/utils/change-approval.d.ts +3 -3
  27. package/dist/lib/commands/utils/change-approval.d.ts.map +1 -1
  28. package/dist/lib/commands/utils/database-sync.d.ts.map +1 -1
  29. package/dist/lib/commands/utils/deployment.d.ts +16 -4
  30. package/dist/lib/commands/utils/deployment.d.ts.map +1 -1
  31. package/dist/lib/commands/utils/pools.d.ts.map +1 -1
  32. package/dist/lib/constants.d.ts +1 -1
  33. package/dist/lib/emulation/docker.d.ts.map +1 -1
  34. package/dist/lib/json.d.ts +1 -1
  35. package/dist/lib/json.d.ts.map +1 -1
  36. package/dist/lib/paginate.d.ts +5 -6
  37. package/dist/lib/paginate.d.ts.map +1 -1
  38. package/dist/lib/parser.d.ts +5 -4
  39. package/dist/lib/parser.d.ts.map +1 -1
  40. package/dist/lib/spinner.d.ts +1 -1
  41. package/dist/lib/spinner.d.ts.map +1 -1
  42. package/dist/lib/utils.d.ts +6 -1
  43. package/dist/lib/utils.d.ts.map +1 -1
  44. package/dist/lib/validations.d.ts +1 -1
  45. package/dist/lib/validations.d.ts.map +1 -1
  46. package/docs/examples/projects/update-status.md +5 -0
  47. package/docs/examples/sites/create-deployment.md +1 -2
  48. package/eslint.config.js +45 -0
  49. package/install.ps1 +2 -2
  50. package/install.sh +1 -1
  51. package/lib/client.ts +3 -3
  52. package/lib/commands/config-validations.ts +1 -1
  53. package/lib/commands/config.ts +2 -2
  54. package/lib/commands/errors.ts +2 -2
  55. package/lib/commands/generate.ts +23 -8
  56. package/lib/commands/generators/base.ts +33 -2
  57. package/lib/commands/generators/index.ts +1 -0
  58. package/lib/commands/generators/typescript/databases.ts +31 -21
  59. package/lib/commands/generators/typescript/templates/databases.ts.hbs +16 -16
  60. package/lib/commands/generic.ts +21 -16
  61. package/lib/commands/init.ts +147 -61
  62. package/lib/commands/pull.ts +1 -1
  63. package/lib/commands/push.ts +19 -19
  64. package/lib/commands/run.ts +15 -9
  65. package/lib/commands/services/account.ts +1 -1
  66. package/lib/commands/services/databases.ts +20 -19
  67. package/lib/commands/services/health.ts +13 -0
  68. package/lib/commands/services/messaging.ts +1 -1
  69. package/lib/commands/services/projects.ts +25 -0
  70. package/lib/commands/services/sites.ts +8 -3
  71. package/lib/commands/services/tables-db.ts +3 -2
  72. package/lib/commands/services/teams.ts +2 -2
  73. package/lib/commands/types.ts +18 -8
  74. package/lib/commands/update.ts +24 -16
  75. package/lib/commands/utils/attributes.ts +6 -6
  76. package/lib/commands/utils/change-approval.ts +26 -19
  77. package/lib/commands/utils/database-sync.ts +58 -18
  78. package/lib/commands/utils/deployment.ts +22 -5
  79. package/lib/commands/utils/pools.ts +11 -5
  80. package/lib/config.ts +1 -1
  81. package/lib/constants.ts +1 -1
  82. package/lib/emulation/docker.ts +5 -6
  83. package/lib/emulation/utils.ts +2 -2
  84. package/lib/json.ts +15 -7
  85. package/lib/paginate.ts +30 -20
  86. package/lib/parser.ts +46 -15
  87. package/lib/questions.ts +38 -38
  88. package/lib/spinner.ts +5 -1
  89. package/lib/utils.ts +15 -3
  90. package/lib/validations.ts +1 -1
  91. package/package.json +8 -2
  92. package/scoop/appwrite.config.json +3 -3
@@ -314,7 +314,7 @@ databases
314
314
  .description(`Create a boolean attribute.
315
315
  `)
316
316
  .requiredOption(`--database-id <database-id>`, `Database ID.`)
317
- .requiredOption(`--collection-id <collection-id>`, `Collection ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).`)
317
+ .requiredOption(`--collection-id <collection-id>`, `Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).`)
318
318
  .requiredOption(`--key <key>`, `Attribute Key.`)
319
319
  .requiredOption(`--required <required>`, `Is attribute required?`, parseBool)
320
320
  .option(
@@ -801,6 +801,22 @@ databases
801
801
  ),
802
802
  );
803
803
 
804
+ databases
805
+ .command(`update-relationship-attribute`)
806
+ .description(`Update relationship attribute. [Learn more about relationship attributes](https://appwrite.io/docs/databases-relationships#relationship-attributes).
807
+ `)
808
+ .requiredOption(`--database-id <database-id>`, `Database ID.`)
809
+ .requiredOption(`--collection-id <collection-id>`, `Collection ID.`)
810
+ .requiredOption(`--key <key>`, `Attribute Key.`)
811
+ .option(`--on-delete <on-delete>`, `Constraints option`)
812
+ .option(`--new-key <new-key>`, `New Attribute Key.`)
813
+ .action(
814
+ actionRunner(
815
+ async ({ databaseId, collectionId, key, onDelete, newKey }) =>
816
+ parse(await (await getDatabasesClient()).updateRelationshipAttribute(databaseId, collectionId, key, onDelete, newKey)),
817
+ ),
818
+ );
819
+
804
820
  databases
805
821
  .command(`create-string-attribute`)
806
822
  .description(`Create a string attribute.
@@ -1005,22 +1021,6 @@ databases
1005
1021
  ),
1006
1022
  );
1007
1023
 
1008
- databases
1009
- .command(`update-relationship-attribute`)
1010
- .description(`Update relationship attribute. [Learn more about relationship attributes](https://appwrite.io/docs/databases-relationships#relationship-attributes).
1011
- `)
1012
- .requiredOption(`--database-id <database-id>`, `Database ID.`)
1013
- .requiredOption(`--collection-id <collection-id>`, `Collection ID.`)
1014
- .requiredOption(`--key <key>`, `Attribute Key.`)
1015
- .option(`--on-delete <on-delete>`, `Constraints option`)
1016
- .option(`--new-key <new-key>`, `New Attribute Key.`)
1017
- .action(
1018
- actionRunner(
1019
- async ({ databaseId, collectionId, key, onDelete, newKey }) =>
1020
- parse(await (await getDatabasesClient()).updateRelationshipAttribute(databaseId, collectionId, key, onDelete, newKey)),
1021
- ),
1022
- );
1023
-
1024
1024
  databases
1025
1025
  .command(`list-documents`)
1026
1026
  .description(`Get a list of all the user's documents in a given collection. You can use the query params to filter your results.`)
@@ -1034,10 +1034,11 @@ databases
1034
1034
  (value: string | undefined) =>
1035
1035
  value === undefined ? true : parseBool(value),
1036
1036
  )
1037
+ .option(`--ttl <ttl>`, `TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).`, parseInteger)
1037
1038
  .action(
1038
1039
  actionRunner(
1039
- async ({ databaseId, collectionId, queries, transactionId, total }) =>
1040
- parse(await (await getDatabasesClient()).listDocuments(databaseId, collectionId, queries, transactionId, total)),
1040
+ async ({ databaseId, collectionId, queries, transactionId, total, ttl }) =>
1041
+ parse(await (await getDatabasesClient()).listDocuments(databaseId, collectionId, queries, transactionId, total, ttl)),
1041
1042
  ),
1042
1043
  );
1043
1044
 
@@ -64,6 +64,19 @@ health
64
64
  ),
65
65
  );
66
66
 
67
+ health
68
+ .command(`get-console-pausing`)
69
+ .description(`Get console pausing health status. Monitors projects approaching the pause threshold to detect potential issues with console access tracking.
70
+ `)
71
+ .option(`--threshold <threshold>`, `Percentage threshold of projects approaching pause. When hit (equal or higher), endpoint returns server error. Default value is 10.`, parseInteger)
72
+ .option(`--inactivity-days <inactivity-days>`, `Number of days of inactivity before a project is paused. Should match the plan's projectInactivityDays setting. Default value is 7.`, parseInteger)
73
+ .action(
74
+ actionRunner(
75
+ async ({ threshold, inactivityDays }) =>
76
+ parse(await (await getHealthClient()).getConsolePausing(threshold, inactivityDays)),
77
+ ),
78
+ );
79
+
67
80
  health
68
81
  .command(`get-db`)
69
82
  .description(`Check the Appwrite database servers are up and connection is successful.`)
@@ -993,7 +993,7 @@ messaging
993
993
  .command(`list-subscribers`)
994
994
  .description(`Get a list of all subscribers from the current Appwrite project.`)
995
995
  .requiredOption(`--topic-id <topic-id>`, `Topic ID. The topic ID subscribed to.`)
996
- .option(`--queries [queries...]`, `Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, provider, type, enabled`)
996
+ .option(`--queries [queries...]`, `Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: targetId, topicId, userId, providerType`)
997
997
  .option(`--search <search>`, `Search term to filter your list results. Max length: 256 chars.`)
998
998
  .option(
999
999
  `--total [value]`,
@@ -270,6 +270,18 @@ projects
270
270
  ),
271
271
  );
272
272
 
273
+ projects
274
+ .command(`update-console-access`)
275
+ .description(`Record console access to a project. This endpoint updates the last accessed timestamp for the project to track console activity.
276
+ `)
277
+ .requiredOption(`--project-id <project-id>`, `Project ID`)
278
+ .action(
279
+ actionRunner(
280
+ async ({ projectId }) =>
281
+ parse(await (await getProjectsClient()).updateConsoleAccess(projectId)),
282
+ ),
283
+ );
284
+
273
285
  projects
274
286
  .command(`list-dev-keys`)
275
287
  .description(`List all the project\'s dev keys. Dev keys are project specific and allow you to bypass rate limits and get better error logging during development.'`)
@@ -639,6 +651,19 @@ projects
639
651
  ),
640
652
  );
641
653
 
654
+ projects
655
+ .command(`update-status`)
656
+ .description(`Update the status of a project. Can be used to archive/restore projects, and to restore paused projects. When restoring a paused project, the console fingerprint header must be provided and the project must not be blocked for any reason other than inactivity.
657
+ `)
658
+ .requiredOption(`--project-id <project-id>`, `Project ID`)
659
+ .requiredOption(`--status <status>`, `New status for the project`)
660
+ .action(
661
+ actionRunner(
662
+ async ({ projectId, status }) =>
663
+ parse(await (await getProjectsClient()).updateStatus(projectId, status)),
664
+ ),
665
+ );
666
+
642
667
  projects
643
668
  .command(`update-team`)
644
669
  .description(`Update the team ID of a project allowing for it to be transferred to another team.`)
@@ -243,14 +243,19 @@ sites
243
243
  .description(`Create a new site code deployment. Use this endpoint to upload a new version of your site code. To activate your newly uploaded code, you'll need to update the site's deployment to use your new deployment ID.`)
244
244
  .requiredOption(`--site-id <site-id>`, `Site ID.`)
245
245
  .requiredOption(`--code <code>`, `Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.`)
246
- .requiredOption(`--activate <activate>`, `Automatically activate the deployment when it is finished building.`, parseBool)
247
246
  .option(`--install-command <install-command>`, `Install Commands.`)
248
247
  .option(`--build-command <build-command>`, `Build Commands.`)
249
248
  .option(`--output-directory <output-directory>`, `Output Directory.`)
249
+ .option(
250
+ `--activate [value]`,
251
+ `Automatically activate the deployment when it is finished building.`,
252
+ (value: string | undefined) =>
253
+ value === undefined ? true : parseBool(value),
254
+ )
250
255
  .action(
251
256
  actionRunner(
252
- async ({ siteId, code, activate, installCommand, buildCommand, outputDirectory }) =>
253
- parse(await (await getSitesClient()).createDeployment(siteId, code, activate, installCommand, buildCommand, outputDirectory)),
257
+ async ({ siteId, code, installCommand, buildCommand, outputDirectory, activate }) =>
258
+ parse(await (await getSitesClient()).createDeployment(siteId, code, installCommand, buildCommand, outputDirectory, activate)),
254
259
  ),
255
260
  );
256
261
 
@@ -1109,10 +1109,11 @@ tablesDB
1109
1109
  (value: string | undefined) =>
1110
1110
  value === undefined ? true : parseBool(value),
1111
1111
  )
1112
+ .option(`--ttl <ttl>`, `TTL (seconds) for cached responses when caching is enabled for select queries. Must be between 0 and 86400 (24 hours).`, parseInteger)
1112
1113
  .action(
1113
1114
  actionRunner(
1114
- async ({ databaseId, tableId, queries, transactionId, total }) =>
1115
- parse(await (await getTablesDBClient()).listRows(databaseId, tableId, queries, transactionId, total)),
1115
+ async ({ databaseId, tableId, queries, transactionId, total, ttl }) =>
1116
+ parse(await (await getTablesDBClient()).listRows(databaseId, tableId, queries, transactionId, total, ttl)),
1116
1117
  ),
1117
1118
  );
1118
1119
 
@@ -139,7 +139,7 @@ Use the \`url\` parameter to redirect the user from the invitation email to your
139
139
  Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.
140
140
  `)
141
141
  .requiredOption(`--team-id <team-id>`, `Team ID.`)
142
- .requiredOption(`--roles [roles...]`, `Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`)
142
+ .requiredOption(`--roles [roles...]`, `Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long.`)
143
143
  .option(`--email <email>`, `Email of the new team member.`)
144
144
  .option(`--user-id <user-id>`, `ID of the user to be added to a team.`)
145
145
  .option(`--phone <phone>`, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`)
@@ -170,7 +170,7 @@ teams
170
170
  `)
171
171
  .requiredOption(`--team-id <team-id>`, `Team ID.`)
172
172
  .requiredOption(`--membership-id <membership-id>`, `Membership ID.`)
173
- .requiredOption(`--roles [roles...]`, `An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`)
173
+ .requiredOption(`--roles [roles...]`, `An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 81 characters long.`)
174
174
  .action(
175
175
  actionRunner(
176
176
  async ({ teamId, membershipId, roles }) =>
@@ -83,6 +83,16 @@ interface TypesOptions {
83
83
  strict: boolean;
84
84
  }
85
85
 
86
+ type TypeAttribute = Record<string, unknown> & {
87
+ relatedTable?: string;
88
+ };
89
+
90
+ type TypeDataItem = Record<string, unknown> & {
91
+ name: string;
92
+ attributes?: TypeAttribute[];
93
+ columns?: TypeAttribute[];
94
+ };
95
+
86
96
  const typesCommand = actionRunner(
87
97
  async (rawOutputDirectory: string, { language, strict }: TypesOptions) => {
88
98
  if (language === "auto") {
@@ -126,13 +136,11 @@ const typesCommand = actionRunner(
126
136
  }
127
137
 
128
138
  // Try tables first, fallback to collections
129
- let tables = localConfig.getTables();
130
- let collections: any[] = [];
131
- let dataSource = "tables";
139
+ const tables = localConfig.getTables();
140
+ let collections: TypeDataItem[] = [];
132
141
 
133
142
  if (tables.length === 0) {
134
143
  collections = localConfig.getCollections();
135
- dataSource = "collections";
136
144
 
137
145
  if (collections.length === 0) {
138
146
  const configFileName = path.basename(localConfig.path);
@@ -143,7 +151,8 @@ const typesCommand = actionRunner(
143
151
  }
144
152
 
145
153
  // Use tables if available, otherwise use collections
146
- let dataItems: any[] = tables.length > 0 ? tables : collections;
154
+ let dataItems: TypeDataItem[] =
155
+ tables.length > 0 ? (tables as TypeDataItem[]) : collections;
147
156
  const itemType = tables.length > 0 ? "tables" : "collections";
148
157
 
149
158
  // Normalize tables data: rename 'columns' to 'attributes' for template compatibility
@@ -152,7 +161,7 @@ const typesCommand = actionRunner(
152
161
  const { columns, ...rest } = table;
153
162
  return {
154
163
  ...rest,
155
- attributes: (columns || []).map((column: any) => {
164
+ attributes: (columns || []).map((column: TypeAttribute) => {
156
165
  if (column.relatedTable) {
157
166
  const { relatedTable, ...columnRest } = column;
158
167
  return {
@@ -167,14 +176,15 @@ const typesCommand = actionRunner(
167
176
  }
168
177
 
169
178
  log(
170
- `Found ${dataItems.length} ${itemType}: ${dataItems.map((c: any) => c.name).join(", ")}`,
179
+ `Found ${dataItems.length} ${itemType}: ${dataItems.map((c) => c.name).join(", ")}`,
171
180
  );
172
181
 
173
182
  // Use columns if available, otherwise use attributes
174
183
  const resourceType = tables.length > 0 ? "columns" : "attributes";
175
184
 
176
185
  const totalAttributes = dataItems.reduce(
177
- (count: number, item: any) => count + (item.attributes || []).length,
186
+ (count: number, item: TypeDataItem) =>
187
+ count + (item.attributes || []).length,
178
188
  0,
179
189
  );
180
190
  log(`Found ${totalAttributes} ${resourceType} across all ${itemType}`);
@@ -3,7 +3,11 @@ import { Command } from "commander";
3
3
  import chalk from "chalk";
4
4
  import inquirer from "inquirer";
5
5
  import { success, log, warn, error, hint, actionRunner } from "../parser.js";
6
- import { getLatestVersion, compareVersions } from "../utils.js";
6
+ import {
7
+ getLatestVersion,
8
+ compareVersions,
9
+ getErrorMessage,
10
+ } from "../utils.js";
7
11
  import {
8
12
  GITHUB_RELEASES_URL,
9
13
  NPM_PACKAGE_NAME,
@@ -12,6 +16,8 @@ import {
12
16
  import packageJson from "../../package.json" with { type: "json" };
13
17
  const { version } = packageJson;
14
18
 
19
+ type ExecCommandOptions = Exclude<Parameters<typeof spawn>[2], undefined>;
20
+
15
21
  /**
16
22
  * Check if the CLI was installed via npm
17
23
  */
@@ -36,7 +42,7 @@ const isInstalledViaNpm = (): boolean => {
36
42
  }
37
43
 
38
44
  return false;
39
- } catch (e) {
45
+ } catch (_e) {
40
46
  return false;
41
47
  }
42
48
  };
@@ -51,7 +57,7 @@ const isInstalledViaHomebrew = (): boolean => {
51
57
  scriptPath.includes("/opt/homebrew/") ||
52
58
  scriptPath.includes("/usr/local/Cellar/")
53
59
  );
54
- } catch (e) {
60
+ } catch (_e) {
55
61
  return false;
56
62
  }
57
63
  };
@@ -62,7 +68,7 @@ const isInstalledViaHomebrew = (): boolean => {
62
68
  const execCommand = (
63
69
  command: string,
64
70
  args: string[] = [],
65
- options: any = {},
71
+ options: ExecCommandOptions = {},
66
72
  ): Promise<void> => {
67
73
  return new Promise((resolve, reject) => {
68
74
  const child = spawn(command, args, {
@@ -94,17 +100,16 @@ const updateViaNpm = async (): Promise<void> => {
94
100
  console.log("");
95
101
  success("Updated to latest version via npm!");
96
102
  hint("Run 'appwrite --version' to verify the new version.");
97
- } catch (e: any) {
98
- if (
99
- e.message.includes("EEXIST") ||
100
- e.message.includes("file already exists")
101
- ) {
103
+ } catch (e: unknown) {
104
+ const message = getErrorMessage(e);
105
+
106
+ if (message.includes("EEXIST") || message.includes("file already exists")) {
102
107
  console.log("");
103
108
  success("Latest version is already installed via npm!");
104
109
  hint("The CLI is up to date. Run 'appwrite --version' to verify.");
105
110
  } else {
106
111
  console.log("");
107
- error(`Failed to update via npm: ${e.message}`);
112
+ error(`Failed to update via npm: ${message}`);
108
113
  hint(`Try running: npm install -g ${NPM_PACKAGE_NAME}@latest --force`);
109
114
  }
110
115
  }
@@ -119,17 +124,19 @@ const updateViaHomebrew = async (): Promise<void> => {
119
124
  console.log("");
120
125
  success("Updated to latest version via Homebrew!");
121
126
  hint("Run 'appwrite --version' to verify the new version.");
122
- } catch (e: any) {
127
+ } catch (e: unknown) {
128
+ const message = getErrorMessage(e);
129
+
123
130
  if (
124
- e.message.includes("already installed") ||
125
- e.message.includes("up-to-date")
131
+ message.includes("already installed") ||
132
+ message.includes("up-to-date")
126
133
  ) {
127
134
  console.log("");
128
135
  success("Latest version is already installed via Homebrew!");
129
136
  hint("The CLI is up to date. Run 'appwrite --version' to verify.");
130
137
  } else {
131
138
  console.log("");
132
- error(`Failed to update via Homebrew: ${e.message}`);
139
+ error(`Failed to update via Homebrew: ${message}`);
133
140
  hint("Try running: brew upgrade appwrite");
134
141
  }
135
142
  }
@@ -230,9 +237,10 @@ const updateCli = async ({ manual }: UpdateOptions = {}): Promise<void> => {
230
237
  } else {
231
238
  await chooseUpdateMethod(latestVersion);
232
239
  }
233
- } catch (e: any) {
240
+ } catch (e: unknown) {
241
+ const message = getErrorMessage(e);
234
242
  console.log("");
235
- error(`Failed to check for updates: ${e.message}`);
243
+ error(`Failed to check for updates: ${message}`);
236
244
  hint(`You can manually check for updates at: ${GITHUB_RELEASES_URL}`);
237
245
  }
238
246
  };
@@ -76,7 +76,7 @@ export class Attributes {
76
76
  return answers.changes;
77
77
  }
78
78
 
79
- let answers = await inquirer.prompt(questionPushChanges);
79
+ const answers = await inquirer.prompt(questionPushChanges);
80
80
 
81
81
  if (answers.changes !== "YES" && answers.changes !== "NO") {
82
82
  answers.changes = await fixConfirmation();
@@ -148,9 +148,9 @@ export class Attributes {
148
148
  const keyName = `${chalk.yellow(local.key)} in ${collection.name} (${collection["$id"]})`;
149
149
  const action = chalk.cyan(recreating ? "recreating" : "changing");
150
150
  let reason = "";
151
- let attribute = recreating ? remote : local;
151
+ const attribute = recreating ? remote : local;
152
152
 
153
- for (let key of Object.keys(remote)) {
153
+ for (const key of Object.keys(remote)) {
154
154
  if (!KeysAttributes.has(key)) {
155
155
  continue;
156
156
  }
@@ -737,7 +737,7 @@ export class Attributes {
737
737
  log(`Creating indexes ...`);
738
738
 
739
739
  const databasesService = await getDatabasesService(this.client);
740
- for (let index of indexes) {
740
+ for (const index of indexes) {
741
741
  await databasesService.createIndex({
742
742
  databaseId: collection["databaseId"],
743
743
  collectionId: collection["$id"],
@@ -769,7 +769,7 @@ export class Attributes {
769
769
  ): Promise<void> => {
770
770
  log(`Creating attributes ...`);
771
771
 
772
- for (let attribute of attributes) {
772
+ for (const attribute of attributes) {
773
773
  if (attribute.side !== "child") {
774
774
  await this.createAttribute(
775
775
  collection["databaseId"],
@@ -803,7 +803,7 @@ export class Attributes {
803
803
  ): Promise<void> => {
804
804
  log(`Creating columns ...`);
805
805
 
806
- for (let column of columns) {
806
+ for (const column of columns) {
807
807
  if (column.side !== "child") {
808
808
  await this.createAttribute(table["databaseId"], table["$id"], column);
809
809
  }
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import inquirer from "inquirer";
3
+ import { AppwriteException } from "@appwrite.io/console";
3
4
  import { cliConfig, success, warn, log, drawTable } from "../../parser.js";
4
5
  import { whitelistKeys } from "../../config.js";
5
6
  import {
@@ -10,7 +11,7 @@ import {
10
11
  /**
11
12
  * Check if a value is considered empty
12
13
  */
13
- export const isEmpty = (value: any): boolean =>
14
+ export const isEmpty = (value: unknown): boolean =>
14
15
  value === null ||
15
16
  value === undefined ||
16
17
  (typeof value === "string" && value.trim().length === 0) ||
@@ -33,7 +34,7 @@ export const getConfirmation = async (): Promise<boolean> => {
33
34
  return answers.changes;
34
35
  }
35
36
 
36
- let answers = await inquirer.prompt(questionPushChanges);
37
+ const answers = await inquirer.prompt(questionPushChanges);
37
38
 
38
39
  if (answers.changes !== "YES" && answers.changes !== "NO") {
39
40
  answers.changes = await fixConfirmation();
@@ -57,9 +58,9 @@ interface ObjectChange {
57
58
  local: string;
58
59
  }
59
60
 
60
- type ComparableValue = boolean | number | string | any[] | undefined;
61
+ type ComparableValue = boolean | number | string | unknown[] | undefined;
61
62
 
62
- export const getObjectChanges = <T extends Record<string, any>>(
63
+ export const getObjectChanges = <T extends Record<string, unknown>>(
63
64
  remote: T,
64
65
  local: T,
65
66
  index: keyof T,
@@ -110,8 +111,10 @@ export const getObjectChanges = <T extends Record<string, any>>(
110
111
  * Compares local resources with remote resources and prompts user for confirmation
111
112
  */
112
113
  export const approveChanges = async (
113
- resource: any[],
114
- resourceGetFunction: Function,
114
+ resource: Array<Record<string, unknown>>,
115
+ resourceGetFunction: (
116
+ options: Record<string, unknown>,
117
+ ) => Promise<Record<string, unknown>>,
115
118
  keys: Set<string>,
116
119
  resourceName: string,
117
120
  resourcePlural: string,
@@ -120,12 +123,12 @@ export const approveChanges = async (
120
123
  secondResourceName: string = "",
121
124
  ): Promise<boolean> => {
122
125
  log("Checking for changes ...");
123
- const changes: any[] = [];
126
+ const changes: Array<Record<string, unknown>> = [];
124
127
 
125
128
  await Promise.all(
126
129
  resource.map(async (localResource) => {
127
130
  try {
128
- const options: Record<string, any> = {
131
+ const options: Record<string, unknown> = {
129
132
  [resourceName]: localResource["$id"],
130
133
  };
131
134
 
@@ -135,7 +138,7 @@ export const approveChanges = async (
135
138
 
136
139
  const remoteResource = await resourceGetFunction(options);
137
140
 
138
- for (let [key, value] of Object.entries(
141
+ for (const [key, value] of Object.entries(
139
142
  whitelistKeys(remoteResource, keys),
140
143
  )) {
141
144
  if (skipKeys.includes(key)) {
@@ -146,28 +149,32 @@ export const approveChanges = async (
146
149
  continue;
147
150
  }
148
151
 
149
- if (Array.isArray(value) && Array.isArray(localResource[key])) {
150
- if (JSON.stringify(value) !== JSON.stringify(localResource[key])) {
152
+ const localValue = localResource[key];
153
+
154
+ if (Array.isArray(value) && Array.isArray(localValue)) {
155
+ if (JSON.stringify(value) !== JSON.stringify(localValue)) {
151
156
  changes.push({
152
157
  id: localResource["$id"],
153
158
  key,
154
159
  remote: chalk.red((value as string[]).join("\n")),
155
- local: chalk.green(localResource[key].join("\n")),
160
+ local: chalk.green(
161
+ localValue.map((entry) => String(entry)).join("\n"),
162
+ ),
156
163
  });
157
164
  }
158
- } else if (value !== localResource[key]) {
165
+ } else if (value !== localValue) {
159
166
  changes.push({
160
167
  id: localResource["$id"],
161
168
  key,
162
- remote: chalk.red(value),
163
- local: chalk.green(localResource[key]),
169
+ remote: chalk.red(String(value ?? "")),
170
+ local: chalk.green(String(localValue ?? "")),
164
171
  });
165
172
  }
166
173
  }
167
- } catch (e: any) {
168
- if (Number(e.code) !== 404) {
169
- throw e;
170
- }
174
+ } catch (e: unknown) {
175
+ const isNotFound =
176
+ e instanceof AppwriteException && Number(e.code) === 404;
177
+ if (!isNotFound) throw e;
171
178
  }
172
179
  }),
173
180
  );