appwrite-utils-cli 0.9.90 → 0.9.92

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.
package/README.md CHANGED
@@ -125,6 +125,8 @@ This updated CLI ensures that developers have robust tools at their fingertips t
125
125
 
126
126
  ## Changelog
127
127
 
128
+ - 0.9.92: Fixed `createOrUpdateAttributes` so it deletes attributes that don't exist in local config when you are running `syncDb`. Also updated the database and collection selection, so it won't try and fetch the collections and databases that don't exist (ones you picked from local config) and error
129
+ - 0.9.91: Fixed another webpack error, screw you react (but you're supported now so I guess not-screw-you)
128
130
  - 0.9.90: Fixed Webpack errors (why tf does webpack add an extra `default`...???)
129
131
  - 0.9.80: Fixed collections not being unique between local and remote
130
132
  - 0.9.79: Fixed local collections not being considered for the synchronization unless all de-selected
@@ -232,10 +232,28 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
232
232
  };
233
233
  export const createUpdateCollectionAttributes = async (db, dbId, collection, attributes) => {
234
234
  console.log(chalk.green(`Creating/Updating attributes for collection: ${collection.name}`));
235
+ // @ts-expect-error
236
+ const existingAttributes = collection.attributes.map((attr) => parseAttribute(attr)) || [];
237
+ const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
238
+ const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
239
+ if (attributesToRemove.length > 0) {
240
+ if (indexesToRemove.length > 0) {
241
+ console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove.map((index) => index.key).join(", ")}`));
242
+ for (const index of indexesToRemove) {
243
+ await tryAwaitWithRetry(async () => await db.deleteIndex(dbId, collection.$id, index.key));
244
+ await delay(100);
245
+ }
246
+ }
247
+ for (const attr of attributesToRemove) {
248
+ console.log(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
249
+ await tryAwaitWithRetry(async () => await db.deleteAttribute(dbId, collection.$id, attr.key));
250
+ await delay(50);
251
+ }
252
+ }
235
253
  const batchSize = 3;
236
254
  for (let i = 0; i < attributes.length; i += batchSize) {
237
255
  const batch = attributes.slice(i, i + batchSize);
238
- const attributePromises = batch.map((attribute) => createOrUpdateAttribute(db, dbId, collection, attribute));
256
+ const attributePromises = batch.map((attribute) => tryAwaitWithRetry(async () => await createOrUpdateAttribute(db, dbId, collection, attribute)));
239
257
  const results = await Promise.allSettled(attributePromises);
240
258
  results.forEach((result) => {
241
259
  if (result.status === "rejected") {
@@ -243,7 +261,7 @@ export const createUpdateCollectionAttributes = async (db, dbId, collection, att
243
261
  }
244
262
  });
245
263
  // Add delay after each batch
246
- await delay(500);
264
+ await delay(200);
247
265
  }
248
266
  console.log(`Finished creating/updating attributes for collection: ${collection.name}`);
249
267
  };
@@ -4,7 +4,7 @@ import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
4
4
  import { fetchAllDatabases } from "./databases/methods.js";
5
5
  import { fetchAllCollections } from "./collections/methods.js";
6
6
  import { listBuckets, createBucket } from "./storage/methods.js";
7
- import { Databases, Storage, Client, Compression, } from "node-appwrite";
7
+ import { Databases, Storage, Client, Compression, Query, } from "node-appwrite";
8
8
  import { getClient } from "./utils/getClientFromConfig.js";
9
9
  import { parseAttribute, PermissionToAppwritePermission } from "appwrite-utils";
10
10
  import { ulid } from "ulidx";
@@ -130,7 +130,14 @@ export class InteractiveCLI {
130
130
  }
131
131
  async selectCollections(database, databasesClient, message, multiSelect = true) {
132
132
  await this.initControllerIfNeeded();
133
- const collections = await fetchAllCollections(database.$id, databasesClient);
133
+ const dbExists = await databasesClient.list([Query.equal("name", database.name)]);
134
+ let collections = [];
135
+ if (dbExists.total === 0) {
136
+ console.log(chalk.red(`Database "${database.name}" does not exist, using only local collection options`));
137
+ }
138
+ else {
139
+ collections = await fetchAllCollections(database.$id, databasesClient);
140
+ }
134
141
  const configCollections = this.getLocalCollections();
135
142
  const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
136
143
  const allCollectionNamesUnique = Array.from(new Set(collectionNames));
@@ -36,7 +36,11 @@ export const loadConfig = async (configDir) => {
36
36
  const configPath = path.join(configDir, "appwriteConfig.ts");
37
37
  console.log(`Loading config from: ${configPath}`);
38
38
  const configUrl = pathToFileURL(configPath).href;
39
- const config = (await import(configUrl)).default;
39
+ const configModule = (await import(configUrl));
40
+ const config = configModule.default?.default || configModule.default || configModule;
41
+ if (!config) {
42
+ throw new Error("Failed to load config");
43
+ }
40
44
  const collectionsDir = path.join(configDir, "collections");
41
45
  const collectionFiles = fs.readdirSync(collectionsDir);
42
46
  config.collections = [];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "0.9.90",
4
+ "version": "0.9.92",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -471,11 +471,32 @@ export const createUpdateCollectionAttributes = async (
471
471
  chalk.green(`Creating/Updating attributes for collection: ${collection.name}`)
472
472
  );
473
473
 
474
+ // @ts-expect-error
475
+ const existingAttributes: Attribute[] = collection.attributes.map((attr) => parseAttribute(attr)) || [];
476
+
477
+ const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
478
+ const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
479
+
480
+ if (attributesToRemove.length > 0) {
481
+ if (indexesToRemove.length > 0) {
482
+ console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove.map((index) => index.key).join(", ")}`));
483
+ for (const index of indexesToRemove) {
484
+ await tryAwaitWithRetry(async () => await db.deleteIndex(dbId, collection.$id, index.key));
485
+ await delay(100);
486
+ }
487
+ }
488
+ for (const attr of attributesToRemove) {
489
+ console.log(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
490
+ await tryAwaitWithRetry(async () => await db.deleteAttribute(dbId, collection.$id, attr.key));
491
+ await delay(50);
492
+ }
493
+ }
494
+
474
495
  const batchSize = 3;
475
496
  for (let i = 0; i < attributes.length; i += batchSize) {
476
497
  const batch = attributes.slice(i, i + batchSize);
477
498
  const attributePromises = batch.map((attribute) =>
478
- createOrUpdateAttribute(db, dbId, collection, attribute)
499
+ tryAwaitWithRetry(async () => await createOrUpdateAttribute(db, dbId, collection, attribute))
479
500
  );
480
501
 
481
502
  const results = await Promise.allSettled(attributePromises);
@@ -486,7 +507,7 @@ export const createUpdateCollectionAttributes = async (
486
507
  });
487
508
 
488
509
  // Add delay after each batch
489
- await delay(500);
510
+ await delay(200);
490
511
  }
491
512
  console.log(
492
513
  `Finished creating/updating attributes for collection: ${collection.name}`
@@ -10,6 +10,7 @@ import {
10
10
  Client,
11
11
  type Models,
12
12
  Compression,
13
+ Query,
13
14
  } from "node-appwrite";
14
15
  import { getClient } from "./utils/getClientFromConfig.js";
15
16
  import type { TransferOptions } from "./migrations/transfer.js";
@@ -37,7 +38,7 @@ enum CHOICES {
37
38
  export class InteractiveCLI {
38
39
  private controller: UtilsController | undefined;
39
40
 
40
- constructor(private currentDir: string) {}
41
+ constructor(private currentDir: string) { }
41
42
 
42
43
  async run(): Promise<void> {
43
44
  console.log(chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley"));
@@ -129,12 +130,12 @@ export class InteractiveCLI {
129
130
  }
130
131
  return acc;
131
132
  }, [] as Models.Database[]);
132
-
133
+
133
134
  const choices = allDatabases
134
135
  .sort((a, b) => a.name.localeCompare(b.name))
135
136
  .map((db) => ({ name: db.name, value: db }))
136
137
  .filter((db) => db.name.toLowerCase() !== "migrations");
137
-
138
+
138
139
  const { selectedDatabases } = await inquirer.prompt([
139
140
  {
140
141
  type: multiSelect ? "checkbox" : "list",
@@ -145,7 +146,7 @@ export class InteractiveCLI {
145
146
  pageSize: 10,
146
147
  },
147
148
  ]);
148
-
149
+
149
150
  return selectedDatabases;
150
151
  }
151
152
 
@@ -153,13 +154,19 @@ export class InteractiveCLI {
153
154
  database: Models.Database,
154
155
  databasesClient: Databases,
155
156
  message: string,
156
- multiSelect = true
157
+ multiSelect = true
157
158
  ): Promise<Models.Collection[]> {
158
159
  await this.initControllerIfNeeded();
159
- const collections = await fetchAllCollections(
160
- database.$id,
161
- databasesClient
162
- );
160
+ const dbExists = await databasesClient.list([Query.equal("name", database.name)]);
161
+ let collections: Models.Collection[] = [];
162
+ if (dbExists.total === 0) {
163
+ console.log(chalk.red(`Database "${database.name}" does not exist, using only local collection options`));
164
+ } else {
165
+ collections = await fetchAllCollections(
166
+ database.$id,
167
+ databasesClient
168
+ );
169
+ }
163
170
  const configCollections = this.getLocalCollections();
164
171
  const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
165
172
  const allCollectionNamesUnique = Array.from(new Set(collectionNames));
@@ -690,10 +697,10 @@ export class InteractiveCLI {
690
697
  let targetDatabases: Models.Database[];
691
698
  let remoteOptions:
692
699
  | {
693
- transferEndpoint: string;
694
- transferProject: string;
695
- transferKey: string;
696
- }
700
+ transferEndpoint: string;
701
+ transferProject: string;
702
+ transferKey: string;
703
+ }
697
704
  | undefined;
698
705
 
699
706
  if (isRemote) {
@@ -771,12 +778,12 @@ export class InteractiveCLI {
771
778
  const sourceStorage = new Storage(this.controller!.appwriteServer!);
772
779
  const targetStorage = isRemote
773
780
  ? new Storage(
774
- getClient(
775
- remoteOptions!.transferEndpoint,
776
- remoteOptions!.transferProject,
777
- remoteOptions!.transferKey
778
- )
781
+ getClient(
782
+ remoteOptions!.transferEndpoint,
783
+ remoteOptions!.transferProject,
784
+ remoteOptions!.transferKey
779
785
  )
786
+ )
780
787
  : sourceStorage;
781
788
 
782
789
  const sourceBuckets = await listBuckets(sourceStorage);
@@ -41,8 +41,11 @@ export const loadConfig = async (
41
41
  const configPath = path.join(configDir, "appwriteConfig.ts");
42
42
  console.log(`Loading config from: ${configPath}`);
43
43
  const configUrl = pathToFileURL(configPath).href;
44
- const config = (await import(configUrl)).default as AppwriteConfig;
45
-
44
+ const configModule = (await import(configUrl));
45
+ const config: AppwriteConfig | undefined = configModule.default?.default || configModule.default || configModule;
46
+ if (!config) {
47
+ throw new Error("Failed to load config");
48
+ }
46
49
  const collectionsDir = path.join(configDir, "collections");
47
50
  const collectionFiles = fs.readdirSync(collectionsDir);
48
51