appwrite-utils-cli 0.9.983 → 0.9.990

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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/dist/main.js +58 -44
  3. package/dist/migrations/dataLoader.js +44 -8
  4. package/dist/migrations/importController.js +30 -7
  5. package/dist/migrations/transfer.d.ts +2 -2
  6. package/dist/migrations/transfer.js +14 -2
  7. package/dist/utilsController.d.ts +2 -4
  8. package/dist/utilsController.js +64 -28
  9. package/package.json +1 -1
  10. package/src/main.ts +80 -55
  11. package/src/migrations/dataLoader.ts +65 -25
  12. package/src/migrations/importController.ts +46 -19
  13. package/src/migrations/transfer.ts +18 -8
  14. package/src/utilsController.ts +88 -55
  15. package/zlogs/album.json +0 -4
  16. package/zlogs/announcements.json +0 -397
  17. package/zlogs/announcementscomments.json +0 -36
  18. package/zlogs/articles.json +0 -138
  19. package/zlogs/articlescomments.json +0 -4
  20. package/zlogs/artist.json +0 -4
  21. package/zlogs/businesscategories.json +0 -7097
  22. package/zlogs/contacts.json +0 -517063
  23. package/zlogs/contactscouncils.json +0 -61905
  24. package/zlogs/contactssociallinks.json +0 -13776
  25. package/zlogs/councils.json +0 -5076
  26. package/zlogs/documents.json +0 -917
  27. package/zlogs/emails.json +0 -4
  28. package/zlogs/events.json +0 -132625
  29. package/zlogs/genre.json +0 -4
  30. package/zlogs/knowledgebase.json +0 -333
  31. package/zlogs/knowledgebasecomments.json +0 -4
  32. package/zlogs/linkcategories.json +0 -180
  33. package/zlogs/links.json +0 -4364
  34. package/zlogs/memberrequests.json +0 -83
  35. package/zlogs/memberrequestscomments.json +0 -65
  36. package/zlogs/mergedUserMap.json +0 -1
  37. package/zlogs/oldIdToNewIdPerCollectionMap.json +0 -1
  38. package/zlogs/playlist.json +0 -4
  39. package/zlogs/regions.json +0 -145
  40. package/zlogs/song.json +0 -4
  41. package/zlogs/testimonials.json +0 -335
  42. package/zlogs/useractivity.json +0 -4
  43. package/zlogs/userdata.json +0 -4
  44. package/zlogs/users.json +0 -4
@@ -1086,6 +1086,7 @@ export class DataLoader {
1086
1086
  }
1087
1087
  }
1088
1088
  // Update the attribute mappings with any actions that need to be performed post-import
1089
+ // We added the basePath to get the folder from the filePath
1089
1090
  const mappingsWithActions = this.getAttributeMappingsWithActions(
1090
1091
  importDef.attributeMappings,
1091
1092
  context,
@@ -1247,6 +1248,7 @@ export class DataLoader {
1247
1248
  continue;
1248
1249
  }
1249
1250
  // Update the attribute mappings with any actions that need to be performed post-import
1251
+ // We added the basePath to get the folder from the filePath
1250
1252
  const mappingsWithActions = this.getAttributeMappingsWithActions(
1251
1253
  importDef.attributeMappings,
1252
1254
  context,
@@ -1462,6 +1464,7 @@ export class DataLoader {
1462
1464
  }
1463
1465
 
1464
1466
  // Update the attribute mappings with any actions that need to be performed post-import
1467
+ // We added the basePath to get the folder from the filePath
1465
1468
  const mappingsWithActions = this.getAttributeMappingsWithActions(
1466
1469
  importDef.attributeMappings,
1467
1470
  context,
@@ -1480,36 +1483,41 @@ export class DataLoader {
1480
1483
  transformedData
1481
1484
  );
1482
1485
  itemDataToUpdate.context = context;
1483
-
1486
+
1484
1487
  // Fix: Ensure we properly merge the attribute mappings and their actions
1485
- const mergedAttributeMappings = newImportDef.attributeMappings.map((newMapping) => {
1486
- const existingMapping = itemDataToUpdate.importDef?.attributeMappings.find(
1487
- (m) => m.targetKey === newMapping.targetKey
1488
- );
1489
-
1490
- return {
1491
- ...newMapping,
1492
- postImportActions: [
1493
- ...(existingMapping?.postImportActions || []),
1494
- ...(newMapping.postImportActions || [])
1495
- ]
1496
- };
1497
- });
1498
-
1488
+ const mergedAttributeMappings = newImportDef.attributeMappings.map(
1489
+ (newMapping) => {
1490
+ const existingMapping =
1491
+ itemDataToUpdate.importDef?.attributeMappings.find(
1492
+ (m) => m.targetKey === newMapping.targetKey
1493
+ );
1494
+
1495
+ return {
1496
+ ...newMapping,
1497
+ postImportActions: [
1498
+ ...(existingMapping?.postImportActions || []),
1499
+ ...(newMapping.postImportActions || []),
1500
+ ],
1501
+ };
1502
+ }
1503
+ );
1504
+
1499
1505
  itemDataToUpdate.importDef = {
1500
1506
  ...newImportDef,
1501
- attributeMappings: mergedAttributeMappings
1507
+ attributeMappings: mergedAttributeMappings,
1502
1508
  };
1503
-
1509
+
1504
1510
  // Debug logging
1505
- if (mergedAttributeMappings.some(m => m.postImportActions?.length > 0)) {
1511
+ if (
1512
+ mergedAttributeMappings.some((m) => m.postImportActions?.length > 0)
1513
+ ) {
1506
1514
  logger.info(
1507
1515
  `Post-import actions for ${collection.name}: ${JSON.stringify(
1508
1516
  mergedAttributeMappings
1509
- .filter(m => m.postImportActions?.length > 0)
1510
- .map(m => ({
1517
+ .filter((m) => m.postImportActions?.length > 0)
1518
+ .map((m) => ({
1511
1519
  targetKey: m.targetKey,
1512
- actions: m.postImportActions
1520
+ actions: m.postImportActions,
1513
1521
  })),
1514
1522
  null,
1515
1523
  2
@@ -1614,10 +1622,42 @@ export class DataLoader {
1614
1622
  );
1615
1623
  // Ensure the file path is absolute if it doesn't start with "http"
1616
1624
  if (!mappingFilePath.toLowerCase().startsWith("http")) {
1617
- mappingFilePath = path.resolve(
1618
- this.appwriteFolderPath,
1619
- mappingFilePath
1620
- );
1625
+ // First try the direct path
1626
+ let fullPath = path.resolve(this.appwriteFolderPath, mappingFilePath);
1627
+
1628
+ // If file doesn't exist, search in subdirectories
1629
+ if (!fs.existsSync(fullPath)) {
1630
+ const findFileInDir = (dir: string): string | null => {
1631
+ const files = fs.readdirSync(dir);
1632
+
1633
+ for (const file of files) {
1634
+ const filePath = path.join(dir, file);
1635
+ const stat = fs.statSync(filePath);
1636
+
1637
+ if (stat.isDirectory()) {
1638
+ // Recursively search subdirectories
1639
+ const found = findFileInDir(filePath);
1640
+ if (found) return found;
1641
+ } else if (file === path.basename(mappingFilePath)) {
1642
+ return filePath;
1643
+ }
1644
+ }
1645
+ return null;
1646
+ };
1647
+
1648
+ const foundPath = findFileInDir(this.appwriteFolderPath);
1649
+ if (foundPath) {
1650
+ mappingFilePath = foundPath;
1651
+ } else {
1652
+ logger.warn(
1653
+ `File not found in any subdirectory: ${mappingFilePath}`
1654
+ );
1655
+ // Keep the original resolved path as fallback
1656
+ mappingFilePath = fullPath;
1657
+ }
1658
+ } else {
1659
+ mappingFilePath = fullPath;
1660
+ }
1621
1661
  }
1622
1662
  // Define the after-import action to create a file and update the field
1623
1663
  const afterImportAction = {
@@ -119,27 +119,54 @@ export class ImportController {
119
119
  updatedDb: Models.Database,
120
120
  targetDb: Models.Database
121
121
  ) {
122
- await transferDatabaseLocalToLocal(
123
- this.database,
124
- updatedDb.$id,
125
- targetDb.$id
126
- );
127
-
128
- // Find the corresponding database configs
129
- const updatedDbConfig = this.config.databases.find(
130
- (db) => db.$id === updatedDb.$id
131
- );
132
- const targetDbConfig = this.config.databases.find(
133
- (db) => db.$id === targetDb.$id
134
- );
122
+ if (this.database) {
123
+ await transferDatabaseLocalToLocal(
124
+ this.database,
125
+ updatedDb.$id,
126
+ targetDb.$id
127
+ );
128
+ }
135
129
 
136
- // Transfer database-specific bucket if both databases have a bucket defined
137
- if (updatedDbConfig?.bucket && targetDbConfig?.bucket) {
138
- await transferStorageLocalToLocal(
139
- this.storage,
140
- updatedDbConfig.bucket.$id,
141
- targetDbConfig.bucket.$id
130
+ if (this.storage) {
131
+ // Find the corresponding database configs
132
+ const updatedDbConfig = this.config.databases.find(
133
+ (db) => db.$id === updatedDb.$id
134
+ );
135
+ const targetDbConfig = this.config.databases.find(
136
+ (db) => db.$id === targetDb.$id
142
137
  );
138
+
139
+ const allBuckets = await this.storage.listBuckets([Query.limit(1000)]);
140
+ const bucketsWithDbIdInThem = allBuckets.buckets.filter(bucket => bucket.name.toLowerCase().includes(updatedDb.$id.toLowerCase()));
141
+ const configuredUpdatedBucketId = `${this.config.documentBucketId}_${updatedDb.$id.toLowerCase().trim().replace(" ", "")}`;
142
+ const configuredTargetBucketId = `${this.config.documentBucketId}_${targetDb.$id.toLowerCase().trim().replace(" ", "")}`;
143
+
144
+ let sourceBucketId: string | undefined;
145
+ let targetBucketId: string | undefined;
146
+
147
+ if (bucketsWithDbIdInThem.find(bucket => bucket.$id === configuredUpdatedBucketId)) {
148
+ sourceBucketId = configuredUpdatedBucketId;
149
+ } else if (bucketsWithDbIdInThem.find(bucket => bucket.$id === configuredTargetBucketId)) {
150
+ targetBucketId = configuredTargetBucketId;
151
+ }
152
+
153
+ if (!sourceBucketId) {
154
+ sourceBucketId = updatedDbConfig?.bucket?.$id ||
155
+ bucketsWithDbIdInThem[0]?.$id;
156
+ }
157
+
158
+ if (!targetBucketId) {
159
+ targetBucketId = targetDbConfig?.bucket?.$id ||
160
+ bucketsWithDbIdInThem[0]?.$id;
161
+ }
162
+
163
+ if (sourceBucketId && targetBucketId) {
164
+ await transferStorageLocalToLocal(
165
+ this.storage,
166
+ sourceBucketId,
167
+ targetBucketId
168
+ );
169
+ }
143
170
  }
144
171
  }
145
172
 
@@ -13,8 +13,8 @@ import { createOrUpdateAttribute } from "../collections/attributes.js";
13
13
  import { parseAttribute } from "appwrite-utils";
14
14
 
15
15
  export interface TransferOptions {
16
- fromDb: Models.Database;
17
- targetDb: Models.Database;
16
+ fromDb: Models.Database | undefined;
17
+ targetDb: Models.Database | undefined;
18
18
  isRemote: boolean;
19
19
  collections?: string[];
20
20
  transferEndpoint?: string;
@@ -64,15 +64,20 @@ export const transferStorageLocalToLocal = async (
64
64
  file.name
65
65
  );
66
66
  console.log(`Creating file: ${file.name}`);
67
- tryAwaitWithRetry(
68
- async () =>
67
+ try {
68
+ await tryAwaitWithRetry(
69
+ async () =>
69
70
  await storage.createFile(
70
71
  toBucketId,
71
72
  file.$id,
72
73
  fileToCreate,
73
74
  file.$permissions
74
75
  )
75
- );
76
+ );
77
+ } catch (error: any) {
78
+ // File already exists, so we can skip it
79
+ continue;
80
+ }
76
81
  numberOfFiles++;
77
82
  }
78
83
  } else {
@@ -167,15 +172,20 @@ export const transferStorageLocalToRemote = async (
167
172
  new Uint8Array(fileData),
168
173
  file.name
169
174
  );
170
- await tryAwaitWithRetry(
171
- async () =>
175
+ try {
176
+ await tryAwaitWithRetry(
177
+ async () =>
172
178
  await remoteStorage.createFile(
173
179
  toBucketId,
174
180
  file.$id,
175
181
  fileToCreate,
176
182
  file.$permissions
177
183
  )
178
- );
184
+ );
185
+ } catch (error: any) {
186
+ // File already exists, so we can skip it
187
+ continue;
188
+ }
179
189
  numberOfFiles++;
180
190
  }
181
191
  console.log(
@@ -145,6 +145,7 @@ export class UtilsController {
145
145
  async getDatabasesByIds(ids: string[]) {
146
146
  await this.init();
147
147
  if (!this.database) throw new Error("Database not initialized");
148
+ if (ids.length === 0) return [];
148
149
  const dbs = await this.database.list([
149
150
  Query.limit(500),
150
151
  Query.equal("$id", ids),
@@ -178,10 +179,34 @@ export class UtilsController {
178
179
  );
179
180
  }
180
181
 
181
- async wipeDatabase(database: Models.Database) {
182
+ async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
182
183
  await this.init();
183
184
  if (!this.database) throw new Error("Database not initialized");
184
- return await wipeDatabase(this.database, database.$id);
185
+ await wipeDatabase(this.database, database.$id);
186
+ if (wipeBucket) {
187
+ await this.wipeBucketFromDatabase(database);
188
+ }
189
+ }
190
+
191
+ async wipeBucketFromDatabase(database: Models.Database) {
192
+ // Check configured bucket in database config
193
+ const configuredBucket = this.config?.databases?.find(db => db.$id === database.$id)?.bucket;
194
+ if (configuredBucket?.$id) {
195
+ await this.wipeDocumentStorage(configuredBucket.$id);
196
+ }
197
+
198
+ // Also check for document bucket ID pattern
199
+ if (this.config?.documentBucketId) {
200
+ const documentBucketId = `${this.config.documentBucketId}_${database.$id.toLowerCase().trim().replace(/\s+/g, "")}`;
201
+ try {
202
+ await this.wipeDocumentStorage(documentBucketId);
203
+ } catch (error: any) {
204
+ // Ignore if bucket doesn't exist
205
+ if (error?.type !== 'storage_bucket_not_found') {
206
+ throw error;
207
+ }
208
+ }
209
+ }
185
210
  }
186
211
 
187
212
  async wipeCollection(database: Models.Database, collection: Models.Collection) {
@@ -289,16 +314,11 @@ export class UtilsController {
289
314
  }
290
315
 
291
316
  async transferData(options: TransferOptions): Promise<void> {
292
- if (!this.database) {
293
- throw new Error(
294
- "Database is not initialized, is the config file correct & created?"
295
- );
296
- }
297
-
317
+ // Remove database requirement check
298
318
  let sourceClient = this.database;
299
- let targetClient: Databases;
300
- let sourceDatabases: Models.Database[];
301
- let targetDatabases: Models.Database[];
319
+ let targetClient: Databases | undefined;
320
+ let sourceDatabases: Models.Database[] = [];
321
+ let targetDatabases: Models.Database[] = [];
302
322
 
303
323
  if (options.isRemote) {
304
324
  if (
@@ -314,63 +334,76 @@ export class UtilsController {
314
334
  options.transferProject,
315
335
  options.transferKey
316
336
  );
317
- targetClient = new Databases(remoteClient);
318
-
319
- sourceDatabases = await fetchAllDatabases(sourceClient);
320
- targetDatabases = await fetchAllDatabases(targetClient);
321
- } else {
337
+
338
+ if (this.database) {
339
+ targetClient = new Databases(remoteClient);
340
+ sourceDatabases = await fetchAllDatabases(sourceClient!);
341
+ targetDatabases = await fetchAllDatabases(targetClient);
342
+ }
343
+ } else if (this.database) {
322
344
  targetClient = sourceClient;
323
- sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
345
+ sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient!);
324
346
  }
325
347
 
326
- // Validate that the provided databases exist in the fetched lists
327
- const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb.$id);
328
- const targetDb = targetDatabases.find(
329
- (db) => db.$id === options.targetDb.$id
330
- );
331
-
332
- if (!fromDb || !targetDb) {
333
- throw new Error("Source or target database not found");
334
- }
335
-
336
- if (options.isRemote) {
337
- // Remote transfer
338
- await transferDatabaseLocalToRemote(
339
- sourceClient,
340
- options.transferEndpoint!,
341
- options.transferProject!,
342
- options.transferKey!,
343
- fromDb.$id,
344
- targetDb.$id
348
+ // Only validate databases if they're provided in options
349
+ if (options.fromDb && options.targetDb) {
350
+ const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb!.$id);
351
+ const targetDb = targetDatabases.find(
352
+ (db) => db.$id === options.targetDb!.$id
345
353
  );
346
354
 
347
- if (this.storage && options.sourceBucket && options.targetBucket) {
348
- await transferStorageLocalToRemote(
349
- this.storage,
355
+ if (!fromDb || !targetDb) {
356
+ throw new Error("Source or target database not found");
357
+ }
358
+
359
+ if (options.isRemote && targetClient) {
360
+ await transferDatabaseLocalToRemote(
361
+ sourceClient!,
350
362
  options.transferEndpoint!,
351
363
  options.transferProject!,
352
364
  options.transferKey!,
353
- options.sourceBucket.$id,
354
- options.targetBucket.$id
365
+ fromDb.$id,
366
+ targetDb.$id
367
+ );
368
+ } else if (targetClient) {
369
+ await transferDatabaseLocalToLocal(
370
+ sourceClient!,
371
+ fromDb.$id,
372
+ targetDb.$id
355
373
  );
356
374
  }
357
- } else {
358
- // Local transfer
359
- await transferDatabaseLocalToLocal(
360
- sourceClient,
361
- fromDb.$id,
362
- targetDb.$id
363
- );
375
+ }
364
376
 
365
- if (this.storage && options.sourceBucket && options.targetBucket) {
366
- await transferStorageLocalToLocal(
367
- this.storage,
368
- options.sourceBucket.$id,
369
- options.targetBucket.$id
370
- );
377
+ // Handle storage transfer separately
378
+ if (this.storage && (options.sourceBucket || options.fromDb)) {
379
+ const sourceBucketId = options.sourceBucket?.$id ||
380
+ (options.fromDb && this.config?.documentBucketId &&
381
+ `${this.config.documentBucketId}_${options.fromDb.$id.toLowerCase().trim().replace(" ", "")}`);
382
+
383
+ const targetBucketId = options.targetBucket?.$id ||
384
+ (options.targetDb && this.config?.documentBucketId &&
385
+ `${this.config.documentBucketId}_${options.targetDb.$id.toLowerCase().trim().replace(" ", "")}`);
386
+
387
+ if (sourceBucketId && targetBucketId) {
388
+ if (options.isRemote) {
389
+ await transferStorageLocalToRemote(
390
+ this.storage,
391
+ options.transferEndpoint!,
392
+ options.transferProject!,
393
+ options.transferKey!,
394
+ sourceBucketId,
395
+ targetBucketId
396
+ );
397
+ } else {
398
+ await transferStorageLocalToLocal(
399
+ this.storage,
400
+ sourceBucketId,
401
+ targetBucketId
402
+ );
403
+ }
371
404
  }
372
405
  }
373
406
 
374
- console.log("Data transfer completed");
407
+ console.log("Transfer completed");
375
408
  }
376
409
  }
package/zlogs/album.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "collection": "album",
3
- "data": []
4
- }