appwrite-utils-cli 1.0.7 → 1.0.9

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
@@ -327,6 +327,122 @@ This updated CLI ensures that developers have robust tools at their fingertips t
327
327
 
328
328
  ## Changelog
329
329
 
330
+ ### 1.0.9 - Enhanced User Transfer with Password Preservation
331
+
332
+ **🔐 Complete Password Hash Preservation During User Transfers**
333
+
334
+ #### Password Hash Support
335
+ - **Universal Hash Support**: Support for all Appwrite password hash types:
336
+ - **Argon2**: Modern default hashing (preserved)
337
+ - **Bcrypt**: Industry standard (preserved)
338
+ - **Scrypt**: Memory-hard function with custom parameters (preserved)
339
+ - **Scrypt Modified**: Firebase-style with salt/separator/signer (preserved)
340
+ - **MD5**: Legacy support (preserved)
341
+ - **SHA variants**: SHA1, SHA256, SHA512 (preserved)
342
+ - **PHPass**: WordPress-style hashing (preserved)
343
+ - **Dynamic Hash Detection**: Automatically detects and uses correct hash creation method
344
+ - **Parameter Preservation**: Maintains hash-specific parameters (salt, iterations, memory cost, etc.)
345
+
346
+ #### Enhanced User Transfer Logic
347
+ - **Smart Password Recreation**: Uses appropriate `create*User` method based on detected hash type
348
+ - **Fallback Mechanism**: Graceful fallback to temporary passwords if hash recreation fails
349
+ - **Hash Options Support**: Preserves algorithm-specific configuration from `hashOptions`
350
+ - **Detailed Logging**: Clear success/failure messages with hash type information
351
+
352
+ #### User Experience Improvements
353
+ - **Accurate Information**: Updated CLI messaging to reflect actual password preservation capabilities
354
+ - **Clear Expectations**: Distinguishes between users who keep passwords vs. those who need reset
355
+ - **Success Feedback**: Detailed reporting of password preservation success rate
356
+ - **Risk Assessment**: Proper warnings only for users who will lose passwords
357
+
358
+ #### Technical Implementation
359
+ - **Hash Type Detection**: `user.hash` field determines creation method
360
+ - **Configuration Parsing**: `user.hashOptions` provides algorithm parameters
361
+ - **Error Resilience**: Comprehensive try-catch with fallback to temporary passwords
362
+ - **Type Safety**: Proper handling of hash option types and parameters
363
+
364
+ #### Migration Benefits
365
+ - **Seamless Login**: Users with preserved hashes can immediately log in with original passwords
366
+ - **Reduced Support**: Dramatically fewer password reset requests after migration
367
+ - **Complete Fidelity**: Maintains original security posture and hash strength
368
+ - **Production Ready**: Safe for live user base migrations
369
+
370
+ #### Usage Examples
371
+ ```bash
372
+ # Users will now preserve passwords during comprehensive transfer
373
+ npx appwrite-utils-cli@latest appwrite-migrate --it
374
+ # Select: 🚀 Comprehensive transfer (users → databases → buckets → functions)
375
+
376
+ # Example output:
377
+ # ✅ User 123 created with preserved argon2 password
378
+ # ✅ User 456 created with preserved bcrypt password
379
+ # ⚠️ User 789 created with temporary password - password reset required
380
+ ```
381
+
382
+ **Breaking Change**: None - fully backward compatible with enhanced capabilities.
383
+
384
+ ### 1.0.8 - Comprehensive Transfer System with Enhanced Rate Limiting
385
+
386
+ **🚀 Complete Cross-Instance Transfer Solution**
387
+
388
+ #### Comprehensive Transfer System
389
+ - **New CLI Option**: `🚀 Comprehensive transfer (users → databases → buckets → functions)` in interactive mode
390
+ - **Orchestrated Transfer Flow**: Proper execution order (users → databases → buckets → functions) for dependency management
391
+ - **Cross-Instance Support**: Transfer entire Appwrite configurations between different instances/projects
392
+ - **Selective Transfer**: Choose which components to transfer (users, databases, buckets, functions)
393
+ - **Dry Run Mode**: Test transfers without making actual changes
394
+
395
+ #### Enhanced Rate Limiting Strategy
396
+ - **Configurable Limits**: 5 to 100 concurrent operations in steps of 5
397
+ - **Differentiated Rates**: Smart rate limiting based on operation type:
398
+ - **General Operations**: Full rate (databases, functions)
399
+ - **User Operations**: Half rate (more sensitive operations)
400
+ - **File Operations**: Quarter rate (most bandwidth intensive)
401
+ - **Visual Feedback**: Real-time rate limit display during transfers
402
+ - **Intelligent Scaling**: Automatic calculation of optimal rates for different operations
403
+
404
+ #### File Transfer Enhancements
405
+ - **File Validation**: Comprehensive integrity checking (empty files, size limits)
406
+ - **Retry Logic**: Exponential backoff for failed file transfers
407
+ - **Error Handling**: Graceful handling of corrupt/invalid files
408
+ - **Progress Tracking**: Real-time progress for large file transfers
409
+
410
+ #### Function Transfer Integration
411
+ - **Automated Function Migration**: Download from source, redeploy to target
412
+ - **Temporary Management**: Automatic cleanup of downloaded function code
413
+ - **Existing Code Integration**: Leverages existing deployment infrastructure
414
+ - **Configuration Preservation**: Maintains function settings and variables
415
+
416
+ #### User Experience Improvements
417
+ - **Password Reset Warnings**: Clear notifications about Appwrite password limitations
418
+ - **Interactive Configuration**: Step-by-step prompts for source/target setup
419
+ - **Comprehensive Reporting**: Detailed transfer summaries with statistics
420
+ - **Smart Confirmations**: Risk-based confirmations for destructive operations
421
+
422
+ #### Technical Implementation
423
+ - **Rate Limiting**: Uses p-limit for concurrent operation control
424
+ - **Error Resilience**: Robust error handling with detailed user feedback
425
+ - **Memory Management**: Efficient processing of large datasets
426
+ - **Progress Tracking**: Real-time progress bars with ETA calculations
427
+
428
+ #### Usage Examples
429
+ ```bash
430
+ # Interactive mode - select comprehensive transfer
431
+ npx appwrite-utils-cli@latest appwrite-migrate --it
432
+
433
+ # Example rate limiting at 20 concurrent:
434
+ # - General operations: 20 concurrent
435
+ # - User operations: 10 concurrent
436
+ # - File operations: 5 concurrent
437
+ ```
438
+
439
+ **Benefits**:
440
+ - Complete Appwrite instance migration capability
441
+ - Intelligent rate limiting prevents API throttling
442
+ - Enhanced file transfer reliability
443
+ - Comprehensive progress tracking and reporting
444
+ - Maintains data integrity across transfers
445
+
330
446
  ### 1.0.7 - Forgot to remove debug logs
331
447
 
332
448
  ### 1.0.6 - Cross-Language Constants Generation
@@ -34,4 +34,5 @@ export declare class InteractiveCLI {
34
34
  private detectConfigurationType;
35
35
  private buildChoicesList;
36
36
  private migrateTypeScriptConfig;
37
+ private comprehensiveTransfer;
37
38
  }
@@ -6,6 +6,7 @@ import { fetchAllCollections } from "./collections/methods.js";
6
6
  import { listBuckets, createBucket } from "./storage/methods.js";
7
7
  import { Databases, Storage, Client, Compression, Query, Functions, } from "node-appwrite";
8
8
  import { getClient } from "./utils/getClientFromConfig.js";
9
+ import { ComprehensiveTransfer } from "./migrations/comprehensiveTransfer.js";
9
10
  import { AppwriteFunctionSchema, parseAttribute, PermissionToAppwritePermission, RuntimeSchema, permissionSchema, } from "appwrite-utils";
10
11
  import { ulid } from "ulidx";
11
12
  import chalk from "chalk";
@@ -33,6 +34,7 @@ var CHOICES;
33
34
  CHOICES["SYNC_DB"] = "\u2B06\uFE0F Push local config to Appwrite";
34
35
  CHOICES["SYNCHRONIZE_CONFIGURATIONS"] = "\uD83D\uDD04 Synchronize configurations - Pull from Appwrite and write to local config";
35
36
  CHOICES["TRANSFER_DATA"] = "\uD83D\uDCE6 Transfer data";
37
+ CHOICES["COMPREHENSIVE_TRANSFER"] = "\uD83D\uDE80 Comprehensive transfer (users \u2192 databases \u2192 buckets \u2192 functions)";
36
38
  CHOICES["BACKUP_DATABASE"] = "\uD83D\uDCBE Backup database";
37
39
  CHOICES["WIPE_DATABASE"] = "\uD83E\uDDF9 Wipe database";
38
40
  CHOICES["WIPE_COLLECTIONS"] = "\uD83E\uDDF9 Wipe collections";
@@ -109,6 +111,9 @@ export class InteractiveCLI {
109
111
  await this.initControllerIfNeeded();
110
112
  await this.transferData();
111
113
  break;
114
+ case CHOICES.COMPREHENSIVE_TRANSFER:
115
+ await this.comprehensiveTransfer();
116
+ break;
112
117
  case CHOICES.BACKUP_DATABASE:
113
118
  await this.initControllerIfNeeded();
114
119
  await this.backupDatabase();
@@ -1396,13 +1401,13 @@ export class InteractiveCLI {
1396
1401
  }));
1397
1402
  }
1398
1403
  async reloadConfig() {
1399
- console.log(chalk.yellow("Reloading configuration files..."));
1404
+ MessageFormatter.progress("Reloading configuration files...", { prefix: "Config" });
1400
1405
  try {
1401
1406
  await this.controller.reloadConfig();
1402
- console.log(chalk.green("Configuration files reloaded successfully."));
1407
+ MessageFormatter.success("Configuration files reloaded successfully", { prefix: "Config" });
1403
1408
  }
1404
1409
  catch (error) {
1405
- console.error(chalk.red("Error reloading configuration files:"), error);
1410
+ MessageFormatter.error("Failed to reload configuration files", error instanceof Error ? error : new Error(String(error)), { prefix: "Config" });
1406
1411
  }
1407
1412
  }
1408
1413
  async updateFunctionSpec() {
@@ -1452,7 +1457,6 @@ export class InteractiveCLI {
1452
1457
  try {
1453
1458
  // Check for YAML config first
1454
1459
  const yamlConfigPath = findYamlConfig(this.currentDir);
1455
- console.log(`DEBUG: YAML config search from ${this.currentDir}, found: ${yamlConfigPath}`);
1456
1460
  if (yamlConfigPath) {
1457
1461
  this.isUsingTypeScriptConfig = false;
1458
1462
  MessageFormatter.info("Using YAML configuration", { prefix: "Config" });
@@ -1509,4 +1513,167 @@ export class InteractiveCLI {
1509
1513
  MessageFormatter.error("Migration failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
1510
1514
  }
1511
1515
  }
1516
+ async comprehensiveTransfer() {
1517
+ MessageFormatter.info("Starting comprehensive transfer configuration...", { prefix: "Transfer" });
1518
+ try {
1519
+ // Get source configuration
1520
+ const sourceConfig = await inquirer.prompt([
1521
+ {
1522
+ type: "input",
1523
+ name: "sourceEndpoint",
1524
+ message: "Enter the source Appwrite endpoint:",
1525
+ validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
1526
+ },
1527
+ {
1528
+ type: "input",
1529
+ name: "sourceProject",
1530
+ message: "Enter the source project ID:",
1531
+ validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
1532
+ },
1533
+ {
1534
+ type: "password",
1535
+ name: "sourceKey",
1536
+ message: "Enter the source API key:",
1537
+ validate: (input) => input.trim() !== "" || "API key cannot be empty",
1538
+ },
1539
+ ]);
1540
+ // Get target configuration
1541
+ const targetConfig = await inquirer.prompt([
1542
+ {
1543
+ type: "input",
1544
+ name: "targetEndpoint",
1545
+ message: "Enter the target Appwrite endpoint:",
1546
+ validate: (input) => input.trim() !== "" || "Endpoint cannot be empty",
1547
+ },
1548
+ {
1549
+ type: "input",
1550
+ name: "targetProject",
1551
+ message: "Enter the target project ID:",
1552
+ validate: (input) => input.trim() !== "" || "Project ID cannot be empty",
1553
+ },
1554
+ {
1555
+ type: "password",
1556
+ name: "targetKey",
1557
+ message: "Enter the target API key:",
1558
+ validate: (input) => input.trim() !== "" || "API key cannot be empty",
1559
+ },
1560
+ ]);
1561
+ // Get transfer options
1562
+ const transferOptions = await inquirer.prompt([
1563
+ {
1564
+ type: "checkbox",
1565
+ name: "transferTypes",
1566
+ message: "Select what to transfer:",
1567
+ choices: [
1568
+ { name: "👥 Users", value: "users", checked: true },
1569
+ { name: "🗄️ Databases", value: "databases", checked: true },
1570
+ { name: "📦 Storage Buckets", value: "buckets", checked: true },
1571
+ { name: "⚡ Functions", value: "functions", checked: true },
1572
+ ],
1573
+ validate: (input) => input.length > 0 || "Select at least one transfer type",
1574
+ },
1575
+ {
1576
+ type: "list",
1577
+ name: "concurrencyLimit",
1578
+ message: "Select concurrency limit:",
1579
+ choices: [
1580
+ { name: "5 (Conservative) - Users: 2, Files: 1", value: 5 },
1581
+ { name: "10 (Balanced) - Users: 5, Files: 2", value: 10 },
1582
+ { name: "15 - Users: 7, Files: 3", value: 15 },
1583
+ { name: "20 - Users: 10, Files: 5", value: 20 },
1584
+ { name: "25 - Users: 12, Files: 6", value: 25 },
1585
+ { name: "30 - Users: 15, Files: 7", value: 30 },
1586
+ { name: "35 - Users: 17, Files: 8", value: 35 },
1587
+ { name: "40 - Users: 20, Files: 10", value: 40 },
1588
+ { name: "45 - Users: 22, Files: 11", value: 45 },
1589
+ { name: "50 - Users: 25, Files: 12", value: 50 },
1590
+ { name: "55 - Users: 27, Files: 13", value: 55 },
1591
+ { name: "60 - Users: 30, Files: 15", value: 60 },
1592
+ { name: "65 - Users: 32, Files: 16", value: 65 },
1593
+ { name: "70 - Users: 35, Files: 17", value: 70 },
1594
+ { name: "75 - Users: 37, Files: 18", value: 75 },
1595
+ { name: "80 - Users: 40, Files: 20", value: 80 },
1596
+ { name: "85 - Users: 42, Files: 21", value: 85 },
1597
+ { name: "90 - Users: 45, Files: 22", value: 90 },
1598
+ { name: "95 - Users: 47, Files: 23", value: 95 },
1599
+ { name: "100 (Aggressive) - Users: 50, Files: 25", value: 100 },
1600
+ ],
1601
+ default: 10,
1602
+ },
1603
+ {
1604
+ type: "confirm",
1605
+ name: "dryRun",
1606
+ message: "Run in dry-run mode (no actual changes)?",
1607
+ default: false,
1608
+ },
1609
+ ]);
1610
+ // Confirmation
1611
+ const { confirmed } = await inquirer.prompt([
1612
+ {
1613
+ type: "confirm",
1614
+ name: "confirmed",
1615
+ message: `Are you sure you want to ${transferOptions.dryRun ? "dry-run" : "perform"} comprehensive transfer from ${sourceConfig.sourceEndpoint} to ${targetConfig.targetEndpoint}?`,
1616
+ default: false,
1617
+ },
1618
+ ]);
1619
+ if (!confirmed) {
1620
+ MessageFormatter.info("Transfer cancelled by user", { prefix: "Transfer" });
1621
+ return;
1622
+ }
1623
+ // Password preservation information
1624
+ if (transferOptions.transferTypes.includes("users") && !transferOptions.dryRun) {
1625
+ MessageFormatter.info("User Password Transfer Information:", { prefix: "Transfer" });
1626
+ MessageFormatter.info("✅ Users with hashed passwords (Argon2, Bcrypt, Scrypt, MD5, SHA, PHPass) will preserve their passwords", { prefix: "Transfer" });
1627
+ MessageFormatter.info("⚠️ Users without hash information will receive temporary passwords and need to reset", { prefix: "Transfer" });
1628
+ MessageFormatter.info("🔒 All user data (preferences, labels, verification status) will be preserved", { prefix: "Transfer" });
1629
+ const { continueWithUsers } = await inquirer.prompt([
1630
+ {
1631
+ type: "confirm",
1632
+ name: "continueWithUsers",
1633
+ message: "Continue with user transfer?",
1634
+ default: true,
1635
+ },
1636
+ ]);
1637
+ if (!continueWithUsers) {
1638
+ // Remove users from transfer types
1639
+ transferOptions.transferTypes = transferOptions.transferTypes.filter((type) => type !== "users");
1640
+ if (transferOptions.transferTypes.length === 0) {
1641
+ MessageFormatter.info("No transfer types selected, cancelling", { prefix: "Transfer" });
1642
+ return;
1643
+ }
1644
+ }
1645
+ }
1646
+ // Execute comprehensive transfer
1647
+ const comprehensiveTransferOptions = {
1648
+ sourceEndpoint: sourceConfig.sourceEndpoint,
1649
+ sourceProject: sourceConfig.sourceProject,
1650
+ sourceKey: sourceConfig.sourceKey,
1651
+ targetEndpoint: targetConfig.targetEndpoint,
1652
+ targetProject: targetConfig.targetProject,
1653
+ targetKey: targetConfig.targetKey,
1654
+ transferUsers: transferOptions.transferTypes.includes("users"),
1655
+ transferDatabases: transferOptions.transferTypes.includes("databases"),
1656
+ transferBuckets: transferOptions.transferTypes.includes("buckets"),
1657
+ transferFunctions: transferOptions.transferTypes.includes("functions"),
1658
+ concurrencyLimit: transferOptions.concurrencyLimit,
1659
+ dryRun: transferOptions.dryRun,
1660
+ };
1661
+ const transfer = new ComprehensiveTransfer(comprehensiveTransferOptions);
1662
+ const results = await transfer.execute();
1663
+ // Display results
1664
+ if (transferOptions.dryRun) {
1665
+ MessageFormatter.success("Dry run completed successfully!", { prefix: "Transfer" });
1666
+ }
1667
+ else {
1668
+ MessageFormatter.success("Comprehensive transfer completed!", { prefix: "Transfer" });
1669
+ if (transferOptions.transferTypes.includes("users") && results.users.transferred > 0) {
1670
+ MessageFormatter.info("Users with preserved password hashes can log in with their original passwords", { prefix: "Transfer" });
1671
+ MessageFormatter.info("Users with temporary passwords will need to reset their passwords", { prefix: "Transfer" });
1672
+ }
1673
+ }
1674
+ }
1675
+ catch (error) {
1676
+ MessageFormatter.error("Comprehensive transfer failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
1677
+ }
1678
+ }
1512
1679
  }
@@ -0,0 +1,66 @@
1
+ export interface ComprehensiveTransferOptions {
2
+ sourceEndpoint: string;
3
+ sourceProject: string;
4
+ sourceKey: string;
5
+ targetEndpoint: string;
6
+ targetProject: string;
7
+ targetKey: string;
8
+ transferUsers?: boolean;
9
+ transferDatabases?: boolean;
10
+ transferBuckets?: boolean;
11
+ transferFunctions?: boolean;
12
+ concurrencyLimit?: number;
13
+ dryRun?: boolean;
14
+ }
15
+ export interface TransferResults {
16
+ users: {
17
+ transferred: number;
18
+ skipped: number;
19
+ failed: number;
20
+ };
21
+ databases: {
22
+ transferred: number;
23
+ skipped: number;
24
+ failed: number;
25
+ };
26
+ buckets: {
27
+ transferred: number;
28
+ skipped: number;
29
+ failed: number;
30
+ };
31
+ functions: {
32
+ transferred: number;
33
+ skipped: number;
34
+ failed: number;
35
+ };
36
+ totalTime: number;
37
+ }
38
+ export declare class ComprehensiveTransfer {
39
+ private options;
40
+ private sourceClient;
41
+ private targetClient;
42
+ private sourceUsers;
43
+ private targetUsers;
44
+ private sourceDatabases;
45
+ private targetDatabases;
46
+ private sourceStorage;
47
+ private targetStorage;
48
+ private sourceFunctions;
49
+ private targetFunctions;
50
+ private limit;
51
+ private userLimit;
52
+ private fileLimit;
53
+ private results;
54
+ private startTime;
55
+ private tempDir;
56
+ constructor(options: ComprehensiveTransferOptions);
57
+ execute(): Promise<TransferResults>;
58
+ private transferAllUsers;
59
+ private transferAllDatabases;
60
+ private transferAllBuckets;
61
+ private transferBucketFiles;
62
+ private validateAndDownloadFile;
63
+ private transferAllFunctions;
64
+ private downloadFunction;
65
+ private printSummary;
66
+ }