appwrite-utils-cli 0.0.32 → 0.0.33

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/src/main.ts CHANGED
@@ -1,83 +1,83 @@
1
- #!/usr/bin/env node
2
- import { program } from "commander";
3
- import { UtilsController } from "./utilsController.js";
4
-
5
- // Setup the main CLI program
6
- program
7
- .version("1.0.0")
8
- .description("Utility CLI for Appwrite configurations and operations")
9
- .option("--endpoint <endpoint>", "Set the Appwrite endpoint", undefined)
10
- .option("--project <project>", "Set the Appwrite project ID", undefined)
11
- .option("--key <key>", "Set the Appwrite API key", undefined)
12
- .option("--backup", "Perform a backup before executing the command", false)
13
- .option("--dev", "Run in development environment", false)
14
- .option("--prod", "Run in production environment", false)
15
- .option("--staging", "Run in staging environment", false)
16
- .option("--sync", "Synchronize configurations", false)
17
- .option("--wipe", "Wipe databases", false)
18
- .option("--wipe-docs", "Wipe documents", false)
19
- .option("--wipe-users", "Wipe users", false)
20
- .option("--generate", "Generate schemas", false)
21
- .option("--import", "Import data", false)
22
- .option("--write-data", "Write data to file", false)
23
- .option("-h, --help", "Display help for command", false);
24
-
25
- program.on("--help", () => {
26
- console.log("");
27
- console.log("Examples:");
28
- console.log(
29
- " $ npx appwrite-utils-cli appwrite-migrate --sync --endpoint https://appwrite.example.com --project 123456 --key 7890"
30
- );
31
- console.log(
32
- " $ npx appwrite-utils-cli appwrite-migrate --sync --dev --backup"
33
- );
34
- console.log(
35
- " $ npx appwrite-utils-cli appwrite-migrate --wipe --wipe-docs --wipe-users --dev"
36
- );
37
- console.log(
38
- " $ npx appwrite-utils-cli appwrite-migrate --generate --import --write-data --dev"
39
- );
40
- console.log(
41
- " $ npx appwrite-utils-cli appwrite-migrate --sync --generate --import --write-data --dev --backup"
42
- );
43
- console.log(" $ npx appwrite-utils-cli appwrite-create");
44
- console.log(
45
- "For more information, visit https://github.com/zachhandley/appwrite-utils"
46
- );
47
- console.log("");
48
- });
49
-
50
- // Parse and handle options
51
- program.action(async (options) => {
52
- const currentUserDir = process.cwd();
53
- const controller = new UtilsController(currentUserDir);
54
- try {
55
- // Convert Commander options to the format expected by UtilsController
56
- const setupOptions = {
57
- sync: options.sync,
58
- runProd: options.prod,
59
- runStaging: options.staging,
60
- runDev: options.dev,
61
- doBackup: options.backup,
62
- wipeDatabases: options.wipe,
63
- wipeDocumentStorage: options.wipeDocs,
64
- wipeUsers: options.wipeUsers,
65
- generateSchemas: options.generate,
66
- generateMockData: false, // Assuming this needs to be set based on other conditions
67
- importData: options.import,
68
- checkDuplicates: false, // Assuming this needs to be set based on other conditions
69
- shouldWriteFile: options.writeData,
70
- endpoint: options.endpoint,
71
- project: options.project,
72
- key: options.key,
73
- };
74
- console.log("Running operation...", setupOptions);
75
-
76
- await controller.run(setupOptions);
77
- console.log("Operation completed successfully.");
78
- } catch (error) {
79
- console.error("Error during operation:", error);
80
- }
81
- });
82
-
83
- program.parse(process.argv);
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { UtilsController } from "./utilsController.js";
4
+
5
+ // Setup the main CLI program
6
+ program
7
+ .version("1.0.0")
8
+ .description("Utility CLI for Appwrite configurations and operations")
9
+ .option("--endpoint <endpoint>", "Set the Appwrite endpoint", undefined)
10
+ .option("--project <project>", "Set the Appwrite project ID", undefined)
11
+ .option("--key <key>", "Set the Appwrite API key", undefined)
12
+ .option("--backup", "Perform a backup before executing the command", false)
13
+ .option("--dev", "Run in development environment", false)
14
+ .option("--prod", "Run in production environment", false)
15
+ .option("--staging", "Run in staging environment", false)
16
+ .option("--sync", "Synchronize configurations", false)
17
+ .option("--wipe", "Wipe databases", false)
18
+ .option("--wipe-docs", "Wipe documents", false)
19
+ .option("--wipe-users", "Wipe users", false)
20
+ .option("--generate", "Generate schemas", false)
21
+ .option("--import", "Import data", false)
22
+ .option("--write-data", "Write data to file", false)
23
+ .option("-h, --help", "Display help for command", false);
24
+
25
+ program.on("--help", () => {
26
+ console.log("");
27
+ console.log("Examples:");
28
+ console.log(
29
+ " $ npx appwrite-utils-cli appwrite-migrate --sync --endpoint https://appwrite.example.com --project 123456 --key 7890"
30
+ );
31
+ console.log(
32
+ " $ npx appwrite-utils-cli appwrite-migrate --sync --dev --backup"
33
+ );
34
+ console.log(
35
+ " $ npx appwrite-utils-cli appwrite-migrate --wipe --wipe-docs --wipe-users --dev"
36
+ );
37
+ console.log(
38
+ " $ npx appwrite-utils-cli appwrite-migrate --generate --import --write-data --dev"
39
+ );
40
+ console.log(
41
+ " $ npx appwrite-utils-cli appwrite-migrate --sync --generate --import --write-data --dev --backup"
42
+ );
43
+ console.log(" $ npx appwrite-utils-cli appwrite-create");
44
+ console.log(
45
+ "For more information, visit https://github.com/zachhandley/appwrite-utils"
46
+ );
47
+ console.log("");
48
+ });
49
+
50
+ // Parse and handle options
51
+ program.action(async (options) => {
52
+ const currentUserDir = process.cwd();
53
+ const controller = new UtilsController(currentUserDir);
54
+ try {
55
+ // Convert Commander options to the format expected by UtilsController
56
+ const setupOptions = {
57
+ sync: options.sync,
58
+ runProd: options.prod,
59
+ runStaging: options.staging,
60
+ runDev: options.dev,
61
+ doBackup: options.backup,
62
+ wipeDatabases: options.wipe,
63
+ wipeDocumentStorage: options.wipeDocs,
64
+ wipeUsers: options.wipeUsers,
65
+ generateSchemas: options.generate,
66
+ generateMockData: false, // Assuming this needs to be set based on other conditions
67
+ importData: options.import,
68
+ checkDuplicates: false, // Assuming this needs to be set based on other conditions
69
+ shouldWriteFile: options.writeData,
70
+ endpoint: options.endpoint,
71
+ project: options.project,
72
+ key: options.key,
73
+ };
74
+ console.log("Running operation...", setupOptions);
75
+
76
+ await controller.run(setupOptions);
77
+ console.log("Operation completed successfully.");
78
+ } catch (error) {
79
+ console.error("Error during operation:", error);
80
+ }
81
+ });
82
+
83
+ program.parse(process.argv);
@@ -298,6 +298,10 @@ export class DataLoader {
298
298
  let isUsersCollection =
299
299
  this.getCollectionKey(this.config.usersCollectionName) ===
300
300
  this.getCollectionKey(collection.name);
301
+ const collectionDefs = collection.importDefs;
302
+ if (!collectionDefs || !collectionDefs.length) {
303
+ continue;
304
+ }
301
305
  // Process create and update definitions for the collection
302
306
  const createDefs = collection.importDefs.filter(
303
307
  (def: ImportDef) => def.type === "create" || !def.type
@@ -331,8 +335,8 @@ export class DataLoader {
331
335
  }
332
336
  }
333
337
  console.log("Running update references");
334
- await this.dealWithMergedUsers();
335
- await this.updateOldReferencesForNew();
338
+ this.dealWithMergedUsers();
339
+ this.updateOldReferencesForNew();
336
340
  console.log("Done running update references");
337
341
  }
338
342
  // for (const collection of this.config.collections) {
@@ -346,7 +350,7 @@ export class DataLoader {
346
350
  }
347
351
  }
348
352
 
349
- async dealWithMergedUsers() {
353
+ dealWithMergedUsers() {
350
354
  const usersCollectionKey = this.getCollectionKey(
351
355
  this.config.usersCollectionName
352
356
  );
@@ -357,7 +361,11 @@ export class DataLoader {
357
361
  // Collect primary key fields from the users collection definitions
358
362
  this.config.collections.forEach((collection) => {
359
363
  if (this.getCollectionKey(collection.name) === usersCollectionKey) {
360
- collection.importDefs.forEach((importDef) => {
364
+ const collectionImportDefs = collection.importDefs;
365
+ if (!collectionImportDefs || !collectionImportDefs.length) {
366
+ return;
367
+ }
368
+ collectionImportDefs.forEach((importDef) => {
361
369
  if (importDef.primaryKeyField) {
362
370
  usersCollectionPrimaryKeyFields.add(importDef.primaryKeyField);
363
371
  }
@@ -371,14 +379,19 @@ export class DataLoader {
371
379
  this.getCollectionKey(collection.name)
372
380
  );
373
381
  if (!collectionData || !collectionData.data) return;
374
-
375
- collection.importDefs.forEach((importDef) => {
382
+ const collectionImportDefs = collection.importDefs;
383
+ if (!collectionImportDefs || !collectionImportDefs.length) {
384
+ return;
385
+ }
386
+ collectionImportDefs.forEach((importDef) => {
376
387
  importDef.idMappings?.forEach((idMapping) => {
377
388
  if (
378
389
  this.getCollectionKey(idMapping.targetCollection) ===
379
390
  usersCollectionKey
380
391
  ) {
381
- if (usersCollectionPrimaryKeyFields.has(idMapping.targetField)) {
392
+ const targetFieldKey =
393
+ idMapping.targetFieldToMatch || idMapping.targetField;
394
+ if (usersCollectionPrimaryKeyFields.has(targetFieldKey)) {
382
395
  // Process each item in the collection
383
396
  collectionData.data.forEach((item) => {
384
397
  const oldId = item.context[idMapping.sourceField];
@@ -386,8 +399,7 @@ export class DataLoader {
386
399
 
387
400
  if (newId) {
388
401
  // Update context to use new user ID
389
- item.context[idMapping.fieldToSet || idMapping.targetField] =
390
- newId;
402
+ item.context[idMapping.fieldToSet || targetFieldKey] = newId;
391
403
  }
392
404
  });
393
405
  }
@@ -401,6 +413,7 @@ export class DataLoader {
401
413
  if (!this.config.collections) {
402
414
  return;
403
415
  }
416
+
404
417
  for (const collectionConfig of this.config.collections) {
405
418
  const collectionKey = this.getCollectionKey(collectionConfig.name);
406
419
  const collectionData = this.importMap.get(collectionKey);
@@ -424,29 +437,41 @@ export class DataLoader {
424
437
  );
425
438
  const fieldToSetKey =
426
439
  idMapping.fieldToSet || idMapping.sourceField;
440
+ const targetFieldKey =
441
+ idMapping.targetFieldToMatch || idMapping.targetField;
427
442
  const valueToMatch =
428
443
  collectionData.data[i].context[idMapping.sourceField];
429
444
 
445
+ // Skip if value to match is missing or empty
430
446
  if (!valueToMatch || _.isEmpty(valueToMatch)) continue;
431
447
 
448
+ const isFieldToSetArray = collectionConfig.attributes.find(
449
+ (attribute) => attribute.key === fieldToSetKey
450
+ )?.array;
451
+
432
452
  const targetCollectionData =
433
453
  this.importMap.get(targetCollectionKey);
434
454
  if (!targetCollectionData || !targetCollectionData.data)
435
455
  continue;
436
456
 
437
- const foundData = targetCollectionData.data.filter((data) => {
438
- const targetValue = data.context[idMapping.targetField];
439
- const isMatch = `${targetValue}` === `${valueToMatch}`;
440
- // Debugging output to understand what's being compared
441
- logger.warn(
442
- `Comparing target: ${targetValue} with match: ${valueToMatch} - Result: ${isMatch}`
443
- );
444
- return isMatch;
445
- });
457
+ // Find matching data in the target collection
458
+ const foundData = targetCollectionData.data.filter(
459
+ ({ context }) => {
460
+ const targetValue = context[targetFieldKey];
461
+ const isMatch = `${targetValue}` === `${valueToMatch}`;
462
+ // Ensure the targetValue is defined and not null
463
+ return (
464
+ isMatch &&
465
+ targetValue !== undefined &&
466
+ targetValue !== null
467
+ );
468
+ }
469
+ );
446
470
 
471
+ // Log and skip if no matching data found
447
472
  if (!foundData.length) {
448
473
  console.log(
449
- `No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey}`
474
+ `No data found for collection ${collectionConfig.name}:\nTarget collection: ${targetCollectionKey}\nValue to match: ${valueToMatch}\nField to set: ${fieldToSetKey}\nTarget field to match: ${targetFieldKey}\nTarget field value: ${idMapping.targetField}`
450
475
  );
451
476
  logger.error(
452
477
  `No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(
@@ -460,143 +485,71 @@ export class DataLoader {
460
485
 
461
486
  needsUpdate = true;
462
487
 
463
- // Properly handle arrays and non-arrays
464
- if (
465
- Array.isArray(collectionData.data[i].finalData[fieldToSetKey])
466
- ) {
467
- collectionData.data[i].finalData[fieldToSetKey] =
468
- foundData.map((data) => data.finalData);
469
- } else {
470
- collectionData.data[i].finalData[fieldToSetKey] =
471
- foundData[0].finalData;
472
- }
473
- }
474
- }
475
- }
476
- }
477
- }
478
-
479
- if (needsUpdate) {
480
- this.importMap.set(collectionKey, collectionData);
481
- }
482
- }
483
- }
484
-
485
- async updateReferencesInRelatedCollections() {
486
- if (!this.config.collections) {
487
- return;
488
- }
489
- // Iterate over each collection configuration
490
- for (const collectionConfig of this.config.collections) {
491
- const collectionKey = this.getCollectionKey(collectionConfig.name);
492
- const collectionData = this.importMap.get(collectionKey);
493
-
494
- if (!collectionData || !collectionData.data) continue;
495
-
496
- console.log(
497
- `Updating references for collection: ${collectionConfig.name}`
498
- );
488
+ const getCurrentDataFiltered = (currentData: any) => {
489
+ if (Array.isArray(currentData.finalData[fieldToSetKey])) {
490
+ return currentData.finalData[fieldToSetKey].filter(
491
+ (data: any) => `${data}` !== `${valueToMatch}`
492
+ );
493
+ }
494
+ return currentData.finalData[fieldToSetKey];
495
+ };
499
496
 
500
- // Iterate over each data item in the current collection
501
- for (const item of collectionData.data) {
502
- let needsUpdate = false;
497
+ // Get the current data to be updated
498
+ const currentDataFiltered = getCurrentDataFiltered(
499
+ collectionData.data[i]
500
+ );
503
501
 
504
- // Check if the current collection has import definitions with idMappings
505
- if (collectionConfig.importDefs) {
506
- for (const importDef of collectionConfig.importDefs) {
507
- if (importDef.idMappings) {
508
- // Iterate over each idMapping defined for the current import definition
509
- for (const idMapping of importDef.idMappings) {
510
- const oldIds = Array.isArray(
511
- item.context[idMapping.sourceField]
512
- )
513
- ? item.context[idMapping.sourceField]
514
- : [item.context[idMapping.sourceField]];
515
- const resolvedNewIds: string[] = [];
516
-
517
- oldIds.forEach((oldId: any) => {
518
- // Attempt to find a new ID for the old ID
519
- let newIdForOldId = this.findNewIdForOldId(
520
- oldId,
521
- idMapping,
522
- importDef
523
- );
502
+ // Extract the new data to set
503
+ const newData = foundData.map(
504
+ (data) => data.context[idMapping.targetField]
505
+ );
524
506
 
525
- if (
526
- newIdForOldId &&
527
- !resolvedNewIds.includes(newIdForOldId)
528
- ) {
529
- resolvedNewIds.push(newIdForOldId);
507
+ // Handle cases where current data is an array
508
+ if (isFieldToSetArray) {
509
+ if (!currentDataFiltered) {
510
+ // Set new data if current data is undefined
511
+ collectionData.data[i].finalData[fieldToSetKey] =
512
+ Array.isArray(newData) ? newData : [newData];
530
513
  } else {
531
- logger.error(
532
- `No new ID found for old ID ${oldId} in collection ${collectionConfig.name}`
533
- );
514
+ // Merge arrays if new data is non-empty array and filter for uniqueness
515
+ collectionData.data[i].finalData[fieldToSetKey] = [
516
+ ...new Set(
517
+ [...currentDataFiltered, ...newData].filter(
518
+ (value: any) => `${value}` !== `${valueToMatch}`
519
+ )
520
+ ),
521
+ ];
522
+ }
523
+ } else {
524
+ if (!currentDataFiltered) {
525
+ // Set new data if current data is undefined
526
+ collectionData.data[i].finalData[fieldToSetKey] =
527
+ Array.isArray(newData) ? newData : [newData];
528
+ } else if (Array.isArray(newData) && newData.length > 0) {
529
+ // Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
530
+ collectionData.data[i].finalData[fieldToSetKey] = [
531
+ ...new Set(
532
+ [currentDataFiltered, ...newData].filter(
533
+ (value: any) => `${value}` !== `${valueToMatch}`
534
+ )
535
+ ),
536
+ ];
537
+ } else if (!Array.isArray(newData) && newData !== undefined) {
538
+ // Simply update the field if new data is not an array and defined
539
+ collectionData.data[i].finalData[fieldToSetKey] = newData;
534
540
  }
535
- });
536
-
537
- if (resolvedNewIds.length) {
538
- const targetField =
539
- idMapping.fieldToSet || idMapping.targetField;
540
- const isArray = collectionConfig.attributes.some(
541
- (attribute) =>
542
- attribute.key === targetField && attribute.array
543
- );
544
-
545
- // Set the target field based on whether it's an array or single value
546
- item.finalData[targetField] = isArray
547
- ? resolvedNewIds
548
- : resolvedNewIds[0];
549
- needsUpdate = true;
550
541
  }
551
542
  }
552
543
  }
553
544
  }
554
545
  }
555
-
556
- // Update the importMap if changes were made to the item
557
- if (needsUpdate) {
558
- this.importMap.set(collectionKey, collectionData);
559
- logger.info(
560
- `Updated item: ${JSON.stringify(item.finalData, undefined, 2)}`
561
- );
562
- }
563
546
  }
564
- }
565
- }
566
547
 
567
- findNewIdForOldId(oldId: string, idMapping: IdMapping, importDef: ImportDef) {
568
- // First, check if this ID mapping is related to the users collection.
569
- const targetCollectionKey = this.getCollectionKey(
570
- idMapping.targetCollection
571
- );
572
- const isUsersCollection =
573
- targetCollectionKey ===
574
- this.getCollectionKey(this.config.usersCollectionName);
575
-
576
- // If handling users, check the mergedUserMap for any existing new ID.
577
- if (isUsersCollection) {
578
- for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
579
- if (oldIds.includes(oldId)) {
580
- return newUserId;
581
- }
582
- }
583
- }
584
-
585
- // If not a user or no merged ID found, check the regular ID mapping from old to new.
586
- const targetCollectionData = this.importMap.get(targetCollectionKey);
587
- if (targetCollectionData) {
588
- const foundEntry = targetCollectionData.data.find(
589
- (entry) => entry.context[importDef.primaryKeyField] === oldId
590
- );
591
- if (foundEntry) {
592
- return foundEntry.context.docId; // Assuming `docId` stores the new ID after import
548
+ // Update the import map if any changes were made
549
+ if (needsUpdate) {
550
+ this.importMap.set(collectionKey, collectionData);
593
551
  }
594
552
  }
595
-
596
- logger.error(
597
- `No corresponding new ID found for ${oldId} in ${targetCollectionKey}`
598
- );
599
- return null; // Return null if no new ID is found
600
553
  }
601
554
 
602
555
  private writeMapsToJsonFile() {
@@ -808,14 +761,14 @@ export class DataLoader {
808
761
  if (!existingId) {
809
762
  // No existing user ID, generate a new unique ID
810
763
  existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
811
- transformedItem.userId = existingId; // Assign the new ID to the transformed data's userId field
764
+ transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
812
765
  }
813
766
 
814
767
  // Create a context object for the item, including the new ID
815
768
  let context = this.createContext(db, collection, item, existingId);
816
769
 
817
770
  // Merge the transformed data into the context
818
- context = { ...context, ...transformedItem };
771
+ context = { ...context, ...transformedItem, ...userData.finalData };
819
772
 
820
773
  // If a primary key field is defined, handle the ID mapping and check for duplicates
821
774
  if (importDef.primaryKeyField) {