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 +3 -0
- package/dist/collections/attributes.js +12 -5
- package/dist/collections/methods.d.ts +2 -1
- package/dist/collections/methods.js +68 -36
- package/dist/interactiveCLI.d.ts +4 -0
- package/dist/interactiveCLI.js +141 -53
- package/dist/main.js +53 -39
- package/dist/migrations/importController.d.ts +3 -3
- package/dist/migrations/importController.js +126 -149
- package/dist/migrations/setupDatabase.d.ts +2 -1
- package/dist/migrations/setupDatabase.js +27 -5
- package/dist/storage/methods.js +4 -1
- package/dist/utils/setupFiles.js +4 -1
- package/dist/utilsController.d.ts +8 -4
- package/dist/utilsController.js +45 -15
- package/package.json +4 -3
- package/src/collections/attributes.ts +12 -7
- package/src/collections/methods.ts +78 -40
- package/src/interactiveCLI.ts +179 -75
- package/src/main.ts +60 -45
- package/src/migrations/importController.ts +167 -279
- package/src/migrations/setupDatabase.ts +48 -7
- package/src/storage/methods.ts +4 -4
- package/src/utils/setupFiles.ts +4 -1
- package/src/utilsController.ts +50 -13
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 =
|
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(
|
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 =
|
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
|
110
|
-
if (!
|
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
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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",
|
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: ${
|
179
|
+
console.log(`Creating collection: ${collectionData.name}`);
|
150
180
|
let foundColl = deletedCollections?.find((coll) => coll.collectionName.toLowerCase().trim().replace(" ", "") ===
|
151
|
-
|
152
|
-
if (
|
153
|
-
collectionId =
|
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,
|
165
|
-
|
166
|
-
nameToIdMapping.set(
|
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 ${
|
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 ${
|
175
|
-
await tryAwaitWithRetry(async () => await database.updateCollection(databaseId, collectionToUse.$id,
|
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,
|
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
|
}
|
package/dist/interactiveCLI.d.ts
CHANGED
@@ -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
|
}
|
package/dist/interactiveCLI.js
CHANGED
@@ -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("
|
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
|
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
|
-
|
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
|
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)
|
406
|
-
|
407
|
-
|
408
|
-
const
|
409
|
-
|
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
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
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
|
-
|
435
|
-
|
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
|
-
|
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
|
}
|