appwrite-utils-cli 0.9.77 → 0.9.79

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
@@ -61,6 +61,7 @@ Available options:
61
61
  - `--collectionIds`: Comma-separated list of collection IDs to operate on
62
62
  - `--bucketIds`: Comma-separated list of bucket IDs to operate on
63
63
  - `--wipe`: Wipe data (all: everything, docs: only documents, users: only user data)
64
+ - `--wipeCollections`: Wipe collections (wipes specified collections from collectionIds -- does this non-destructively, deletes all documents)
64
65
  - `--generate`: Generate TypeScript schemas from database schemas
65
66
  - `--import`: Import data into your databases
66
67
  - `--backup`: Perform a backup of your databases
@@ -124,6 +125,8 @@ This updated CLI ensures that developers have robust tools at their fingertips t
124
125
 
125
126
  ## Changelog
126
127
 
128
+ - 0.9.79: Fixed local collections not being considered for the synchronization unless all de-selected
129
+ - 0.9.78: Added colored text! And also added a lot more customization options as to what to wipe, update, etc.
127
130
  - 0.9.75: Fixed attribute bug
128
131
  - 0.9.72: Fixed my own null bug
129
132
  - 0.9.71: Reverted `node-appwrite` to 14, this seems to fix the xdefault error
@@ -3,6 +3,7 @@ import { attributeSchema, parseAttribute, } from "appwrite-utils";
3
3
  import { nameToIdMapping, enqueueOperation } from "../migrations/queue.js";
4
4
  import _ from "lodash";
5
5
  import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
6
+ import chalk from "chalk";
6
7
  const attributesSame = (databaseAttribute, configAttribute) => {
7
8
  const attributesToCheck = [
8
9
  'key',
@@ -62,6 +63,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
62
63
  // @ts-expect-error
63
64
  (attr) => attr.key === attribute.key);
64
65
  foundAttribute = parseAttribute(collectionAttr);
66
+ // console.log(`Found attribute: ${JSON.stringify(foundAttribute)}`);
65
67
  }
66
68
  catch (error) {
67
69
  foundAttribute = undefined;
@@ -74,7 +76,10 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
74
76
  // console.log(
75
77
  // `Updating attribute with same key ${attribute.key} but different values`
76
78
  // );
77
- finalAttribute = attribute;
79
+ finalAttribute = {
80
+ ...foundAttribute,
81
+ ...attribute,
82
+ };
78
83
  action = "update";
79
84
  }
80
85
  else if (!updateEnabled && foundAttribute && !attributesSame(foundAttribute, attribute)) {
@@ -93,7 +98,9 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
93
98
  collectionFoundViaRelatedCollection = await db.getCollection(dbId, relatedCollectionId);
94
99
  }
95
100
  catch (e) {
96
- console.log(`Collection not found: ${finalAttribute.relatedCollection} when nameToIdMapping was set`);
101
+ // console.log(
102
+ // `Collection not found: ${finalAttribute.relatedCollection} when nameToIdMapping was set`
103
+ // );
97
104
  collectionFoundViaRelatedCollection = undefined;
98
105
  }
99
106
  }
@@ -108,7 +115,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
108
115
  }
109
116
  }
110
117
  if (!(relatedCollectionId && collectionFoundViaRelatedCollection)) {
111
- console.log(`Enqueueing operation for attribute: ${finalAttribute.key}`);
118
+ // console.log(`Enqueueing operation for attribute: ${finalAttribute.key}`);
112
119
  enqueueOperation({
113
120
  type: "attribute",
114
121
  collectionId: collection.$id,
@@ -119,7 +126,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
119
126
  return;
120
127
  }
121
128
  }
122
- finalAttribute = attributeSchema.parse(finalAttribute);
129
+ finalAttribute = parseAttribute(finalAttribute);
123
130
  // console.log(`Final Attribute: ${JSON.stringify(finalAttribute)}`);
124
131
  switch (finalAttribute.type) {
125
132
  case "string":
@@ -224,7 +231,7 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
224
231
  }
225
232
  };
226
233
  export const createUpdateCollectionAttributes = async (db, dbId, collection, attributes) => {
227
- console.log(`Creating/Updating attributes for collection: ${collection.name}`);
234
+ console.log(chalk.green(`Creating/Updating attributes for collection: ${collection.name}`));
228
235
  const batchSize = 3;
229
236
  for (let i = 0; i < attributes.length; i += batchSize) {
230
237
  const batch = attributes.slice(i, i + batchSize);
@@ -7,10 +7,11 @@ export declare const wipeDatabase: (database: Databases, databaseId: string) =>
7
7
  collectionId: string;
8
8
  collectionName: string;
9
9
  }[]>;
10
+ export declare const wipeCollection: (database: Databases, databaseId: string, collectionId: string) => Promise<void>;
10
11
  export declare const generateSchemas: (config: AppwriteConfig, appwriteFolderPath: string) => Promise<void>;
11
12
  export declare const createOrUpdateCollections: (database: Databases, databaseId: string, config: AppwriteConfig, deletedCollections?: {
12
13
  collectionId: string;
13
14
  collectionName: string;
14
- }[]) => Promise<void>;
15
+ }[], selectedCollections?: Models.Collection[]) => Promise<void>;
15
16
  export declare const generateMockData: (database: Databases, databaseId: string, configCollections: any[]) => Promise<void>;
16
17
  export declare const fetchAllCollections: (dbId: string, database: Databases) => Promise<Models.Collection[]>;
@@ -86,6 +86,21 @@ export const fetchAndCacheCollectionByName = async (db, dbId, collectionName) =>
86
86
  }
87
87
  }
88
88
  };
89
+ async function wipeDocumentsFromCollection(database, databaseId, collectionId) {
90
+ const initialDocuments = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
91
+ let documents = initialDocuments.documents;
92
+ while (documents.length === 1000) {
93
+ const docsResponse = await database.listDocuments(databaseId, collectionId, [Query.limit(1000)]);
94
+ documents = documents.concat(docsResponse.documents);
95
+ }
96
+ const batchDeletePromises = documents.map(doc => database.deleteDocument(databaseId, collectionId, doc.$id));
97
+ const maxStackSize = 100;
98
+ for (let i = 0; i < batchDeletePromises.length; i += maxStackSize) {
99
+ await Promise.all(batchDeletePromises.slice(i, i + maxStackSize));
100
+ await delay(100);
101
+ }
102
+ console.log(`Deleted ${documents.length} documents from collection ${collectionId}`);
103
+ }
89
104
  export const wipeDatabase = async (database, databaseId) => {
90
105
  console.log(`Wiping database: ${databaseId}`);
91
106
  const existingCollections = await fetchAllCollections(databaseId, database);
@@ -101,56 +116,71 @@ export const wipeDatabase = async (database, databaseId) => {
101
116
  }
102
117
  return collectionsDeleted;
103
118
  };
119
+ export const wipeCollection = async (database, databaseId, collectionId) => {
120
+ const collections = await database.listCollections(databaseId, [Query.equal("$id", collectionId)]);
121
+ if (collections.total === 0) {
122
+ console.log(`Collection ${collectionId} not found`);
123
+ return;
124
+ }
125
+ const collection = collections.collections[0];
126
+ await wipeDocumentsFromCollection(database, databaseId, collection.$id);
127
+ };
104
128
  export const generateSchemas = async (config, appwriteFolderPath) => {
105
129
  const schemaGenerator = new SchemaGenerator(config, appwriteFolderPath);
106
130
  schemaGenerator.generateSchemas();
107
131
  };
108
- export const createOrUpdateCollections = async (database, databaseId, config, deletedCollections) => {
109
- const configCollections = config.collections;
110
- if (!configCollections) {
132
+ export const createOrUpdateCollections = async (database, databaseId, config, deletedCollections, selectedCollections = []) => {
133
+ const collectionsToProcess = selectedCollections.length > 0 ? selectedCollections : config.collections;
134
+ if (!collectionsToProcess) {
111
135
  return;
112
136
  }
113
137
  const usedIds = new Set();
114
- for (const { attributes, indexes, ...collection } of configCollections) {
138
+ for (const collection of collectionsToProcess) {
139
+ const { attributes, indexes, ...collectionData } = collection;
115
140
  // Prepare permissions for the collection
116
141
  const permissions = [];
117
142
  if (collection.$permissions && collection.$permissions.length > 0) {
118
143
  for (const permission of collection.$permissions) {
119
- switch (permission.permission) {
120
- case "read":
121
- permissions.push(Permission.read(permission.target));
122
- break;
123
- case "create":
124
- permissions.push(Permission.create(permission.target));
125
- break;
126
- case "update":
127
- permissions.push(Permission.update(permission.target));
128
- break;
129
- case "delete":
130
- permissions.push(Permission.delete(permission.target));
131
- break;
132
- case "write":
133
- permissions.push(Permission.write(permission.target));
134
- break;
135
- default:
136
- console.log(`Unknown permission: ${permission.permission}`);
137
- break;
144
+ if (typeof permission === 'string') {
145
+ permissions.push(permission);
146
+ }
147
+ else {
148
+ switch (permission.permission) {
149
+ case "read":
150
+ permissions.push(Permission.read(permission.target));
151
+ break;
152
+ case "create":
153
+ permissions.push(Permission.create(permission.target));
154
+ break;
155
+ case "update":
156
+ permissions.push(Permission.update(permission.target));
157
+ break;
158
+ case "delete":
159
+ permissions.push(Permission.delete(permission.target));
160
+ break;
161
+ case "write":
162
+ permissions.push(Permission.write(permission.target));
163
+ break;
164
+ default:
165
+ console.log(`Unknown permission: ${permission.permission}`);
166
+ break;
167
+ }
138
168
  }
139
169
  }
140
170
  }
141
171
  // Check if the collection already exists by name
142
172
  let collectionsFound = await tryAwaitWithRetry(async () => await database.listCollections(databaseId, [
143
- Query.equal("name", collection.name),
173
+ Query.equal("name", collectionData.name),
144
174
  ]));
145
175
  let collectionToUse = collectionsFound.total > 0 ? collectionsFound.collections[0] : null;
146
176
  // Determine the correct ID for the collection
147
177
  let collectionId;
148
178
  if (!collectionToUse) {
149
- console.log(`Creating collection: ${collection.name}`);
179
+ console.log(`Creating collection: ${collectionData.name}`);
150
180
  let foundColl = deletedCollections?.find((coll) => coll.collectionName.toLowerCase().trim().replace(" ", "") ===
151
- collection.name.toLowerCase().trim().replace(" ", ""));
152
- if (collection.$id) {
153
- collectionId = collection.$id;
181
+ collectionData.name.toLowerCase().trim().replace(" ", ""));
182
+ if (collectionData.$id) {
183
+ collectionId = collectionData.$id;
154
184
  }
155
185
  else if (foundColl && !usedIds.has(foundColl.collectionId)) {
156
186
  collectionId = foundColl.collectionId;
@@ -161,28 +191,30 @@ export const createOrUpdateCollections = async (database, databaseId, config, de
161
191
  usedIds.add(collectionId);
162
192
  // Create the collection with the determined ID
163
193
  try {
164
- collectionToUse = await tryAwaitWithRetry(async () => await database.createCollection(databaseId, collectionId, collection.name, permissions, collection.documentSecurity ?? false, collection.enabled ?? true));
165
- collection.$id = collectionToUse.$id;
166
- nameToIdMapping.set(collection.name, collectionToUse.$id);
194
+ collectionToUse = await tryAwaitWithRetry(async () => await database.createCollection(databaseId, collectionId, collectionData.name, permissions, collectionData.documentSecurity ?? false, collectionData.enabled ?? true));
195
+ collectionData.$id = collectionToUse.$id;
196
+ nameToIdMapping.set(collectionData.name, collectionToUse.$id);
167
197
  }
168
198
  catch (error) {
169
- console.error(`Failed to create collection ${collection.name} with ID ${collectionId}: ${error}`);
199
+ console.error(`Failed to create collection ${collectionData.name} with ID ${collectionId}: ${error}`);
170
200
  continue;
171
201
  }
172
202
  }
173
203
  else {
174
- console.log(`Collection ${collection.name} exists, updating it`);
175
- await tryAwaitWithRetry(async () => await database.updateCollection(databaseId, collectionToUse.$id, collection.name, permissions, collection.documentSecurity ?? false, collection.enabled ?? true));
204
+ console.log(`Collection ${collectionData.name} exists, updating it`);
205
+ await tryAwaitWithRetry(async () => await database.updateCollection(databaseId, collectionToUse.$id, collectionData.name, permissions, collectionData.documentSecurity ?? false, collectionData.enabled ?? true));
176
206
  }
177
207
  // Add delay after creating/updating collection
178
208
  await delay(250);
179
209
  // Update attributes and indexes for the collection
180
210
  console.log("Creating Attributes");
181
- await createUpdateCollectionAttributes(database, databaseId, collectionToUse, attributes);
211
+ await createUpdateCollectionAttributes(database, databaseId, collectionToUse,
212
+ // @ts-expect-error
213
+ attributes);
182
214
  // Add delay after creating attributes
183
215
  await delay(250);
184
216
  console.log("Creating Indexes");
185
- await createOrUpdateIndexes(databaseId, database, collectionToUse.$id, indexes ?? []);
217
+ await createOrUpdateIndexes(databaseId, database, collectionToUse.$id, (indexes ?? []));
186
218
  // Add delay after creating indexes
187
219
  await delay(250);
188
220
  }
@@ -14,7 +14,11 @@ export declare class InteractiveCLI {
14
14
  private synchronizeConfigurations;
15
15
  private backupDatabase;
16
16
  private wipeDatabase;
17
+ private wipeCollections;
17
18
  private generateSchemas;
18
19
  private importData;
19
20
  private transferData;
21
+ private getLocalCollections;
22
+ private getLocalDatabases;
23
+ private reloadConfig;
20
24
  }
@@ -6,7 +6,10 @@ import { fetchAllCollections } from "./collections/methods.js";
6
6
  import { listBuckets, createBucket } from "./storage/methods.js";
7
7
  import { Databases, Storage, Client, Compression, } from "node-appwrite";
8
8
  import { getClient } from "./utils/getClientFromConfig.js";
9
+ import { parseAttribute, PermissionToAppwritePermission } from "appwrite-utils";
9
10
  import { ulid } from "ulidx";
11
+ import chalk from "chalk";
12
+ import { DateTime } from "luxon";
10
13
  var CHOICES;
11
14
  (function (CHOICES) {
12
15
  CHOICES["CREATE_COLLECTION_CONFIG"] = "Create collection config file";
@@ -17,8 +20,10 @@ var CHOICES;
17
20
  CHOICES["TRANSFER_DATA"] = "Transfer data";
18
21
  CHOICES["BACKUP_DATABASE"] = "Backup database";
19
22
  CHOICES["WIPE_DATABASE"] = "Wipe database";
23
+ CHOICES["WIPE_COLLECTIONS"] = "Wipe collections";
20
24
  CHOICES["GENERATE_SCHEMAS"] = "Generate schemas";
21
25
  CHOICES["IMPORT_DATA"] = "Import data";
26
+ CHOICES["RELOAD_CONFIG"] = "Reload configuration files";
22
27
  CHOICES["EXIT"] = "Exit";
23
28
  })(CHOICES || (CHOICES = {}));
24
29
  export class InteractiveCLI {
@@ -28,17 +33,18 @@ export class InteractiveCLI {
28
33
  this.currentDir = currentDir;
29
34
  }
30
35
  async run() {
31
- console.log("Welcome to Appwrite Utils CLI Tool by Zach Handley");
32
- console.log("For more information, visit https://github.com/zachhandley/AppwriteUtils");
36
+ console.log(chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley"));
37
+ console.log(chalk.blue("For more information, visit https://github.com/zachhandley/AppwriteUtils"));
33
38
  while (true) {
34
39
  const { action } = await inquirer.prompt([
35
40
  {
36
41
  type: "list",
37
42
  name: "action",
38
- message: "What would you like to do?",
43
+ message: chalk.yellow("What would you like to do?"),
39
44
  choices: Object.values(CHOICES),
40
45
  },
41
46
  ]);
47
+ await this.initControllerIfNeeded();
42
48
  switch (action) {
43
49
  case CHOICES.CREATE_COLLECTION_CONFIG:
44
50
  await this.createCollectionConfig();
@@ -69,6 +75,10 @@ export class InteractiveCLI {
69
75
  await this.initControllerIfNeeded();
70
76
  await this.wipeDatabase();
71
77
  break;
78
+ case CHOICES.WIPE_COLLECTIONS:
79
+ await this.initControllerIfNeeded();
80
+ await this.wipeCollections();
81
+ break;
72
82
  case CHOICES.GENERATE_SCHEMAS:
73
83
  await this.initControllerIfNeeded();
74
84
  await this.generateSchemas();
@@ -77,8 +87,12 @@ export class InteractiveCLI {
77
87
  await this.initControllerIfNeeded();
78
88
  await this.importData();
79
89
  break;
90
+ case CHOICES.RELOAD_CONFIG:
91
+ await this.initControllerIfNeeded();
92
+ await this.reloadConfig();
93
+ break;
80
94
  case CHOICES.EXIT:
81
- console.log("Exiting...");
95
+ console.log(chalk.green("Goodbye!"));
82
96
  return;
83
97
  }
84
98
  }
@@ -90,12 +104,14 @@ export class InteractiveCLI {
90
104
  }
91
105
  }
92
106
  async selectDatabases(databases, message, multiSelect = true) {
93
- const choices = databases.map((db) => ({ name: db.name, value: db }));
107
+ const choices = databases.map((db) => ({ name: db.name, value: db })).filter((db) => db.name.toLowerCase() !== "migrations");
108
+ const configDatabases = this.getLocalDatabases();
109
+ const allDatabases = Array.from(new Set([...databases, ...configDatabases]));
94
110
  const { selectedDatabases } = await inquirer.prompt([
95
111
  {
96
112
  type: multiSelect ? "checkbox" : "list",
97
113
  name: "selectedDatabases",
98
- message,
114
+ message: chalk.blue(message),
99
115
  choices,
100
116
  loop: false,
101
117
  pageSize: 10,
@@ -105,7 +121,9 @@ export class InteractiveCLI {
105
121
  }
106
122
  async selectCollections(database, databasesClient, message, multiSelect = true) {
107
123
  const collections = await fetchAllCollections(database.$id, databasesClient);
108
- const choices = collections.map((collection) => ({
124
+ const configCollections = this.getLocalCollections();
125
+ const allCollections = Array.from(new Set([...collections, ...configCollections]));
126
+ const choices = allCollections.map((collection) => ({
109
127
  name: collection.name,
110
128
  value: collection,
111
129
  }));
@@ -113,7 +131,7 @@ export class InteractiveCLI {
113
131
  {
114
132
  type: multiSelect ? "checkbox" : "list",
115
133
  name: "selectedCollections",
116
- message,
134
+ message: chalk.blue(message),
117
135
  choices,
118
136
  loop: false,
119
137
  pageSize: 10,
@@ -130,7 +148,7 @@ export class InteractiveCLI {
130
148
  {
131
149
  type: multiSelect ? "checkbox" : "list",
132
150
  name: "selectedBuckets",
133
- message,
151
+ message: chalk.blue(message),
134
152
  choices,
135
153
  loop: false,
136
154
  pageSize: 10,
@@ -143,11 +161,11 @@ export class InteractiveCLI {
143
161
  {
144
162
  type: "input",
145
163
  name: "collectionName",
146
- message: "Enter the name of the collection:",
164
+ message: chalk.blue("Enter the name of the collection:"),
147
165
  validate: (input) => input.trim() !== "" || "Collection name cannot be empty.",
148
166
  },
149
167
  ]);
150
- console.log(`Creating collection config file for '${collectionName}'...`);
168
+ console.log(chalk.green(`Creating collection config file for '${collectionName}'...`));
151
169
  createEmptyCollection(collectionName);
152
170
  }
153
171
  async configureBuckets(config, databases) {
@@ -163,7 +181,7 @@ export class InteractiveCLI {
163
181
  {
164
182
  type: "confirm",
165
183
  name: "wantCreateBucket",
166
- message: `There are no buckets. Do you want to create a bucket for the database "${database.name}"?`,
184
+ message: chalk.blue(`There are no buckets. Do you want to create a bucket for the database "${database.name}"?`),
167
185
  default: true,
168
186
  },
169
187
  ]);
@@ -322,7 +340,11 @@ export class InteractiveCLI {
322
340
  }, bucketId.length > 0 ? bucketId : ulid());
323
341
  }
324
342
  async syncDb() {
325
- await this.controller.syncDb();
343
+ console.log(chalk.yellow("Syncing database..."));
344
+ const databases = await this.selectDatabases(await fetchAllDatabases(this.controller.database), chalk.blue("Select databases to synchronize:"), true);
345
+ const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select collections to synchronize:"), true);
346
+ await this.controller.syncDb(databases, collections);
347
+ console.log(chalk.green("Database sync completed."));
326
348
  }
327
349
  async synchronizeConfigurations() {
328
350
  if (!this.controller.database) {
@@ -330,10 +352,11 @@ export class InteractiveCLI {
330
352
  }
331
353
  const databases = await fetchAllDatabases(this.controller.database);
332
354
  const selectedDatabases = await this.selectDatabases(databases, "Select databases to synchronize:");
333
- console.log("Configuring storage buckets...");
355
+ console.log(chalk.yellow("Configuring storage buckets..."));
334
356
  const updatedConfig = await this.configureBuckets(this.controller.config, selectedDatabases);
335
- console.log("Synchronizing configurations...");
357
+ console.log(chalk.yellow("Synchronizing configurations..."));
336
358
  await this.controller.synchronizeConfigurations(selectedDatabases, updatedConfig);
359
+ console.log(chalk.green("Configuration synchronization completed."));
337
360
  }
338
361
  async backupDatabase() {
339
362
  if (!this.controller.database) {
@@ -342,9 +365,10 @@ export class InteractiveCLI {
342
365
  const databases = await fetchAllDatabases(this.controller.database);
343
366
  const selectedDatabases = await this.selectDatabases(databases, "Select databases to backup:");
344
367
  for (const db of selectedDatabases) {
345
- console.log(`Backing up database: ${db.name}`);
368
+ console.log(chalk.yellow(`Backing up database: ${db.name}`));
346
369
  await this.controller.backupDatabase(db);
347
370
  }
371
+ console.log(chalk.green("Database backup completed."));
348
372
  }
349
373
  async wipeDatabase() {
350
374
  if (!this.controller.database || !this.controller.storage) {
@@ -373,12 +397,12 @@ export class InteractiveCLI {
373
397
  {
374
398
  type: "confirm",
375
399
  name: "confirm",
376
- message: "Are you sure you want to wipe the selected items? This action cannot be undone.",
400
+ message: chalk.red("Are you sure you want to wipe the selected items? This action cannot be undone."),
377
401
  default: false,
378
402
  },
379
403
  ]);
380
404
  if (confirm) {
381
- console.log("Wiping selected items...");
405
+ console.log(chalk.yellow("Wiping selected items..."));
382
406
  for (const db of selectedDatabases) {
383
407
  await this.controller.wipeDatabase(db);
384
408
  }
@@ -388,26 +412,58 @@ export class InteractiveCLI {
388
412
  if (wipeUsers) {
389
413
  await this.controller.wipeUsers();
390
414
  }
415
+ console.log(chalk.green("Wipe operation completed."));
391
416
  }
392
417
  else {
393
- console.log("Wipe operation cancelled.");
418
+ console.log(chalk.blue("Wipe operation cancelled."));
394
419
  }
395
420
  }
396
- async generateSchemas() {
397
- console.log("Generating schemas...");
398
- await this.controller.generateSchemas();
399
- }
400
- async importData() {
421
+ async wipeCollections() {
401
422
  if (!this.controller.database) {
402
423
  throw new Error("Database is not initialized, is the config file correct & created?");
403
424
  }
404
425
  const databases = await fetchAllDatabases(this.controller.database);
405
- const selectedDatabases = await this.selectDatabases(databases, "Select the database(s) to import data into:");
406
- let selectedCollections = [];
407
- for (const db of selectedDatabases) {
408
- const dbCollections = await this.selectCollections(db, this.controller.database, `Select collections to import data into for database ${db.name}:`, true);
409
- selectedCollections = [...selectedCollections, ...dbCollections];
426
+ const selectedDatabases = await this.selectDatabases(databases, "Select the database(s) containing the collections to wipe:", true);
427
+ for (const database of selectedDatabases) {
428
+ const collections = await this.selectCollections(database, this.controller.database, `Select collections to wipe from ${database.name}:`, true);
429
+ const { confirm } = await inquirer.prompt([
430
+ {
431
+ type: "confirm",
432
+ name: "confirm",
433
+ message: chalk.red(`Are you sure you want to wipe the selected collections from ${database.name}? This action cannot be undone.`),
434
+ default: false,
435
+ },
436
+ ]);
437
+ if (confirm) {
438
+ console.log(chalk.yellow(`Wiping selected collections from ${database.name}...`));
439
+ for (const collection of collections) {
440
+ await this.controller.wipeCollection(database, collection);
441
+ console.log(chalk.green(`Collection ${collection.name} wiped successfully.`));
442
+ }
443
+ }
444
+ else {
445
+ console.log(chalk.blue(`Wipe operation cancelled for ${database.name}.`));
446
+ }
410
447
  }
448
+ console.log(chalk.green("Wipe collections operation completed."));
449
+ }
450
+ async generateSchemas() {
451
+ console.log(chalk.yellow("Generating schemas..."));
452
+ await this.controller.generateSchemas();
453
+ console.log(chalk.green("Schema generation completed."));
454
+ }
455
+ async importData() {
456
+ console.log(chalk.yellow("Importing data..."));
457
+ const { doBackup } = await inquirer.prompt([
458
+ {
459
+ type: "confirm",
460
+ name: "doBackup",
461
+ message: "Do you want to perform a backup before importing?",
462
+ default: true,
463
+ },
464
+ ]);
465
+ const databases = await this.selectDatabases(await fetchAllDatabases(this.controller.database), "Select databases to import data into:", true);
466
+ const collections = await this.selectCollections(databases[0], this.controller.database, "Select collections to import data into (leave empty for all):", true);
411
467
  const { shouldWriteFile } = await inquirer.prompt([
412
468
  {
413
469
  type: "confirm",
@@ -416,24 +472,20 @@ export class InteractiveCLI {
416
472
  default: false,
417
473
  },
418
474
  ]);
419
- // const { checkDuplicates } = await inquirer.prompt([
420
- // {
421
- // type: "confirm",
422
- // name: "checkDuplicates",
423
- // message: "Do you want to check for duplicates during import?",
424
- // default: true,
425
- // },
426
- // ]);
427
- console.log("Importing data...");
428
- await this.controller.importData({
429
- databases: selectedDatabases,
430
- collections: selectedCollections.length > 0
431
- ? selectedCollections.map((c) => c.$id)
432
- : undefined,
475
+ const options = {
476
+ databases,
477
+ collections: collections.map(c => c.name),
478
+ doBackup,
479
+ importData: true,
433
480
  shouldWriteFile,
434
- checkDuplicates: false,
435
- // checkDuplicates,
436
- });
481
+ };
482
+ try {
483
+ await this.controller.importData(options);
484
+ console.log(chalk.green("Data import completed successfully."));
485
+ }
486
+ catch (error) {
487
+ console.error(chalk.red("Error importing data:"), error);
488
+ }
437
489
  }
438
490
  async transferData() {
439
491
  if (!this.controller.database) {
@@ -481,18 +533,17 @@ export class InteractiveCLI {
481
533
  sourceDatabases = targetDatabases = allDatabases;
482
534
  }
483
535
  const fromDbs = await this.selectDatabases(sourceDatabases, "Select the source database:", false);
484
- console.log(fromDbs);
485
- const fromDb = fromDbs;
536
+ const fromDb = fromDbs[0];
486
537
  if (!fromDb) {
487
538
  throw new Error("No source database selected");
488
539
  }
489
540
  const availableDbs = targetDatabases.filter((db) => db.$id !== fromDb.$id);
490
541
  const targetDbs = await this.selectDatabases(availableDbs, "Select the target database:", false);
491
- const targetDb = targetDbs;
542
+ const targetDb = targetDbs[0];
492
543
  if (!targetDb) {
493
544
  throw new Error("No target database selected");
494
545
  }
495
- const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer):");
546
+ const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer:");
496
547
  const { transferStorage } = await inquirer.prompt([
497
548
  {
498
549
  type: "confirm",
@@ -513,8 +564,8 @@ export class InteractiveCLI {
513
564
  : sourceBuckets;
514
565
  const sourceBucketPicked = await this.selectBuckets(sourceBuckets.buckets, "Select the source bucket:", false);
515
566
  const targetBucketPicked = await this.selectBuckets(targetBuckets.buckets, "Select the target bucket:", false);
516
- sourceBucket = sourceBucketPicked;
517
- targetBucket = targetBucketPicked;
567
+ sourceBucket = sourceBucketPicked[0];
568
+ targetBucket = targetBucketPicked[0];
518
569
  }
519
570
  let transferOptions = {
520
571
  fromDb,
@@ -532,7 +583,44 @@ export class InteractiveCLI {
532
583
  ...remoteOptions,
533
584
  };
534
585
  }
535
- console.log("Transferring data...");
586
+ console.log(chalk.yellow("Transferring data..."));
536
587
  await this.controller.transferData(transferOptions);
588
+ console.log(chalk.green("Data transfer completed."));
589
+ }
590
+ getLocalCollections() {
591
+ const configCollections = this.controller.config.collections || [];
592
+ // @ts-expect-error - appwrite invalid types
593
+ return configCollections.map(c => ({
594
+ $id: c.$id || ulid(),
595
+ $createdAt: DateTime.now().toISO(),
596
+ $updatedAt: DateTime.now().toISO(),
597
+ name: c.name,
598
+ enabled: c.enabled || true,
599
+ documentSecurity: c.documentSecurity || false,
600
+ attributes: c.attributes || [],
601
+ indexes: c.indexes || [],
602
+ $permissions: PermissionToAppwritePermission(c.$permissions) || [],
603
+ databaseId: c.databaseId,
604
+ }));
605
+ }
606
+ getLocalDatabases() {
607
+ const configDatabases = this.controller.config.databases || [];
608
+ return configDatabases.map(db => ({
609
+ $id: db.$id || ulid(),
610
+ $createdAt: DateTime.now().toISO(),
611
+ $updatedAt: DateTime.now().toISO(),
612
+ name: db.name,
613
+ enabled: true,
614
+ }));
615
+ }
616
+ async reloadConfig() {
617
+ console.log(chalk.yellow("Reloading configuration files..."));
618
+ try {
619
+ await this.controller.reloadConfig();
620
+ console.log(chalk.green("Configuration files reloaded successfully."));
621
+ }
622
+ catch (error) {
623
+ console.error(chalk.red("Error reloading configuration files:"), error);
624
+ }
537
625
  }
538
626
  }