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 +2 -0
- package/dist/collections/attributes.js +20 -2
- package/dist/interactiveCLI.js +73 -19
- package/package.json +1 -1
- package/src/collections/attributes.ts +23 -2
- package/src/interactiveCLI.ts +140 -38
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(
|
264
|
+
await delay(200);
|
247
265
|
}
|
248
266
|
console.log(`Finished creating/updating attributes for collection: ${collection.name}`);
|
249
267
|
};
|
package/dist/interactiveCLI.js
CHANGED
@@ -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]
|
110
|
-
|
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) => ({
|
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
|
-
|
136
|
-
const
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
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.
|
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(
|
510
|
+
await delay(200);
|
490
511
|
}
|
491
512
|
console.log(
|
492
513
|
`Finished creating/updating attributes for collection: ${collection.name}`
|
package/src/interactiveCLI.ts
CHANGED
@@ -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 {
|
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.
|
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]
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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) => ({
|
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
|
-
|
160
|
-
database.$id,
|
161
|
-
databasesClient
|
162
|
-
);
|
195
|
+
|
163
196
|
const configCollections = this.getLocalCollections();
|
164
|
-
|
165
|
-
|
166
|
-
const
|
167
|
-
|
168
|
-
|
169
|
-
|
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(
|
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
|
-
|
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(
|
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(
|
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(
|
703
|
+
console.log(
|
704
|
+
chalk.green(`Collection ${collection.name} wiped successfully.`)
|
705
|
+
);
|
607
706
|
}
|
608
707
|
} else {
|
609
|
-
console.log(
|
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
|
+
}
|