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.
- package/README.md +102 -0
- package/dist/collections/attributes.d.ts +8 -0
- package/dist/collections/attributes.js +195 -0
- package/dist/collections/indexes.d.ts +8 -0
- package/dist/collections/indexes.js +150 -0
- package/dist/collections/methods.js +105 -53
- package/dist/interactiveCLI.js +143 -48
- package/dist/migrations/transfer.js +111 -53
- package/package.json +1 -1
- package/src/collections/attributes.ts +339 -0
- package/src/collections/indexes.ts +264 -0
- package/src/collections/methods.ts +175 -87
- package/src/interactiveCLI.ts +146 -48
- package/src/migrations/transfer.ts +228 -121
@@ -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
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
479
|
-
|
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
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
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
|
-
|
517
|
-
|
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
|
-
|
521
|
-
|
522
|
-
|
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
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
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.
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
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,
|
637
|
-
|
731
|
+
phone,
|
732
|
+
tempPassword,
|
638
733
|
user.name
|
639
734
|
)
|
640
735
|
);
|
641
|
-
|
642
|
-
|
643
|
-
|
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 () =>
|