appwrite-utils-cli 1.0.8 → 1.1.0

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.
@@ -13,6 +13,7 @@ import { getAppwriteClient } from "../utils/helperFunctions.js";
13
13
  import {
14
14
  createOrUpdateAttribute,
15
15
  createUpdateCollectionAttributes,
16
+ createUpdateCollectionAttributesWithStatusCheck,
16
17
  } from "../collections/attributes.js";
17
18
  import { parseAttribute } from "appwrite-utils";
18
19
  import chalk from "chalk";
@@ -22,6 +23,7 @@ import { ProgressManager } from "../shared/progressManager.js";
22
23
  import {
23
24
  createOrUpdateIndex,
24
25
  createOrUpdateIndexes,
26
+ createOrUpdateIndexesWithStatusCheck,
25
27
  } from "../collections/indexes.js";
26
28
  import { getClient } from "../utils/getClientFromConfig.js";
27
29
 
@@ -305,44 +307,23 @@ export const transferDatabaseLocalToLocal = async (
305
307
  );
306
308
  }
307
309
 
308
- // Handle attributes
309
- const existingAttributes = await tryAwaitWithRetry(
310
- async () =>
311
- await localDb.listAttributes(targetDbId, targetCollection.$id)
310
+ // Handle attributes with enhanced status checking
311
+ console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
312
+
313
+ const allAttributes = collection.attributes.map(attr => parseAttribute(attr as any));
314
+ const attributeSuccess = await createUpdateCollectionAttributesWithStatusCheck(
315
+ localDb,
316
+ targetDbId,
317
+ targetCollection,
318
+ allAttributes
312
319
  );
313
-
314
- for (const attribute of collection.attributes) {
315
- const parsedAttribute = parseAttribute(attribute as any);
316
- const existingAttribute = existingAttributes.attributes.find(
317
- (attr: any) => attr.key === parsedAttribute.key
318
- );
319
-
320
- if (!existingAttribute) {
321
- await tryAwaitWithRetry(async () =>
322
- createOrUpdateAttribute(
323
- localDb,
324
- targetDbId,
325
- targetCollection,
326
- parsedAttribute
327
- )
328
- );
329
- console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
330
- } else {
331
- console.log(
332
- chalk.blue(
333
- `Attribute ${parsedAttribute.key} exists, checking for updates...`
334
- )
335
- );
336
- await tryAwaitWithRetry(async () =>
337
- createOrUpdateAttribute(
338
- localDb,
339
- targetDbId,
340
- targetCollection,
341
- parsedAttribute
342
- )
343
- );
344
- }
320
+
321
+ if (!attributeSuccess) {
322
+ console.log(chalk.red(`❌ Failed to create all attributes for collection ${collection.name}, skipping to next collection`));
323
+ continue;
345
324
  }
325
+
326
+ console.log(chalk.green(`✅ All attributes created successfully for collection ${collection.name}`));
346
327
 
347
328
  // Handle indexes
348
329
  const existingIndexes = await tryAwaitWithRetry(
@@ -474,73 +455,41 @@ export const transferDatabaseLocalToRemote = async (
474
455
  );
475
456
  }
476
457
 
477
- // Handle attributes
478
- const existingAttributes = await tryAwaitWithRetry(
479
- async () => await remoteDb.listAttributes(toDbId, targetCollection.$id)
458
+ // Handle attributes with enhanced status checking
459
+ console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
460
+
461
+ const attributesToCreate = collection.attributes.map(attr => parseAttribute(attr as any));
462
+
463
+ const attributesSuccess = await createUpdateCollectionAttributesWithStatusCheck(
464
+ remoteDb,
465
+ toDbId,
466
+ targetCollection,
467
+ attributesToCreate
480
468
  );
481
-
482
- for (const attribute of collection.attributes) {
483
- const parsedAttribute = parseAttribute(attribute as any);
484
- const existingAttribute = existingAttributes.attributes.find(
485
- (attr: any) => attr.key === parsedAttribute.key
486
- );
487
-
488
- if (!existingAttribute) {
489
- await tryAwaitWithRetry(async () =>
490
- createOrUpdateAttribute(
491
- remoteDb,
492
- toDbId,
493
- targetCollection,
494
- parsedAttribute
495
- )
496
- );
497
- console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
498
- } else {
499
- console.log(
500
- chalk.blue(
501
- `Attribute ${parsedAttribute.key} exists, checking for updates...`
502
- )
503
- );
504
- await tryAwaitWithRetry(async () =>
505
- createOrUpdateAttribute(
506
- remoteDb,
507
- toDbId,
508
- targetCollection,
509
- parsedAttribute
510
- )
511
- );
512
- }
469
+
470
+ if (!attributesSuccess) {
471
+ console.log(chalk.red(`Failed to create some attributes for collection ${collection.name}`));
472
+ // Continue with the transfer even if some attributes failed
473
+ } else {
474
+ console.log(chalk.green(`All attributes created successfully for collection ${collection.name}`));
513
475
  }
514
476
 
515
- // Handle indexes
516
- const existingIndexes = await tryAwaitWithRetry(
517
- async () => await remoteDb.listIndexes(toDbId, targetCollection.$id)
477
+ // Handle indexes with enhanced status checking
478
+ console.log(chalk.blue(`Creating indexes for collection ${collection.name} with enhanced monitoring...`));
479
+
480
+ const indexesSuccess = await createOrUpdateIndexesWithStatusCheck(
481
+ toDbId,
482
+ remoteDb,
483
+ targetCollection.$id,
484
+ targetCollection,
485
+ collection.indexes as any
518
486
  );
519
-
520
- for (const index of collection.indexes) {
521
- const existingIndex = existingIndexes.indexes.find(
522
- (idx) => idx.key === index.key
523
- );
524
-
525
- if (!existingIndex) {
526
- await createOrUpdateIndex(
527
- toDbId,
528
- remoteDb,
529
- targetCollection.$id,
530
- index as any
531
- );
532
- console.log(chalk.green(`Index ${index.key} created`));
533
- } else {
534
- console.log(
535
- chalk.blue(`Index ${index.key} exists, checking for updates...`)
536
- );
537
- await createOrUpdateIndex(
538
- toDbId,
539
- remoteDb,
540
- targetCollection.$id,
541
- index as any
542
- );
543
- }
487
+
488
+ if (!indexesSuccess) {
489
+ console.log(chalk.red(`Failed to create some indexes for collection ${collection.name}`));
490
+ // Continue with the transfer even if some indexes failed
491
+ } else {
492
+ console.log(chalk.green(`All indexes created successfully for collection ${collection.name}`));
544
493
  }
545
494
 
546
495
  // Transfer documents
@@ -609,41 +558,199 @@ export const transferUsersLocalToRemote = async (
609
558
  ? converterFunctions.convertPhoneStringToUSInternational(user.phone)
610
559
  : undefined;
611
560
 
612
- if (user.hash) {
613
- await tryAwaitWithRetry(async () =>
614
- remoteUsers.createArgon2User(
615
- user.$id,
616
- user.email,
617
- user.password!, // password - cannot transfer hashed passwords
618
- user.name // phone - optional
619
- )
620
- );
621
- if (phone) {
561
+ // Handle user creation based on hash type
562
+ if (user.hash && user.password) {
563
+ // User has a hashed password - recreate with proper hash method
564
+ const hashType = user.hash.toLowerCase();
565
+ const hashedPassword = user.password; // This is already hashed
566
+ const hashOptions = (user.hashOptions as Record<string, any>) || {};
567
+
568
+ try {
569
+ switch (hashType) {
570
+ case 'argon2':
571
+ await tryAwaitWithRetry(async () =>
572
+ remoteUsers.createArgon2User(
573
+ user.$id,
574
+ user.email,
575
+ hashedPassword,
576
+ user.name
577
+ )
578
+ );
579
+ break;
580
+
581
+ case 'bcrypt':
582
+ await tryAwaitWithRetry(async () =>
583
+ remoteUsers.createBcryptUser(
584
+ user.$id,
585
+ user.email,
586
+ hashedPassword,
587
+ user.name
588
+ )
589
+ );
590
+ break;
591
+
592
+ case 'scrypt':
593
+ // Scrypt requires additional parameters from hashOptions
594
+ const salt = typeof hashOptions.salt === 'string' ? hashOptions.salt : '';
595
+ const costCpu = typeof hashOptions.costCpu === 'number' ? hashOptions.costCpu : 32768;
596
+ const costMemory = typeof hashOptions.costMemory === 'number' ? hashOptions.costMemory : 14;
597
+ const costParallel = typeof hashOptions.costParallel === 'number' ? hashOptions.costParallel : 1;
598
+ const length = typeof hashOptions.length === 'number' ? hashOptions.length : 64;
599
+
600
+ // Warn if using default values due to missing hash options
601
+ if (!hashOptions.salt || typeof hashOptions.costCpu !== 'number') {
602
+ console.log(chalk.yellow(`User ${user.$id}: Using default Scrypt parameters due to missing hashOptions`));
603
+ }
604
+
605
+ await tryAwaitWithRetry(async () =>
606
+ remoteUsers.createScryptUser(
607
+ user.$id,
608
+ user.email,
609
+ hashedPassword,
610
+ salt,
611
+ costCpu,
612
+ costMemory,
613
+ costParallel,
614
+ length,
615
+ user.name
616
+ )
617
+ );
618
+ break;
619
+
620
+ case 'scryptmodified':
621
+ // Scrypt Modified (Firebase) requires salt, separator, and signer key
622
+ const modSalt = typeof hashOptions.salt === 'string' ? hashOptions.salt : '';
623
+ const saltSeparator = typeof hashOptions.saltSeparator === 'string' ? hashOptions.saltSeparator : '';
624
+ const signerKey = typeof hashOptions.signerKey === 'string' ? hashOptions.signerKey : '';
625
+
626
+ // Warn if critical parameters are missing
627
+ if (!hashOptions.salt || !hashOptions.saltSeparator || !hashOptions.signerKey) {
628
+ console.log(chalk.yellow(`User ${user.$id}: Missing critical Scrypt Modified parameters in hashOptions`));
629
+ }
630
+
631
+ await tryAwaitWithRetry(async () =>
632
+ remoteUsers.createScryptModifiedUser(
633
+ user.$id,
634
+ user.email,
635
+ hashedPassword,
636
+ modSalt,
637
+ saltSeparator,
638
+ signerKey,
639
+ user.name
640
+ )
641
+ );
642
+ break;
643
+
644
+ case 'md5':
645
+ await tryAwaitWithRetry(async () =>
646
+ remoteUsers.createMD5User(
647
+ user.$id,
648
+ user.email,
649
+ hashedPassword,
650
+ user.name
651
+ )
652
+ );
653
+ break;
654
+
655
+ case 'sha':
656
+ case 'sha1':
657
+ case 'sha256':
658
+ case 'sha512':
659
+ // SHA variants - determine version from hash type
660
+ const getPasswordHashVersion = (hash: string) => {
661
+ switch (hash.toLowerCase()) {
662
+ case 'sha1': return 'sha1' as any;
663
+ case 'sha256': return 'sha256' as any;
664
+ case 'sha512': return 'sha512' as any;
665
+ default: return 'sha256' as any; // Default to SHA256
666
+ }
667
+ };
668
+
669
+ await tryAwaitWithRetry(async () =>
670
+ remoteUsers.createSHAUser(
671
+ user.$id,
672
+ user.email,
673
+ hashedPassword,
674
+ getPasswordHashVersion(hashType),
675
+ user.name
676
+ )
677
+ );
678
+ break;
679
+
680
+ case 'phpass':
681
+ await tryAwaitWithRetry(async () =>
682
+ remoteUsers.createPHPassUser(
683
+ user.$id,
684
+ user.email,
685
+ hashedPassword,
686
+ user.name
687
+ )
688
+ );
689
+ break;
690
+
691
+ default:
692
+ console.log(chalk.yellow(`Unknown hash type '${hashType}' for user ${user.$id}, falling back to Argon2`));
693
+ await tryAwaitWithRetry(async () =>
694
+ remoteUsers.createArgon2User(
695
+ user.$id,
696
+ user.email,
697
+ hashedPassword,
698
+ user.name
699
+ )
700
+ );
701
+ break;
702
+ }
703
+
704
+ console.log(chalk.green(`User ${user.$id} created with preserved ${hashType} password`));
705
+
706
+ } catch (error) {
707
+ console.log(chalk.yellow(`Failed to create user ${user.$id} with ${hashType} hash, trying with temporary password`));
708
+
709
+ // Fallback to creating user with temporary password
622
710
  await tryAwaitWithRetry(async () =>
623
- remoteUsers.updatePhone(user.$id, phone)
624
- );
625
- }
626
- if (user.labels && user.labels.length > 0) {
627
- await tryAwaitWithRetry(async () =>
628
- remoteUsers.updateLabels(user.$id, user.labels)
711
+ remoteUsers.create(
712
+ user.$id,
713
+ user.email,
714
+ phone,
715
+ `changeMe${user.email}`,
716
+ user.name
717
+ )
629
718
  );
719
+
720
+ console.log(chalk.yellow(`User ${user.$id} created with temporary password - password reset required`));
630
721
  }
722
+
631
723
  } else {
724
+ // No hash or password - create with temporary password
725
+ const tempPassword = user.password || `changeMe${user.email}`;
726
+
632
727
  await tryAwaitWithRetry(async () =>
633
728
  remoteUsers.create(
634
729
  user.$id,
635
730
  user.email,
636
- phone, // phone - optional
637
- user.password, // password - cannot transfer hashed passwords
731
+ phone,
732
+ tempPassword,
638
733
  user.name
639
734
  )
640
735
  );
641
- if (user.labels && user.labels.length > 0) {
642
- await tryAwaitWithRetry(async () =>
643
- remoteUsers.updateLabels(user.$id, user.labels)
644
- );
736
+
737
+ if (!user.password) {
738
+ console.log(chalk.yellow(`User ${user.$id} created with temporary password - password reset required`));
645
739
  }
646
740
  }
741
+
742
+ // Update phone, labels, and other attributes
743
+ if (phone) {
744
+ await tryAwaitWithRetry(async () =>
745
+ remoteUsers.updatePhone(user.$id, phone)
746
+ );
747
+ }
748
+
749
+ if (user.labels && user.labels.length > 0) {
750
+ await tryAwaitWithRetry(async () =>
751
+ remoteUsers.updateLabels(user.$id, user.labels)
752
+ );
753
+ }
647
754
 
648
755
  // Update user preferences and status
649
756
  await tryAwaitWithRetry(async () =>