appwrite-utils-cli 0.9.91 → 0.9.93

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.93: Updated `selectDatabases` and `selectCollections` from the interactive CLI to prefer local collections or databases when synchronizing the databases
129
+ - 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
128
130
  - 0.9.91: Fixed another webpack error, screw you react (but you're supported now so I guess not-screw-you)
129
131
  - 0.9.90: Fixed Webpack errors (why tf does webpack add an extra `default`...???)
130
132
  - 0.9.80: Fixed collections not being unique between local and remote
@@ -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,9 +4,9 @@ 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
- import { parseAttribute, PermissionToAppwritePermission } from "appwrite-utils";
9
+ import { parseAttribute, PermissionToAppwritePermission, } from "appwrite-utils";
10
10
  import { ulid } from "ulidx";
11
11
  import chalk from "chalk";
12
12
  import { DateTime } from "luxon";
@@ -106,15 +106,34 @@ export class InteractiveCLI {
106
106
  async selectDatabases(databases, message, multiSelect = true) {
107
107
  await this.initControllerIfNeeded();
108
108
  const configDatabases = this.getLocalDatabases();
109
- const allDatabases = [...databases, ...configDatabases].reduce((acc, db) => {
110
- if (!acc.find(d => d.name === db.name)) {
109
+ const allDatabases = [...databases, ...configDatabases]
110
+ .reduce((acc, db) => {
111
+ // Local config takes precedence - if a database with same name exists, use local version
112
+ const existingIndex = acc.findIndex((d) => d.name === db.name);
113
+ if (existingIndex >= 0) {
114
+ if (configDatabases.some((cdb) => cdb.name === db.name)) {
115
+ acc[existingIndex] = db; // Replace with local version
116
+ }
117
+ }
118
+ else {
111
119
  acc.push(db);
112
120
  }
113
121
  return acc;
114
- }, []);
122
+ }, [])
123
+ .filter((db) => db.name.toLowerCase() !== "migrations");
124
+ const hasLocalAndRemote = allDatabases.some((db) => configDatabases.some((c) => c.name === db.name)) &&
125
+ allDatabases.some((db) => !configDatabases.some((c) => c.name === db.name));
115
126
  const choices = allDatabases
116
127
  .sort((a, b) => a.name.localeCompare(b.name))
117
- .map((db) => ({ name: db.name, value: db }))
128
+ .map((db) => ({
129
+ name: db.name +
130
+ (hasLocalAndRemote
131
+ ? configDatabases.some((c) => c.name === db.name)
132
+ ? " (Local)"
133
+ : " (Remote)"
134
+ : ""),
135
+ value: db,
136
+ }))
118
137
  .filter((db) => db.name.toLowerCase() !== "migrations");
119
138
  const { selectedDatabases } = await inquirer.prompt([
120
139
  {
@@ -128,15 +147,41 @@ export class InteractiveCLI {
128
147
  ]);
129
148
  return selectedDatabases;
130
149
  }
131
- async selectCollections(database, databasesClient, message, multiSelect = true) {
150
+ async selectCollections(database, databasesClient, message, multiSelect = true, preferLocal = false) {
132
151
  await this.initControllerIfNeeded();
133
- const collections = await fetchAllCollections(database.$id, databasesClient);
134
152
  const configCollections = this.getLocalCollections();
135
- const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
136
- const allCollectionNamesUnique = Array.from(new Set(collectionNames));
137
- const allCollections = allCollectionNamesUnique.map((name) => configCollections.find((c) => c.name === name) ?? collections.find((c) => c.name === name)).filter((v) => v !== undefined);
138
- const choices = allCollections.map((collection) => ({
139
- name: collection.name,
153
+ let remoteCollections = [];
154
+ const dbExists = await databasesClient.list([
155
+ Query.equal("name", database.name),
156
+ ]);
157
+ if (dbExists.total === 0) {
158
+ console.log(chalk.red(`Database "${database.name}" does not exist, using only local collection options`));
159
+ }
160
+ else {
161
+ remoteCollections = await fetchAllCollections(database.$id, databasesClient);
162
+ }
163
+ const allCollections = preferLocal
164
+ ? remoteCollections.reduce((acc, remoteCollection) => {
165
+ if (!acc.some((c) => c.name === remoteCollection.name)) {
166
+ acc.push(remoteCollection);
167
+ }
168
+ return acc;
169
+ }, [...configCollections])
170
+ : [
171
+ ...remoteCollections,
172
+ ...configCollections.filter((c) => !remoteCollections.some((rc) => rc.name === c.name)),
173
+ ];
174
+ const hasLocalAndRemote = allCollections.some((coll) => configCollections.some((c) => c.name === coll.name)) &&
175
+ allCollections.some((coll) => !configCollections.some((c) => c.name === coll.name));
176
+ const choices = allCollections
177
+ .sort((a, b) => a.name.localeCompare(b.name))
178
+ .map((collection) => ({
179
+ name: collection.name +
180
+ (hasLocalAndRemote
181
+ ? configCollections.some((c) => c.name === collection.name)
182
+ ? " (Local)"
183
+ : " (Remote)"
184
+ : ""),
140
185
  value: collection,
141
186
  }));
142
187
  const { selectedCollections } = await inquirer.prompt([
@@ -188,7 +233,14 @@ export class InteractiveCLI {
188
233
  const allBuckets = await listBuckets(storage);
189
234
  // If there are no buckets, ask to create one for each database
190
235
  if (allBuckets.total === 0) {
191
- for (const database of databases ?? config.databases) {
236
+ const databasesToUse = databases ?? config.databases;
237
+ for (const database of databasesToUse) {
238
+ // If database has bucket config in local config, use that
239
+ const localDatabase = this.controller.config?.databases.find((db) => db.name === database.name);
240
+ if (localDatabase?.bucket) {
241
+ database.bucket = localDatabase.bucket;
242
+ continue;
243
+ }
192
244
  const { wantCreateBucket } = await inquirer.prompt([
193
245
  {
194
246
  type: "confirm",
@@ -354,7 +406,8 @@ export class InteractiveCLI {
354
406
  async syncDb() {
355
407
  console.log(chalk.yellow("Syncing database..."));
356
408
  const databases = await this.selectDatabases(await fetchAllDatabases(this.controller.database), chalk.blue("Select databases to synchronize:"), true);
357
- const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select collections to synchronize:"), true);
409
+ const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select collections to synchronize:"), true, true // prefer local
410
+ );
358
411
  await this.controller.syncDb(databases, collections);
359
412
  console.log(chalk.green("Database sync completed."));
360
413
  }
@@ -486,7 +539,7 @@ export class InteractiveCLI {
486
539
  ]);
487
540
  const options = {
488
541
  databases,
489
- collections: collections.map(c => c.name),
542
+ collections: collections.map((c) => c.name),
490
543
  doBackup,
491
544
  importData: true,
492
545
  shouldWriteFile,
@@ -555,7 +608,8 @@ export class InteractiveCLI {
555
608
  if (!targetDb) {
556
609
  throw new Error("No target database selected");
557
610
  }
558
- const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer:");
611
+ const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer:", true, false // don't prefer local for transfers
612
+ );
559
613
  const { transferStorage } = await inquirer.prompt([
560
614
  {
561
615
  type: "confirm",
@@ -602,7 +656,7 @@ export class InteractiveCLI {
602
656
  getLocalCollections() {
603
657
  const configCollections = this.controller.config?.collections || [];
604
658
  // @ts-expect-error - appwrite invalid types
605
- return configCollections.map(c => ({
659
+ return configCollections.map((c) => ({
606
660
  $id: c.$id || ulid(),
607
661
  $createdAt: DateTime.now().toISO(),
608
662
  $updatedAt: DateTime.now().toISO(),
@@ -617,7 +671,7 @@ export class InteractiveCLI {
617
671
  }
618
672
  getLocalDatabases() {
619
673
  const configDatabases = this.controller.config?.databases || [];
620
- return configDatabases.map(db => ({
674
+ return configDatabases.map((db) => ({
621
675
  $id: db.$id || ulid(),
622
676
  $createdAt: DateTime.now().toISO(),
623
677
  $updatedAt: DateTime.now().toISO(),
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.91",
4
+ "version": "0.9.93",
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,10 +10,16 @@ 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";
16
- import { parseAttribute, PermissionToAppwritePermission, type AppwriteConfig, type ConfigDatabases } from "appwrite-utils";
17
+ import {
18
+ parseAttribute,
19
+ PermissionToAppwritePermission,
20
+ type AppwriteConfig,
21
+ type ConfigDatabases,
22
+ } from "appwrite-utils";
17
23
  import { ulid } from "ulidx";
18
24
  import chalk from "chalk";
19
25
  import { DateTime } from "luxon";
@@ -40,9 +46,13 @@ export class InteractiveCLI {
40
46
  constructor(private currentDir: string) {}
41
47
 
42
48
  async run(): Promise<void> {
43
- console.log(chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley"));
44
49
  console.log(
45
- chalk.blue("For more information, visit https://github.com/zachhandley/AppwriteUtils")
50
+ chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley")
51
+ );
52
+ console.log(
53
+ chalk.blue(
54
+ "For more information, visit https://github.com/zachhandley/AppwriteUtils"
55
+ )
46
56
  );
47
57
 
48
58
  while (true) {
@@ -123,18 +133,43 @@ export class InteractiveCLI {
123
133
  ): Promise<Models.Database[]> {
124
134
  await this.initControllerIfNeeded();
125
135
  const configDatabases = this.getLocalDatabases();
126
- const allDatabases = [...databases, ...configDatabases].reduce((acc, db) => {
127
- if (!acc.find(d => d.name === db.name)) {
128
- acc.push(db);
129
- }
130
- return acc;
131
- }, [] as Models.Database[]);
132
-
136
+ const allDatabases = [...databases, ...configDatabases]
137
+ .reduce((acc, db) => {
138
+ // Local config takes precedence - if a database with same name exists, use local version
139
+ const existingIndex = acc.findIndex((d) => d.name === db.name);
140
+ if (existingIndex >= 0) {
141
+ if (configDatabases.some((cdb) => cdb.name === db.name)) {
142
+ acc[existingIndex] = db; // Replace with local version
143
+ }
144
+ } else {
145
+ acc.push(db);
146
+ }
147
+ return acc;
148
+ }, [] as Models.Database[])
149
+ .filter((db) => db.name.toLowerCase() !== "migrations");
150
+
151
+ const hasLocalAndRemote =
152
+ allDatabases.some((db) =>
153
+ configDatabases.some((c) => c.name === db.name)
154
+ ) &&
155
+ allDatabases.some(
156
+ (db) => !configDatabases.some((c) => c.name === db.name)
157
+ );
158
+
133
159
  const choices = allDatabases
134
160
  .sort((a, b) => a.name.localeCompare(b.name))
135
- .map((db) => ({ name: db.name, value: db }))
161
+ .map((db) => ({
162
+ name:
163
+ db.name +
164
+ (hasLocalAndRemote
165
+ ? configDatabases.some((c) => c.name === db.name)
166
+ ? " (Local)"
167
+ : " (Remote)"
168
+ : ""),
169
+ value: db,
170
+ }))
136
171
  .filter((db) => db.name.toLowerCase() !== "migrations");
137
-
172
+
138
173
  const { selectedDatabases } = await inquirer.prompt([
139
174
  {
140
175
  type: multiSelect ? "checkbox" : "list",
@@ -145,7 +180,7 @@ export class InteractiveCLI {
145
180
  pageSize: 10,
146
181
  },
147
182
  ]);
148
-
183
+
149
184
  return selectedDatabases;
150
185
  }
151
186
 
@@ -153,21 +188,67 @@ export class InteractiveCLI {
153
188
  database: Models.Database,
154
189
  databasesClient: Databases,
155
190
  message: string,
156
- multiSelect = true
191
+ multiSelect = true,
192
+ preferLocal = false
157
193
  ): Promise<Models.Collection[]> {
158
194
  await this.initControllerIfNeeded();
159
- const collections = await fetchAllCollections(
160
- database.$id,
161
- databasesClient
162
- );
195
+
163
196
  const configCollections = this.getLocalCollections();
164
- const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
165
- const allCollectionNamesUnique = Array.from(new Set(collectionNames));
166
- const allCollections = allCollectionNamesUnique.map((name) => configCollections.find((c) => c.name === name) ?? collections.find((c) => c.name === name)).filter((v) => v !== undefined);
167
- const choices = allCollections.map((collection) => ({
168
- name: collection.name,
169
- value: collection,
170
- }));
197
+ let remoteCollections: Models.Collection[] = [];
198
+
199
+ const dbExists = await databasesClient.list([
200
+ Query.equal("name", database.name),
201
+ ]);
202
+ if (dbExists.total === 0) {
203
+ console.log(
204
+ chalk.red(
205
+ `Database "${database.name}" does not exist, using only local collection options`
206
+ )
207
+ );
208
+ } else {
209
+ remoteCollections = await fetchAllCollections(
210
+ database.$id,
211
+ databasesClient
212
+ );
213
+ }
214
+
215
+ const allCollections = preferLocal
216
+ ? remoteCollections.reduce(
217
+ (acc, remoteCollection) => {
218
+ if (!acc.some((c) => c.name === remoteCollection.name)) {
219
+ acc.push(remoteCollection);
220
+ }
221
+ return acc;
222
+ },
223
+ [...configCollections]
224
+ )
225
+ : [
226
+ ...remoteCollections,
227
+ ...configCollections.filter(
228
+ (c) => !remoteCollections.some((rc) => rc.name === c.name)
229
+ ),
230
+ ];
231
+
232
+ const hasLocalAndRemote =
233
+ allCollections.some((coll) =>
234
+ configCollections.some((c) => c.name === coll.name)
235
+ ) &&
236
+ allCollections.some(
237
+ (coll) => !configCollections.some((c) => c.name === coll.name)
238
+ );
239
+
240
+ const choices = allCollections
241
+ .sort((a, b) => a.name.localeCompare(b.name))
242
+ .map((collection) => ({
243
+ name:
244
+ collection.name +
245
+ (hasLocalAndRemote
246
+ ? configCollections.some((c) => c.name === collection.name)
247
+ ? " (Local)"
248
+ : " (Remote)"
249
+ : ""),
250
+ value: collection,
251
+ }));
171
252
 
172
253
  const { selectedCollections } = await inquirer.prompt([
173
254
  {
@@ -217,7 +298,9 @@ export class InteractiveCLI {
217
298
  input.trim() !== "" || "Collection name cannot be empty.",
218
299
  },
219
300
  ]);
220
- console.log(chalk.green(`Creating collection config file for '${collectionName}'...`));
301
+ console.log(
302
+ chalk.green(`Creating collection config file for '${collectionName}'...`)
303
+ );
221
304
  createEmptyCollection(collectionName);
222
305
  }
223
306
 
@@ -236,12 +319,23 @@ export class InteractiveCLI {
236
319
 
237
320
  // If there are no buckets, ask to create one for each database
238
321
  if (allBuckets.total === 0) {
239
- for (const database of databases ?? config.databases) {
322
+ const databasesToUse = databases ?? config.databases;
323
+ for (const database of databasesToUse) {
324
+ // If database has bucket config in local config, use that
325
+ const localDatabase = this.controller!.config?.databases.find(
326
+ (db) => db.name === database.name
327
+ );
328
+ if (localDatabase?.bucket) {
329
+ database.bucket = localDatabase.bucket;
330
+ continue;
331
+ }
240
332
  const { wantCreateBucket } = await inquirer.prompt([
241
333
  {
242
334
  type: "confirm",
243
335
  name: "wantCreateBucket",
244
- message: chalk.blue(`There are no buckets. Do you want to create a bucket for the database "${database.name}"?`),
336
+ message: chalk.blue(
337
+ `There are no buckets. Do you want to create a bucket for the database "${database.name}"?`
338
+ ),
245
339
  default: true,
246
340
  },
247
341
  ]);
@@ -448,13 +542,14 @@ export class InteractiveCLI {
448
542
  const databases = await this.selectDatabases(
449
543
  await fetchAllDatabases(this.controller!.database!),
450
544
  chalk.blue("Select databases to synchronize:"),
451
- true,
545
+ true
452
546
  );
453
547
  const collections = await this.selectCollections(
454
548
  databases[0],
455
549
  this.controller!.database!,
456
550
  chalk.blue("Select collections to synchronize:"),
457
551
  true,
552
+ true // prefer local
458
553
  );
459
554
  await this.controller!.syncDb(databases, collections);
460
555
  console.log(chalk.green("Database sync completed."));
@@ -600,13 +695,19 @@ export class InteractiveCLI {
600
695
  ]);
601
696
 
602
697
  if (confirm) {
603
- console.log(chalk.yellow(`Wiping selected collections from ${database.name}...`));
698
+ console.log(
699
+ chalk.yellow(`Wiping selected collections from ${database.name}...`)
700
+ );
604
701
  for (const collection of collections) {
605
702
  await this.controller!.wipeCollection(database, collection);
606
- console.log(chalk.green(`Collection ${collection.name} wiped successfully.`));
703
+ console.log(
704
+ chalk.green(`Collection ${collection.name} wiped successfully.`)
705
+ );
607
706
  }
608
707
  } else {
609
- console.log(chalk.blue(`Wipe operation cancelled for ${database.name}.`));
708
+ console.log(
709
+ chalk.blue(`Wipe operation cancelled for ${database.name}.`)
710
+ );
610
711
  }
611
712
  }
612
713
  console.log(chalk.green("Wipe collections operation completed."));
@@ -654,7 +755,7 @@ export class InteractiveCLI {
654
755
 
655
756
  const options = {
656
757
  databases,
657
- collections: collections.map(c => c.name),
758
+ collections: collections.map((c) => c.name),
658
759
  doBackup,
659
760
  importData: true,
660
761
  shouldWriteFile,
@@ -753,7 +854,9 @@ export class InteractiveCLI {
753
854
  const selectedCollections = await this.selectCollections(
754
855
  fromDb,
755
856
  sourceClient,
756
- "Select collections to transfer:"
857
+ "Select collections to transfer:",
858
+ true,
859
+ false // don't prefer local for transfers
757
860
  );
758
861
 
759
862
  const { transferStorage } = await inquirer.prompt([
@@ -822,11 +925,10 @@ export class InteractiveCLI {
822
925
  console.log(chalk.green("Data transfer completed."));
823
926
  }
824
927
 
825
-
826
928
  private getLocalCollections(): Models.Collection[] {
827
929
  const configCollections = this.controller!.config?.collections || [];
828
930
  // @ts-expect-error - appwrite invalid types
829
- return configCollections.map(c => ({
931
+ return configCollections.map((c) => ({
830
932
  $id: c.$id || ulid(),
831
933
  $createdAt: DateTime.now().toISO(),
832
934
  $updatedAt: DateTime.now().toISO(),
@@ -842,7 +944,7 @@ export class InteractiveCLI {
842
944
 
843
945
  private getLocalDatabases(): Models.Database[] {
844
946
  const configDatabases = this.controller!.config?.databases || [];
845
- return configDatabases.map(db => ({
947
+ return configDatabases.map((db) => ({
846
948
  $id: db.$id || ulid(),
847
949
  $createdAt: DateTime.now().toISO(),
848
950
  $updatedAt: DateTime.now().toISO(),
@@ -860,4 +962,4 @@ export class InteractiveCLI {
860
962
  console.error(chalk.red("Error reloading configuration files:"), error);
861
963
  }
862
964
  }
863
- }
965
+ }