appwrite-utils-cli 0.9.3 → 0.9.5

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
@@ -124,6 +124,8 @@ This updated CLI ensures that developers have robust tools at their fingertips t
124
124
 
125
125
  ## Changelog
126
126
 
127
+ - 0.9.5: Fixed not checking for storage bucket for each database (checking the creation status) when importing data
128
+ - 0.9.4: Fixed migrations database ensuring it has the required collections
127
129
  - 0.9.3: Fixed deployment error && fix lack of existing `appwriteConfig.ts` file from causing error (you want to be able to setup yeah? haha)
128
130
  - 0.9.2: Added changelog back, whoops
129
131
  - 0.0.90: Updated README with new command-line options and fixed alias issues
@@ -91,10 +91,6 @@ export class InteractiveCLI {
91
91
  }
92
92
  async selectDatabases(databases, message, multiSelect = true) {
93
93
  const choices = databases.map((db) => ({ name: db.name, value: db }));
94
- if (multiSelect) {
95
- choices.unshift({ name: "Select All", value: "ALL" });
96
- choices.push({ name: "Clear Selection", value: "CLEAR" });
97
- }
98
94
  const { selectedDatabases } = await inquirer.prompt([
99
95
  {
100
96
  type: multiSelect ? "checkbox" : "list",
@@ -105,15 +101,7 @@ export class InteractiveCLI {
105
101
  pageSize: 10,
106
102
  },
107
103
  ]);
108
- if (multiSelect) {
109
- if (selectedDatabases.includes("ALL")) {
110
- return databases;
111
- }
112
- else if (selectedDatabases.includes("CLEAR")) {
113
- return [];
114
- }
115
- }
116
- return selectedDatabases.filter((db) => typeof db !== "string");
104
+ return selectedDatabases;
117
105
  }
118
106
  async selectCollections(database, databasesClient, message, multiSelect = true) {
119
107
  const collections = await fetchAllCollections(database.$id, databasesClient);
@@ -121,10 +109,6 @@ export class InteractiveCLI {
121
109
  name: collection.name,
122
110
  value: collection,
123
111
  }));
124
- if (multiSelect) {
125
- choices.unshift({ name: "Select All", value: "ALL" });
126
- choices.push({ name: "Clear Selection", value: "CLEAR" });
127
- }
128
112
  const { selectedCollections } = await inquirer.prompt([
129
113
  {
130
114
  type: multiSelect ? "checkbox" : "list",
@@ -135,25 +119,13 @@ export class InteractiveCLI {
135
119
  pageSize: 10,
136
120
  },
137
121
  ]);
138
- if (multiSelect) {
139
- if (selectedCollections.includes("ALL")) {
140
- return collections;
141
- }
142
- else if (selectedCollections.includes("CLEAR")) {
143
- return [];
144
- }
145
- }
146
- return selectedCollections.filter((collection) => typeof collection !== "string");
122
+ return selectedCollections;
147
123
  }
148
124
  async selectBuckets(buckets, message, multiSelect = true) {
149
125
  const choices = buckets.map((bucket) => ({
150
126
  name: bucket.name,
151
- value: bucket.$id,
127
+ value: bucket,
152
128
  }));
153
- if (multiSelect) {
154
- choices.unshift({ name: "Select All", value: "ALL" });
155
- choices.push({ name: "Clear Selection", value: "CLEAR" });
156
- }
157
129
  const { selectedBuckets } = await inquirer.prompt([
158
130
  {
159
131
  type: multiSelect ? "checkbox" : "list",
@@ -164,15 +136,7 @@ export class InteractiveCLI {
164
136
  pageSize: 10,
165
137
  },
166
138
  ]);
167
- if (multiSelect) {
168
- if (selectedBuckets.includes("ALL")) {
169
- return buckets;
170
- }
171
- else if (selectedBuckets.includes("CLEAR")) {
172
- return [];
173
- }
174
- }
175
- return selectedBuckets.map((id) => buckets.find((bucket) => bucket.$id === id));
139
+ return selectedBuckets;
176
140
  }
177
141
  async createCollectionConfig() {
178
142
  const { collectionName } = await inquirer.prompt([
@@ -365,7 +329,7 @@ export class InteractiveCLI {
365
329
  throw new Error("Database is not initialized. Is the config file correct and created?");
366
330
  }
367
331
  const databases = await fetchAllDatabases(this.controller.database);
368
- const selectedDatabases = await this.selectDatabases(databases, "Select databases to synchronize (or select none to synchronize all):");
332
+ const selectedDatabases = await this.selectDatabases(databases, "Select databases to synchronize:");
369
333
  console.log("Configuring storage buckets...");
370
334
  const updatedConfig = await this.configureBuckets(this.controller.config, selectedDatabases);
371
335
  console.log("Synchronizing configurations...");
@@ -376,7 +340,7 @@ export class InteractiveCLI {
376
340
  throw new Error("Database is not initialized, is the config file correct & created?");
377
341
  }
378
342
  const databases = await fetchAllDatabases(this.controller.database);
379
- const selectedDatabases = await this.selectDatabases(databases, "Select databases to backup (or select none to backup all):");
343
+ const selectedDatabases = await this.selectDatabases(databases, "Select databases to backup:");
380
344
  for (const db of selectedDatabases) {
381
345
  console.log(`Backing up database: ${db.name}`);
382
346
  await this.controller.backupDatabase(db);
@@ -388,12 +352,12 @@ export class InteractiveCLI {
388
352
  }
389
353
  const databases = await fetchAllDatabases(this.controller.database);
390
354
  const storage = await listBuckets(this.controller.storage);
391
- const selectedDatabases = await this.selectDatabases(databases, "Select databases to wipe (or select none to skip database wipe):");
355
+ const selectedDatabases = await this.selectDatabases(databases, "Select databases to wipe:");
392
356
  const { selectedStorage } = await inquirer.prompt([
393
357
  {
394
358
  type: "checkbox",
395
359
  name: "selectedStorage",
396
- message: "Select storage buckets to wipe (or select none to skip storage wipe):",
360
+ message: "Select storage buckets to wipe:",
397
361
  choices: storage.buckets.map((s) => ({ name: s.name, value: s.$id })),
398
362
  },
399
363
  ]);
@@ -441,7 +405,7 @@ export class InteractiveCLI {
441
405
  const selectedDatabases = await this.selectDatabases(databases, "Select the database(s) to import data into:");
442
406
  let selectedCollections = [];
443
407
  for (const db of selectedDatabases) {
444
- const dbCollections = await this.selectCollections(db, this.controller.database, `Select collections to import data into for database ${db.name} (or select none to import into all):`, true);
408
+ const dbCollections = await this.selectCollections(db, this.controller.database, `Select collections to import data into for database ${db.name}:`, true);
445
409
  selectedCollections = [...selectedCollections, ...dbCollections];
446
410
  }
447
411
  const { shouldWriteFile } = await inquirer.prompt([
@@ -516,7 +480,7 @@ export class InteractiveCLI {
516
480
  }
517
481
  const [fromDb] = await this.selectDatabases(sourceDatabases, "Select the source database:", false);
518
482
  const [targetDb] = await this.selectDatabases(targetDatabases.filter((db) => db.$id !== fromDb.$id), "Select the target database:", false);
519
- const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer (or select none to transfer all):");
483
+ const selectedCollections = await this.selectCollections(fromDb, sourceClient, "Select collections to transfer):");
520
484
  const { transferStorage } = await inquirer.prompt([
521
485
  {
522
486
  type: "confirm",
@@ -9,7 +9,7 @@ export const setupMigrationDatabase = async (config) => {
9
9
  console.log("Starting Migrations Setup");
10
10
  console.log("---------------------------------");
11
11
  const database = new Databases(config.appwriteClient);
12
- let db = null;
12
+ let db;
13
13
  const migrationCollectionsSetup = getMigrationCollectionSchemas();
14
14
  try {
15
15
  db = await tryAwaitWithRetry(async () => await database.get("migrations"), undefined, true);
@@ -19,18 +19,40 @@ export const setupMigrationDatabase = async (config) => {
19
19
  db = await tryAwaitWithRetry(async () => await database.create("migrations", "Migrations", true));
20
20
  console.log("Migrations database created");
21
21
  }
22
+ if (!db) {
23
+ console.error("Failed to create or retrieve the migrations database");
24
+ return;
25
+ }
22
26
  for (const [collectionName, { collection, attributes }] of Object.entries(migrationCollectionsSetup)) {
23
27
  const collectionId = toCamelCase(collectionName);
24
- let collectionFound = null;
28
+ let collectionFound;
25
29
  try {
26
- collectionFound = await tryAwaitWithRetry(async () => await database.getCollection(db.$id, collectionId));
30
+ collectionFound = await tryAwaitWithRetry(async () => await database.getCollection(db.$id, collectionId), undefined, true);
31
+ console.log(`Collection found: ${collectionId}`);
27
32
  }
28
33
  catch (e) {
29
34
  console.log(`Collection not found: ${collectionId}`);
30
- collectionFound = await tryAwaitWithRetry(async () => await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled));
35
+ try {
36
+ collectionFound = await tryAwaitWithRetry(async () => await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled), undefined, true);
37
+ console.log(`Collection created: ${collectionId}`);
38
+ }
39
+ catch (createError) {
40
+ console.error(`Failed to create collection: ${collectionId}`, createError);
41
+ continue;
42
+ }
43
+ }
44
+ if (!collectionFound) {
45
+ console.error(`Failed to create or retrieve collection: ${collectionId}`);
46
+ continue;
31
47
  }
32
48
  for (const attribute of attributes) {
33
- await createOrUpdateAttribute(database, db.$id, collectionFound, attribute);
49
+ try {
50
+ await createOrUpdateAttribute(database, db.$id, collectionFound, attribute);
51
+ console.log(`Attribute created/updated: ${attribute.key}`);
52
+ }
53
+ catch (attrError) {
54
+ console.error(`Failed to create/update attribute: ${attribute.key}`, attrError);
55
+ }
34
56
  }
35
57
  }
36
58
  console.log("---------------------------------");
@@ -43,7 +65,7 @@ export const ensureDatabasesExist = async (config) => {
43
65
  const existingDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
44
66
  const migrationsDatabase = existingDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
45
67
  if (existingDatabases.databases.length !== 0 && migrationsDatabase) {
46
- console.log("Wiping all databases except migrations");
68
+ console.log("Creating all databases except migrations");
47
69
  databasesToEnsure.push(migrationsDatabase);
48
70
  }
49
71
  for (const db of databasesToEnsure) {
@@ -0,0 +1,6 @@
1
+ export declare class SetupController {
2
+ private currentDir;
3
+ constructor(currentDir: string);
4
+ runSetup(withExampleData?: boolean): Promise<void>;
5
+ hasExistingConfig(): boolean;
6
+ }
@@ -0,0 +1,17 @@
1
+ import { setupDirsFiles } from "./utils/setupFiles.js";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ export class SetupController {
5
+ currentDir;
6
+ constructor(currentDir) {
7
+ this.currentDir = currentDir;
8
+ }
9
+ async runSetup(withExampleData = false) {
10
+ await setupDirsFiles(withExampleData, this.currentDir);
11
+ console.log("Setup completed successfully.");
12
+ }
13
+ hasExistingConfig() {
14
+ const configPath = path.join(this.currentDir, "appwrite", "appwriteConfig.ts");
15
+ return fs.existsSync(configPath);
16
+ }
17
+ }
@@ -9,6 +9,7 @@ export declare const deleteBucket: (storage: Storage, bucketId: string) => Promi
9
9
  export declare const getFile: (storage: Storage, bucketId: string, fileId: string) => Promise<Models.File>;
10
10
  export declare const listFiles: (storage: Storage, bucketId: string, queries?: string[], search?: string) => Promise<Models.FileList>;
11
11
  export declare const deleteFile: (storage: Storage, bucketId: string, fileId: string) => Promise<{}>;
12
+ export declare const ensureDatabaseConfigBucketsExist: (storage: Storage, config: AppwriteConfig, databases?: Models.Database[]) => Promise<void>;
12
13
  export declare const wipeDocumentStorage: (storage: Storage, bucketId: string) => Promise<void>;
13
14
  export declare const initOrGetDocumentStorage: (storage: Storage, config: AppwriteConfig, dbId: string, bucketName?: string) => Promise<Models.Bucket>;
14
15
  export declare const initOrGetBackupStorage: (config: AppwriteConfig, storage: Storage) => Promise<Models.Bucket>;
@@ -34,6 +34,52 @@ export const listFiles = async (storage, bucketId, queries, search) => {
34
34
  export const deleteFile = async (storage, bucketId, fileId) => {
35
35
  return await storage.deleteFile(bucketId, fileId);
36
36
  };
37
+ export const ensureDatabaseConfigBucketsExist = async (storage, config, databases = []) => {
38
+ for (const db of databases) {
39
+ const database = config.databases?.find((d) => d.$id === db.$id);
40
+ if (database?.bucket) {
41
+ try {
42
+ await storage.getBucket(database.bucket.$id);
43
+ console.log(`Bucket ${database.bucket.$id} already exists.`);
44
+ }
45
+ catch (e) {
46
+ const permissions = [];
47
+ if (database.bucket.permissions &&
48
+ database.bucket.permissions.length > 0) {
49
+ for (const permission of database.bucket.permissions) {
50
+ switch (permission.permission) {
51
+ case "read":
52
+ permissions.push(Permission.read(permission.target));
53
+ break;
54
+ case "create":
55
+ permissions.push(Permission.create(permission.target));
56
+ break;
57
+ case "update":
58
+ permissions.push(Permission.update(permission.target));
59
+ break;
60
+ case "delete":
61
+ permissions.push(Permission.delete(permission.target));
62
+ break;
63
+ case "write":
64
+ permissions.push(Permission.write(permission.target));
65
+ break;
66
+ default:
67
+ console.warn(`Unknown permission: ${permission.permission}`);
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ try {
73
+ await storage.createBucket(database.bucket.$id, database.bucket.name, permissions, database.bucket.fileSecurity, database.bucket.enabled, database.bucket.maximumFileSize, database.bucket.allowedFileExtensions, database.bucket.compression, database.bucket.encryption, database.bucket.antivirus);
74
+ console.log(`Bucket ${database.bucket.$id} created successfully.`);
75
+ }
76
+ catch (createError) {
77
+ console.error(`Failed to create bucket ${database.bucket.$id}:`, createError);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ };
37
83
  export const wipeDocumentStorage = async (storage, bucketId) => {
38
84
  console.log(`Wiping storage for bucket ID: ${bucketId}`);
39
85
  let moreFiles = true;
@@ -27,6 +27,7 @@ export declare class UtilsController {
27
27
  constructor(currentUserDir: string);
28
28
  init(): Promise<void>;
29
29
  setupMigrationDatabase(): Promise<void>;
30
+ ensureDatabaseConfigBucketsExist(databases?: Models.Database[]): Promise<void>;
30
31
  ensureDatabasesExist(databases?: Models.Database[]): Promise<void>;
31
32
  getDatabasesByIds(ids: string[]): Promise<Models.Database[]>;
32
33
  wipeOtherDatabases(databasesToKeep: Models.Database[]): Promise<void>;
@@ -7,7 +7,7 @@ import { ImportController } from "./migrations/importController.js";
7
7
  import { ImportDataActions } from "./migrations/importDataActions.js";
8
8
  import { setupMigrationDatabase, ensureDatabasesExist, wipeOtherDatabases, } from "./migrations/setupDatabase.js";
9
9
  import { createOrUpdateCollections, wipeDatabase, generateSchemas, fetchAllCollections, } from "./collections/methods.js";
10
- import { backupDatabase, initOrGetBackupStorage, wipeDocumentStorage, } from "./storage/methods.js";
10
+ import { backupDatabase, ensureDatabaseConfigBucketsExist, initOrGetBackupStorage, wipeDocumentStorage, } from "./storage/methods.js";
11
11
  import path from "path";
12
12
  import { converterFunctions, validationRules, } from "appwrite-utils";
13
13
  import { afterImportActions } from "./migrations/afterImportActions.js";
@@ -59,11 +59,20 @@ export class UtilsController {
59
59
  throw new Error("Config not initialized");
60
60
  await setupMigrationDatabase(this.config);
61
61
  }
62
+ async ensureDatabaseConfigBucketsExist(databases = []) {
63
+ await this.init();
64
+ if (!this.storage)
65
+ throw new Error("Storage not initialized");
66
+ if (!this.config)
67
+ throw new Error("Config not initialized");
68
+ await ensureDatabaseConfigBucketsExist(this.storage, this.config, databases);
69
+ }
62
70
  async ensureDatabasesExist(databases) {
63
71
  await this.init();
64
72
  if (!this.config)
65
73
  throw new Error("Config not initialized");
66
74
  await this.setupMigrationDatabase();
75
+ await this.ensureDatabaseConfigBucketsExist(databases);
67
76
  await ensureDatabasesExist(this.config);
68
77
  }
69
78
  async getDatabasesByIds(ids) {
@@ -112,6 +121,8 @@ export class UtilsController {
112
121
  if (!this.database || !this.config)
113
122
  throw new Error("Database or config not initialized");
114
123
  for (const database of databases) {
124
+ if (database.$id === "migrations")
125
+ continue;
115
126
  await this.createOrUpdateCollections(database);
116
127
  }
117
128
  }
@@ -151,6 +162,7 @@ export class UtilsController {
151
162
  throw new Error("Database not initialized");
152
163
  const databases = await fetchAllDatabases(this.database);
153
164
  await this.ensureDatabasesExist(databases);
165
+ await this.ensureDatabaseConfigBucketsExist(databases);
154
166
  await this.createOrUpdateCollectionsForDatabases(databases);
155
167
  }
156
168
  getAppwriteFolderPath() {
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.3",
4
+ "version": "0.9.5",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -110,11 +110,6 @@ export class InteractiveCLI {
110
110
  ): Promise<Models.Database[]> {
111
111
  const choices = databases.map((db) => ({ name: db.name, value: db }));
112
112
 
113
- if (multiSelect) {
114
- choices.unshift({ name: "Select All", value: "ALL" as any });
115
- choices.push({ name: "Clear Selection", value: "CLEAR" as any });
116
- }
117
-
118
113
  const { selectedDatabases } = await inquirer.prompt([
119
114
  {
120
115
  type: multiSelect ? "checkbox" : "list",
@@ -126,18 +121,7 @@ export class InteractiveCLI {
126
121
  },
127
122
  ]);
128
123
 
129
- if (multiSelect) {
130
- if (selectedDatabases.includes("ALL")) {
131
- return databases;
132
- } else if (selectedDatabases.includes("CLEAR")) {
133
- return [];
134
- }
135
- }
136
-
137
- return selectedDatabases.filter(
138
- (db: Models.Database | string): db is Models.Database =>
139
- typeof db !== "string"
140
- );
124
+ return selectedDatabases;
141
125
  }
142
126
 
143
127
  private async selectCollections(
@@ -155,11 +139,6 @@ export class InteractiveCLI {
155
139
  value: collection,
156
140
  }));
157
141
 
158
- if (multiSelect) {
159
- choices.unshift({ name: "Select All", value: "ALL" as any });
160
- choices.push({ name: "Clear Selection", value: "CLEAR" as any });
161
- }
162
-
163
142
  const { selectedCollections } = await inquirer.prompt([
164
143
  {
165
144
  type: multiSelect ? "checkbox" : "list",
@@ -171,19 +150,7 @@ export class InteractiveCLI {
171
150
  },
172
151
  ]);
173
152
 
174
- if (multiSelect) {
175
- if (selectedCollections.includes("ALL")) {
176
- return collections;
177
- } else if (selectedCollections.includes("CLEAR")) {
178
- return [];
179
- }
180
- }
181
-
182
- return selectedCollections.filter(
183
- (
184
- collection: Models.Collection | string
185
- ): collection is Models.Collection => typeof collection !== "string"
186
- );
153
+ return selectedCollections;
187
154
  }
188
155
 
189
156
  private async selectBuckets(
@@ -193,14 +160,9 @@ export class InteractiveCLI {
193
160
  ): Promise<Models.Bucket[]> {
194
161
  const choices = buckets.map((bucket) => ({
195
162
  name: bucket.name,
196
- value: bucket.$id,
163
+ value: bucket,
197
164
  }));
198
165
 
199
- if (multiSelect) {
200
- choices.unshift({ name: "Select All", value: "ALL" });
201
- choices.push({ name: "Clear Selection", value: "CLEAR" });
202
- }
203
-
204
166
  const { selectedBuckets } = await inquirer.prompt([
205
167
  {
206
168
  type: multiSelect ? "checkbox" : "list",
@@ -212,17 +174,7 @@ export class InteractiveCLI {
212
174
  },
213
175
  ]);
214
176
 
215
- if (multiSelect) {
216
- if (selectedBuckets.includes("ALL")) {
217
- return buckets;
218
- } else if (selectedBuckets.includes("CLEAR")) {
219
- return [];
220
- }
221
- }
222
-
223
- return selectedBuckets.map(
224
- (id: string) => buckets.find((bucket) => bucket.$id === id)!
225
- );
177
+ return selectedBuckets;
226
178
  }
227
179
 
228
180
  private async createCollectionConfig(): Promise<void> {
@@ -475,7 +427,7 @@ export class InteractiveCLI {
475
427
 
476
428
  const selectedDatabases = await this.selectDatabases(
477
429
  databases,
478
- "Select databases to synchronize (or select none to synchronize all):"
430
+ "Select databases to synchronize:"
479
431
  );
480
432
 
481
433
  console.log("Configuring storage buckets...");
@@ -501,7 +453,7 @@ export class InteractiveCLI {
501
453
 
502
454
  const selectedDatabases = await this.selectDatabases(
503
455
  databases,
504
- "Select databases to backup (or select none to backup all):"
456
+ "Select databases to backup:"
505
457
  );
506
458
 
507
459
  for (const db of selectedDatabases) {
@@ -521,15 +473,14 @@ export class InteractiveCLI {
521
473
 
522
474
  const selectedDatabases = await this.selectDatabases(
523
475
  databases,
524
- "Select databases to wipe (or select none to skip database wipe):"
476
+ "Select databases to wipe:"
525
477
  );
526
478
 
527
479
  const { selectedStorage } = await inquirer.prompt([
528
480
  {
529
481
  type: "checkbox",
530
482
  name: "selectedStorage",
531
- message:
532
- "Select storage buckets to wipe (or select none to skip storage wipe):",
483
+ message: "Select storage buckets to wipe:",
533
484
  choices: storage.buckets.map((s) => ({ name: s.name, value: s.$id })),
534
485
  },
535
486
  ]);
@@ -592,7 +543,7 @@ export class InteractiveCLI {
592
543
  const dbCollections = await this.selectCollections(
593
544
  db,
594
545
  this.controller!.database,
595
- `Select collections to import data into for database ${db.name} (or select none to import into all):`,
546
+ `Select collections to import data into for database ${db.name}:`,
596
547
  true
597
548
  );
598
549
  selectedCollections = [...selectedCollections, ...dbCollections];
@@ -703,7 +654,7 @@ export class InteractiveCLI {
703
654
  const selectedCollections = await this.selectCollections(
704
655
  fromDb,
705
656
  sourceClient,
706
- "Select collections to transfer (or select none to transfer all):"
657
+ "Select collections to transfer):"
707
658
  );
708
659
 
709
660
  const { transferStorage } = await inquirer.prompt([
@@ -14,7 +14,7 @@ export const setupMigrationDatabase = async (config: AppwriteConfig) => {
14
14
  console.log("Starting Migrations Setup");
15
15
  console.log("---------------------------------");
16
16
  const database = new Databases(config.appwriteClient!);
17
- let db: Models.Database | null = null;
17
+ let db: Models.Database | undefined;
18
18
  const migrationCollectionsSetup = getMigrationCollectionSchemas();
19
19
 
20
20
  try {
@@ -31,36 +31,69 @@ export const setupMigrationDatabase = async (config: AppwriteConfig) => {
31
31
  console.log("Migrations database created");
32
32
  }
33
33
 
34
+ if (!db) {
35
+ console.error("Failed to create or retrieve the migrations database");
36
+ return;
37
+ }
38
+
34
39
  for (const [collectionName, { collection, attributes }] of Object.entries(
35
40
  migrationCollectionsSetup
36
41
  )) {
37
42
  const collectionId = toCamelCase(collectionName);
38
- let collectionFound: Models.Collection | null = null;
43
+ let collectionFound: Models.Collection | undefined;
39
44
  try {
40
45
  collectionFound = await tryAwaitWithRetry(
41
- async () => await database.getCollection(db!.$id, collectionId)
46
+ async () => await database.getCollection(db.$id, collectionId),
47
+ undefined,
48
+ true
42
49
  );
50
+ console.log(`Collection found: ${collectionId}`);
43
51
  } catch (e) {
44
52
  console.log(`Collection not found: ${collectionId}`);
45
- collectionFound = await tryAwaitWithRetry(
46
- async () =>
47
- await database.createCollection(
48
- db!.$id,
49
- collectionId,
50
- collectionName,
51
- undefined,
52
- collection.documentSecurity,
53
- collection.enabled
54
- )
55
- );
53
+ try {
54
+ collectionFound = await tryAwaitWithRetry(
55
+ async () =>
56
+ await database.createCollection(
57
+ db.$id,
58
+ collectionId,
59
+ collectionName,
60
+ undefined,
61
+ collection.documentSecurity,
62
+ collection.enabled
63
+ ),
64
+ undefined,
65
+ true
66
+ );
67
+ console.log(`Collection created: ${collectionId}`);
68
+ } catch (createError) {
69
+ console.error(
70
+ `Failed to create collection: ${collectionId}`,
71
+ createError
72
+ );
73
+ continue;
74
+ }
56
75
  }
76
+
77
+ if (!collectionFound) {
78
+ console.error(`Failed to create or retrieve collection: ${collectionId}`);
79
+ continue;
80
+ }
81
+
57
82
  for (const attribute of attributes) {
58
- await createOrUpdateAttribute(
59
- database,
60
- db!.$id,
61
- collectionFound!,
62
- attribute
63
- );
83
+ try {
84
+ await createOrUpdateAttribute(
85
+ database,
86
+ db.$id,
87
+ collectionFound,
88
+ attribute
89
+ );
90
+ console.log(`Attribute created/updated: ${attribute.key}`);
91
+ } catch (attrError) {
92
+ console.error(
93
+ `Failed to create/update attribute: ${attribute.key}`,
94
+ attrError
95
+ );
96
+ }
64
97
  }
65
98
  }
66
99
  console.log("---------------------------------");
@@ -80,7 +113,7 @@ export const ensureDatabasesExist = async (config: AppwriteConfig) => {
80
113
  (d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations"
81
114
  );
82
115
  if (existingDatabases.databases.length !== 0 && migrationsDatabase) {
83
- console.log("Wiping all databases except migrations");
116
+ console.log("Creating all databases except migrations");
84
117
  databasesToEnsure.push(migrationsDatabase);
85
118
  }
86
119
 
@@ -0,0 +1,25 @@
1
+ import { setupDirsFiles } from "./utils/setupFiles.js";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ export class SetupController {
6
+ private currentDir: string;
7
+
8
+ constructor(currentDir: string) {
9
+ this.currentDir = currentDir;
10
+ }
11
+
12
+ async runSetup(withExampleData: boolean = false): Promise<void> {
13
+ await setupDirsFiles(withExampleData, this.currentDir);
14
+ console.log("Setup completed successfully.");
15
+ }
16
+
17
+ hasExistingConfig(): boolean {
18
+ const configPath = path.join(
19
+ this.currentDir,
20
+ "appwrite",
21
+ "appwriteConfig.ts"
22
+ );
23
+ return fs.existsSync(configPath);
24
+ }
25
+ }
@@ -100,6 +100,71 @@ export const deleteFile = async (
100
100
  return await storage.deleteFile(bucketId, fileId);
101
101
  };
102
102
 
103
+ export const ensureDatabaseConfigBucketsExist = async (
104
+ storage: Storage,
105
+ config: AppwriteConfig,
106
+ databases: Models.Database[] = []
107
+ ) => {
108
+ for (const db of databases) {
109
+ const database = config.databases?.find((d) => d.$id === db.$id);
110
+ if (database?.bucket) {
111
+ try {
112
+ await storage.getBucket(database.bucket.$id);
113
+ console.log(`Bucket ${database.bucket.$id} already exists.`);
114
+ } catch (e) {
115
+ const permissions: string[] = [];
116
+ if (
117
+ database.bucket.permissions &&
118
+ database.bucket.permissions.length > 0
119
+ ) {
120
+ for (const permission of database.bucket.permissions) {
121
+ switch (permission.permission) {
122
+ case "read":
123
+ permissions.push(Permission.read(permission.target));
124
+ break;
125
+ case "create":
126
+ permissions.push(Permission.create(permission.target));
127
+ break;
128
+ case "update":
129
+ permissions.push(Permission.update(permission.target));
130
+ break;
131
+ case "delete":
132
+ permissions.push(Permission.delete(permission.target));
133
+ break;
134
+ case "write":
135
+ permissions.push(Permission.write(permission.target));
136
+ break;
137
+ default:
138
+ console.warn(`Unknown permission: ${permission.permission}`);
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ try {
144
+ await storage.createBucket(
145
+ database.bucket.$id,
146
+ database.bucket.name,
147
+ permissions,
148
+ database.bucket.fileSecurity,
149
+ database.bucket.enabled,
150
+ database.bucket.maximumFileSize,
151
+ database.bucket.allowedFileExtensions,
152
+ database.bucket.compression as Compression,
153
+ database.bucket.encryption,
154
+ database.bucket.antivirus
155
+ );
156
+ console.log(`Bucket ${database.bucket.$id} created successfully.`);
157
+ } catch (createError) {
158
+ console.error(
159
+ `Failed to create bucket ${database.bucket.$id}:`,
160
+ createError
161
+ );
162
+ }
163
+ }
164
+ }
165
+ }
166
+ };
167
+
103
168
  export const wipeDocumentStorage = async (
104
169
  storage: Storage,
105
170
  bucketId: string
@@ -18,6 +18,7 @@ import {
18
18
  } from "./collections/methods.js";
19
19
  import {
20
20
  backupDatabase,
21
+ ensureDatabaseConfigBucketsExist,
21
22
  initOrGetBackupStorage,
22
23
  wipeDocumentStorage,
23
24
  } from "./storage/methods.js";
@@ -101,10 +102,22 @@ export class UtilsController {
101
102
  await setupMigrationDatabase(this.config);
102
103
  }
103
104
 
105
+ async ensureDatabaseConfigBucketsExist(databases: Models.Database[] = []) {
106
+ await this.init();
107
+ if (!this.storage) throw new Error("Storage not initialized");
108
+ if (!this.config) throw new Error("Config not initialized");
109
+ await ensureDatabaseConfigBucketsExist(
110
+ this.storage,
111
+ this.config,
112
+ databases
113
+ );
114
+ }
115
+
104
116
  async ensureDatabasesExist(databases?: Models.Database[]) {
105
117
  await this.init();
106
118
  if (!this.config) throw new Error("Config not initialized");
107
119
  await this.setupMigrationDatabase();
120
+ await this.ensureDatabaseConfigBucketsExist(databases);
108
121
  await ensureDatabasesExist(this.config);
109
122
  }
110
123
 
@@ -161,6 +174,7 @@ export class UtilsController {
161
174
  if (!this.database || !this.config)
162
175
  throw new Error("Database or config not initialized");
163
176
  for (const database of databases) {
177
+ if (database.$id === "migrations") continue;
164
178
  await this.createOrUpdateCollections(database);
165
179
  }
166
180
  }
@@ -230,6 +244,7 @@ export class UtilsController {
230
244
  if (!this.database) throw new Error("Database not initialized");
231
245
  const databases = await fetchAllDatabases(this.database);
232
246
  await this.ensureDatabasesExist(databases);
247
+ await this.ensureDatabaseConfigBucketsExist(databases);
233
248
  await this.createOrUpdateCollectionsForDatabases(databases);
234
249
  }
235
250