appwrite-utils-cli 1.7.8 → 1.8.1

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 (111) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +10 -10
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/services/ConfigLoaderService.d.ts +7 -0
  18. package/dist/config/services/ConfigLoaderService.js +47 -1
  19. package/dist/config/yamlConfig.d.ts +221 -88
  20. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  21. package/dist/examples/yamlTerminologyExample.js +6 -3
  22. package/dist/functions/deployments.js +5 -23
  23. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  24. package/dist/functions/fnConfigDiscovery.js +108 -0
  25. package/dist/functions/methods.js +4 -2
  26. package/dist/functions/pathResolution.d.ts +37 -0
  27. package/dist/functions/pathResolution.js +185 -0
  28. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  29. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  30. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  31. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  32. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  33. package/dist/functions/templates/hono-typescript/README.md +286 -0
  34. package/dist/functions/templates/hono-typescript/package.json +26 -0
  35. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  36. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  37. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  38. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  39. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  40. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  41. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  42. package/dist/functions/templates/typescript-node/README.md +32 -0
  43. package/dist/functions/templates/typescript-node/package.json +25 -0
  44. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  45. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  46. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  47. package/dist/functions/templates/uv/README.md +31 -0
  48. package/dist/functions/templates/uv/pyproject.toml +30 -0
  49. package/dist/functions/templates/uv/src/__init__.py +0 -0
  50. package/dist/functions/templates/uv/src/context.py +125 -0
  51. package/dist/functions/templates/uv/src/index.py +46 -0
  52. package/dist/interactiveCLI.js +18 -15
  53. package/dist/main.js +219 -81
  54. package/dist/migrations/appwriteToX.d.ts +88 -23
  55. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  56. package/dist/migrations/comprehensiveTransfer.js +83 -6
  57. package/dist/migrations/dataLoader.d.ts +227 -69
  58. package/dist/migrations/dataLoader.js +3 -3
  59. package/dist/migrations/importController.js +3 -3
  60. package/dist/migrations/relationships.d.ts +8 -2
  61. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  62. package/dist/migrations/transfer.js +159 -37
  63. package/dist/shared/attributeMapper.d.ts +20 -0
  64. package/dist/shared/attributeMapper.js +203 -0
  65. package/dist/shared/selectionDialogs.d.ts +1 -1
  66. package/dist/shared/selectionDialogs.js +39 -11
  67. package/dist/storage/schemas.d.ts +354 -92
  68. package/dist/utils/configDiscovery.js +4 -3
  69. package/dist/utils/versionDetection.d.ts +0 -4
  70. package/dist/utils/versionDetection.js +41 -173
  71. package/dist/utils/yamlConverter.js +89 -16
  72. package/dist/utils/yamlLoader.d.ts +1 -1
  73. package/dist/utils/yamlLoader.js +6 -2
  74. package/dist/utilsController.d.ts +2 -1
  75. package/dist/utilsController.js +151 -22
  76. package/package.json +7 -5
  77. package/scripts/copy-templates.ts +23 -0
  78. package/src/adapters/AdapterFactory.ts +119 -143
  79. package/src/adapters/DatabaseAdapter.ts +18 -3
  80. package/src/adapters/LegacyAdapter.ts +236 -105
  81. package/src/adapters/TablesDBAdapter.ts +773 -643
  82. package/src/cli/commands/databaseCommands.ts +19 -19
  83. package/src/cli/commands/functionCommands.ts +23 -14
  84. package/src/collections/attributes.ts +2054 -1611
  85. package/src/collections/methods.ts +208 -293
  86. package/src/collections/tableOperations.ts +506 -0
  87. package/src/collections/transferOperations.ts +218 -144
  88. package/src/config/services/ConfigLoaderService.ts +62 -1
  89. package/src/examples/yamlTerminologyExample.ts +10 -5
  90. package/src/functions/deployments.ts +10 -35
  91. package/src/functions/fnConfigDiscovery.ts +103 -0
  92. package/src/functions/methods.ts +4 -2
  93. package/src/functions/pathResolution.ts +227 -0
  94. package/src/interactiveCLI.ts +25 -20
  95. package/src/main.ts +557 -202
  96. package/src/migrations/comprehensiveTransfer.ts +126 -50
  97. package/src/migrations/dataLoader.ts +3 -3
  98. package/src/migrations/importController.ts +3 -3
  99. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  100. package/src/migrations/transfer.ts +148 -131
  101. package/src/shared/attributeMapper.ts +229 -0
  102. package/src/shared/selectionDialogs.ts +65 -32
  103. package/src/utils/configDiscovery.ts +9 -3
  104. package/src/utils/versionDetection.ts +74 -228
  105. package/src/utils/yamlConverter.ts +94 -17
  106. package/src/utils/yamlLoader.ts +11 -4
  107. package/src/utilsController.ts +202 -36
  108. package/dist/utils/schemaStrings.d.ts +0 -14
  109. package/dist/utils/schemaStrings.js +0 -428
  110. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  111. package/dist/utils/sessionPreservationExample.js +0 -101
package/dist/main.js CHANGED
@@ -18,9 +18,9 @@ import { logger } from "./shared/logging.js";
18
18
  import path from "path";
19
19
  import fs from "fs";
20
20
  import { createRequire } from "node:module";
21
- import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig } from "./utils/projectConfig.js";
22
- import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus } from "./utils/sessionAuth.js";
23
- import { findYamlConfig, loadYamlConfigWithSession } from "./config/yamlConfig.js";
21
+ import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig, } from "./utils/projectConfig.js";
22
+ import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus, } from "./utils/sessionAuth.js";
23
+ import { findYamlConfig, loadYamlConfigWithSession, } from "./config/yamlConfig.js";
24
24
  const require = createRequire(import.meta.url);
25
25
  if (!globalThis.require) {
26
26
  globalThis.require = require;
@@ -76,13 +76,13 @@ async function performEnhancedSync(controller, parsedArgv) {
76
76
  isNew: false
77
77
  }));
78
78
  const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
79
- const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
79
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'pull');
80
80
  if (!confirmed) {
81
- MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
81
+ MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
82
82
  return null;
83
83
  }
84
- // Perform sync with existing configuration
85
- await controller.selectiveSync(databaseSelections, bucketSelections);
84
+ // Perform sync with existing configuration (pull from remote)
85
+ await controller.selectivePull(databaseSelections, bucketSelections);
86
86
  return selectionSummary;
87
87
  }
88
88
  }
@@ -94,9 +94,9 @@ async function performEnhancedSync(controller, parsedArgv) {
94
94
  const allowNewOnly = !syncExisting;
95
95
  // Select databases
96
96
  const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, {
97
- showSelectAll: true,
97
+ showSelectAll: false,
98
98
  allowNewOnly,
99
- defaultSelected: syncExisting ? configuredDatabases.map(db => db.$id) : []
99
+ defaultSelected: []
100
100
  });
101
101
  if (selectedDatabaseIds.length === 0) {
102
102
  MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
@@ -116,9 +116,9 @@ async function performEnhancedSync(controller, parsedArgv) {
116
116
  const configuredTables = controller.config.collections || [];
117
117
  // Select tables for this database
118
118
  const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(databaseId, database.name, availableTables, configuredTables, {
119
- showSelectAll: true,
119
+ showSelectAll: false,
120
120
  allowNewOnly,
121
- defaultSelected: syncExisting ? configuredTables.map((t) => t.$id) : []
121
+ defaultSelected: []
122
122
  });
123
123
  tableSelectionsMap.set(databaseId, selectedTableIds);
124
124
  if (selectedTableIds.length === 0) {
@@ -137,10 +137,10 @@ async function performEnhancedSync(controller, parsedArgv) {
137
137
  // you'd fetch this from the Appwrite API
138
138
  const availableBuckets = configuredBuckets; // Placeholder
139
139
  selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, {
140
- showSelectAll: true,
140
+ showSelectAll: false,
141
141
  allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
142
142
  groupByDatabase: true,
143
- defaultSelected: syncExisting ? configuredBuckets.map(b => b.$id) : []
143
+ defaultSelected: []
144
144
  });
145
145
  }
146
146
  catch (error) {
@@ -154,13 +154,13 @@ async function performEnhancedSync(controller, parsedArgv) {
154
154
  configuredBuckets, availableDatabases);
155
155
  // Show final confirmation
156
156
  const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
157
- const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
157
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'pull');
158
158
  if (!confirmed) {
159
- MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
159
+ MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
160
160
  return null;
161
161
  }
162
- // Perform the selective sync
163
- await controller.selectiveSync(databaseSelections, bucketSelections);
162
+ // Perform the selective sync (pull from remote)
163
+ await controller.selectivePull(databaseSelections, bucketSelections);
164
164
  MessageFormatter.success("Enhanced sync completed successfully", { prefix: "Sync" });
165
165
  return selectionSummary;
166
166
  }
@@ -185,24 +185,28 @@ function checkMigrationConditions(configPath) {
185
185
  if (!fs.existsSync(collectionsPath)) {
186
186
  return {
187
187
  allowed: false,
188
- reason: "No collections/ folder found. Migration requires existing collections to migrate."
188
+ reason: "No collections/ folder found. Migration requires existing collections to migrate.",
189
189
  };
190
190
  }
191
191
  // Check if collections/ folder has YAML files
192
- const collectionFiles = fs.readdirSync(collectionsPath).filter(file => file.endsWith(".yaml") || file.endsWith(".yml"));
192
+ const collectionFiles = fs
193
+ .readdirSync(collectionsPath)
194
+ .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
193
195
  if (collectionFiles.length === 0) {
194
196
  return {
195
197
  allowed: false,
196
- reason: "No YAML files found in collections/ folder. Migration requires existing collection YAML files."
198
+ reason: "No YAML files found in collections/ folder. Migration requires existing collection YAML files.",
197
199
  };
198
200
  }
199
201
  // Check if tables/ folder exists and has YAML files
200
202
  if (fs.existsSync(tablesPath)) {
201
- const tableFiles = fs.readdirSync(tablesPath).filter(file => file.endsWith(".yaml") || file.endsWith(".yml"));
203
+ const tableFiles = fs
204
+ .readdirSync(tablesPath)
205
+ .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
202
206
  if (tableFiles.length > 0) {
203
207
  return {
204
208
  allowed: false,
205
- reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`
209
+ reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`,
206
210
  };
207
211
  }
208
212
  }
@@ -433,16 +437,23 @@ async function main() {
433
437
  // Enhanced config creation with session and project file support
434
438
  let directConfig = undefined;
435
439
  // Show authentication status on startup if no config provided
436
- if (!argv.config && !argv.endpoint && !argv.projectId && !argv.apiKey && !argv.useSession && !argv.sessionCookie) {
440
+ if (!argv.config &&
441
+ !argv.endpoint &&
442
+ !argv.projectId &&
443
+ !argv.apiKey &&
444
+ !argv.useSession &&
445
+ !argv.sessionCookie) {
437
446
  if (hasAnyValidSessions) {
438
447
  MessageFormatter.info(`Found ${availableSessions.length} available session(s)`, { prefix: "Auth" });
439
- availableSessions.forEach(session => {
440
- MessageFormatter.info(` \u2022 ${session.projectId} (${session.email || 'unknown'}) at ${session.endpoint}`, { prefix: "Auth" });
448
+ availableSessions.forEach((session) => {
449
+ MessageFormatter.info(` \u2022 ${session.projectId} (${session.email || "unknown"}) at ${session.endpoint}`, { prefix: "Auth" });
441
450
  });
442
451
  MessageFormatter.info("Use --session to enable session authentication", { prefix: "Auth" });
443
452
  }
444
453
  else {
445
- MessageFormatter.info("No active Appwrite sessions found", { prefix: "Auth" });
454
+ MessageFormatter.info("No active Appwrite sessions found", {
455
+ prefix: "Auth",
456
+ });
446
457
  MessageFormatter.info("\u2022 Run 'appwrite login' to authenticate with session", { prefix: "Auth" });
447
458
  MessageFormatter.info("\u2022 Or provide --apiKey for API key authentication", { prefix: "Auth" });
448
459
  }
@@ -457,7 +468,11 @@ async function main() {
457
468
  }
458
469
  }
459
470
  // Priority 2: CLI arguments override project config
460
- if (argv.endpoint || argv.projectId || argv.apiKey || argv.useSession || argv.sessionCookie) {
471
+ if (argv.endpoint ||
472
+ argv.projectId ||
473
+ argv.apiKey ||
474
+ argv.useSession ||
475
+ argv.sessionCookie) {
461
476
  directConfig = {
462
477
  ...directConfig,
463
478
  appwriteEndpoint: argv.endpoint || directConfig?.appwriteEndpoint,
@@ -482,7 +497,9 @@ async function main() {
482
497
  MessageFormatter.warning("Session authentication requested but no valid session found", { prefix: "Auth" });
483
498
  const availableSessions = getAvailableSessions();
484
499
  if (availableSessions.length > 0) {
485
- MessageFormatter.info(`Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
500
+ MessageFormatter.info(`Available sessions: ${availableSessions
501
+ .map((s) => `${s.projectId} (${s.email || "unknown"})`)
502
+ .join(", ")}`, { prefix: "Auth" });
486
503
  MessageFormatter.info("Use --session flag to enable session authentication", { prefix: "Auth" });
487
504
  }
488
505
  else {
@@ -503,11 +520,16 @@ async function main() {
503
520
  // 3. Auto-detect session authentication when possible
504
521
  let finalDirectConfig = directConfig;
505
522
  if ((argv.useSession || argv.sessionCookie) &&
506
- (!directConfig || !directConfig.appwriteEndpoint || !directConfig.appwriteProject)) {
523
+ (!directConfig ||
524
+ !directConfig.appwriteEndpoint ||
525
+ !directConfig.appwriteProject)) {
507
526
  // Don't pass incomplete directConfig - let UtilsController load YAML config normally
508
527
  finalDirectConfig = null;
509
528
  }
510
- else if (finalDirectConfig && !finalDirectConfig.appwriteKey && !argv.useSession && !argv.sessionCookie) {
529
+ else if (finalDirectConfig &&
530
+ !finalDirectConfig.appwriteKey &&
531
+ !argv.useSession &&
532
+ !argv.sessionCookie) {
511
533
  // Auto-detect session authentication when no API key provided
512
534
  if (sessionAuthAvailable) {
513
535
  MessageFormatter.info("No API key provided, but session authentication is available", { prefix: "Auth" });
@@ -539,10 +561,14 @@ async function main() {
539
561
  if (argv.generateConstants) {
540
562
  const { ConstantsGenerator } = await import("./utils/constantsGenerator.js");
541
563
  if (!controller.config) {
542
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Constants" });
564
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
565
+ prefix: "Constants",
566
+ });
543
567
  return;
544
568
  }
545
- const languages = argv.constantsLanguages.split(",").map(l => l.trim());
569
+ const languages = argv
570
+ .constantsLanguages.split(",")
571
+ .map((l) => l.trim());
546
572
  // Determine output directory - use config folder/constants by default, or custom path if specified
547
573
  let outputDir;
548
574
  if (argv.constantsOutput === "auto") {
@@ -560,13 +586,17 @@ async function main() {
560
586
  const generator = new ConstantsGenerator(controller.config);
561
587
  await generator.generateFiles(languages, outputDir);
562
588
  operationStats.generatedConstants = languages.length;
563
- MessageFormatter.success(`Constants generated in ${outputDir}`, { prefix: "Constants" });
589
+ MessageFormatter.success(`Constants generated in ${outputDir}`, {
590
+ prefix: "Constants",
591
+ });
564
592
  return;
565
593
  }
566
594
  if (argv.migrateCollectionsToTables) {
567
595
  try {
568
596
  if (!controller.config) {
569
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Migration" });
597
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
598
+ prefix: "Migration",
599
+ });
570
600
  return;
571
601
  }
572
602
  // Get the config path from the controller or use .appwrite in current directory
@@ -587,18 +617,22 @@ async function main() {
587
617
  const migrationCheck = checkMigrationConditions(configPath);
588
618
  if (!migrationCheck.allowed) {
589
619
  MessageFormatter.error(`Migration not allowed: ${migrationCheck.reason}`, undefined, { prefix: "Migration" });
590
- MessageFormatter.info("Migration requirements:", { prefix: "Migration" });
620
+ MessageFormatter.info("Migration requirements:", {
621
+ prefix: "Migration",
622
+ });
591
623
  MessageFormatter.info(" • Configuration must be loaded (use --config or have .appwrite/ folder)", { prefix: "Migration" });
592
624
  MessageFormatter.info(" • collections/ folder must exist with YAML files", { prefix: "Migration" });
593
625
  MessageFormatter.info(" • tables/ folder must not exist or be empty", { prefix: "Migration" });
594
626
  return;
595
627
  }
596
628
  const { migrateCollectionsToTables } = await import("./config/configMigration.js");
597
- MessageFormatter.info("Starting collections to tables migration...", { prefix: "Migration" });
629
+ MessageFormatter.info("Starting collections to tables migration...", {
630
+ prefix: "Migration",
631
+ });
598
632
  const result = migrateCollectionsToTables(controller.config, {
599
633
  strategy: "full_migration",
600
634
  validateResult: true,
601
- dryRun: false
635
+ dryRun: false,
602
636
  });
603
637
  if (result.success) {
604
638
  operationStats.migratedCollections = result.changes.length;
@@ -619,17 +653,29 @@ async function main() {
619
653
  // Provide better guidance based on available authentication methods
620
654
  const availableSessions = getAvailableSessions();
621
655
  if (availableSessions.length > 0) {
622
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
623
- MessageFormatter.info("Available authentication options:", { prefix: "Auth" });
624
- MessageFormatter.info("• Session authentication: Add --session flag", { prefix: "Auth" });
656
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
657
+ prefix: "CLI",
658
+ });
659
+ MessageFormatter.info("Available authentication options:", {
660
+ prefix: "Auth",
661
+ });
662
+ MessageFormatter.info("• Session authentication: Add --session flag", {
663
+ prefix: "Auth",
664
+ });
625
665
  MessageFormatter.info("• API key authentication: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
626
- MessageFormatter.info(`• Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
666
+ MessageFormatter.info(`• Available sessions: ${availableSessions
667
+ .map((s) => `${s.projectId} (${s.email || "unknown"})`)
668
+ .join(", ")}`, { prefix: "Auth" });
627
669
  }
628
670
  else {
629
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
671
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
672
+ prefix: "CLI",
673
+ });
630
674
  MessageFormatter.info("Authentication options:", { prefix: "Auth" });
631
675
  MessageFormatter.info("• Login with Appwrite CLI: Run 'appwrite login' then use --session flag", { prefix: "Auth" });
632
- MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
676
+ MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", {
677
+ prefix: "Auth",
678
+ });
633
679
  MessageFormatter.info("• Create config file: Run with --setup to initialize project configuration", { prefix: "Auth" });
634
680
  }
635
681
  return;
@@ -640,13 +686,15 @@ async function main() {
640
686
  const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
641
687
  const { listBackups } = await import("./shared/backupTracking.js");
642
688
  if (!controller.config) {
643
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Backups" });
689
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
690
+ prefix: "Backups",
691
+ });
644
692
  return;
645
693
  }
646
694
  const { adapter } = await AdapterFactory.create({
647
695
  appwriteEndpoint: controller.config.appwriteEndpoint,
648
696
  appwriteProject: controller.config.appwriteProject,
649
- appwriteKey: controller.config.appwriteKey
697
+ appwriteKey: controller.config.appwriteKey,
650
698
  });
651
699
  const databases = parsedArgv.dbIds
652
700
  ? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
@@ -699,11 +747,16 @@ async function main() {
699
747
  await controller.updateFunctionSpecifications(parsedArgv.functionId, parsedArgv.specification);
700
748
  }
701
749
  // Add default databases if not specified (only if we need them for operations)
702
- const needsDatabases = options.doBackup || options.wipeDatabase ||
703
- options.wipeDocumentStorage || options.wipeUsers ||
704
- options.wipeCollections || options.importData ||
705
- parsedArgv.sync || parsedArgv.transfer;
706
- if (needsDatabases && (!options.databases || options.databases.length === 0)) {
750
+ const needsDatabases = options.doBackup ||
751
+ options.wipeDatabase ||
752
+ options.wipeDocumentStorage ||
753
+ options.wipeUsers ||
754
+ options.wipeCollections ||
755
+ options.importData ||
756
+ parsedArgv.sync ||
757
+ parsedArgv.transfer;
758
+ if (needsDatabases &&
759
+ (!options.databases || options.databases.length === 0)) {
707
760
  const allDatabases = await fetchAllDatabases(controller.database);
708
761
  options.databases = allDatabases;
709
762
  }
@@ -736,58 +789,62 @@ async function main() {
736
789
  else {
737
790
  // Interactive selection
738
791
  const inquirer = (await import("inquirer")).default;
739
- const answer = await inquirer.prompt([{
740
- type: 'list',
741
- name: 'trackingDb',
742
- message: 'Select database to store backup tracking metadata:',
743
- choices: allDatabases.map(db => ({
792
+ const answer = await inquirer.prompt([
793
+ {
794
+ type: "list",
795
+ name: "trackingDb",
796
+ message: "Select database to store backup tracking metadata:",
797
+ choices: allDatabases.map((db) => ({
744
798
  name: `${db.name} (${db.$id})`,
745
- value: db.$id
746
- }))
747
- }]);
799
+ value: db.$id,
800
+ })),
801
+ },
802
+ ]);
748
803
  trackingDatabaseId = answer.trackingDb;
749
804
  }
750
805
  }
751
806
  // Ensure trackingDatabaseId is defined before proceeding
752
807
  if (!trackingDatabaseId) {
753
- throw new Error('Tracking database ID is required for comprehensive backup');
808
+ throw new Error("Tracking database ID is required for comprehensive backup");
754
809
  }
755
- MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, { prefix: "Backup" });
810
+ MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, {
811
+ prefix: "Backup",
812
+ });
756
813
  // Create adapter for backup tracking
757
814
  const { adapter } = await AdapterFactory.create({
758
815
  appwriteEndpoint: controller.config.appwriteEndpoint,
759
816
  appwriteProject: controller.config.appwriteProject,
760
817
  appwriteKey: controller.config.appwriteKey,
761
- sessionCookie: controller.config.sessionCookie
818
+ sessionCookie: controller.config.sessionCookie,
762
819
  });
763
820
  const result = await comprehensiveBackup(controller.config, controller.database, controller.storage, adapter, {
764
821
  trackingDatabaseId,
765
- backupFormat: parsedArgv.backupFormat || 'zip',
822
+ backupFormat: parsedArgv.backupFormat || "zip",
766
823
  parallelDownloads: parsedArgv.parallelDownloads || 10,
767
824
  onProgress: (message) => {
768
825
  MessageFormatter.info(message, { prefix: "Backup" });
769
- }
826
+ },
770
827
  });
771
828
  operationStats.comprehensiveBackup = 1;
772
829
  operationStats.databasesBackedUp = result.databaseBackups.length;
773
830
  operationStats.bucketsBackedUp = result.bucketBackups.length;
774
831
  operationStats.totalBackupSize = result.totalSizeBytes;
775
- if (result.status === 'completed') {
832
+ if (result.status === "completed") {
776
833
  MessageFormatter.success(`Comprehensive backup completed successfully (ID: ${result.backupId})`, { prefix: "Backup" });
777
834
  }
778
- else if (result.status === 'partial') {
835
+ else if (result.status === "partial") {
779
836
  MessageFormatter.warning(`Comprehensive backup completed with errors (ID: ${result.backupId})`, { prefix: "Backup" });
780
- result.errors.forEach(err => MessageFormatter.warning(err, { prefix: "Backup" }));
837
+ result.errors.forEach((err) => MessageFormatter.warning(err, { prefix: "Backup" }));
781
838
  }
782
839
  else {
783
840
  MessageFormatter.error(`Comprehensive backup failed (ID: ${result.backupId})`, undefined, { prefix: "Backup" });
784
- result.errors.forEach(err => MessageFormatter.error(err, undefined, { prefix: "Backup" }));
841
+ result.errors.forEach((err) => MessageFormatter.error(err, undefined, { prefix: "Backup" }));
785
842
  }
786
843
  }
787
844
  if (options.doBackup && options.databases) {
788
845
  MessageFormatter.info(`Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`, { prefix: "Backup" });
789
846
  for (const db of options.databases) {
790
- await controller.backupDatabase(db, parsedArgv.backupFormat || 'json');
847
+ await controller.backupDatabase(db, parsedArgv.backupFormat || "json");
791
848
  }
792
849
  operationStats.backups = options.databases.length;
793
850
  MessageFormatter.success(`Backup completed for ${options.databases.length} database(s)`, { prefix: "Backup" });
@@ -797,10 +854,10 @@ async function main() {
797
854
  options.wipeUsers ||
798
855
  options.wipeCollections) {
799
856
  // Confirm destructive operations
800
- const databaseNames = options.databases?.map(db => db.name) || [];
857
+ const databaseNames = options.databases?.map((db) => db.name) || [];
801
858
  const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(databaseNames, {
802
859
  includeStorage: options.wipeDocumentStorage,
803
- includeUsers: options.wipeUsers
860
+ includeUsers: options.wipeUsers,
804
861
  });
805
862
  if (!confirmed) {
806
863
  MessageFormatter.info("Operation cancelled by user", { prefix: "CLI" });
@@ -842,7 +899,7 @@ async function main() {
842
899
  const dbCollections = await fetchAllCollections(db.$id, controller.database);
843
900
  const collectionsToWipe = dbCollections.filter((c) => options.collections.includes(c.$id));
844
901
  // Confirm collection wipe
845
- const collectionNames = collectionsToWipe.map(c => c.name);
902
+ const collectionNames = collectionsToWipe.map((c) => c.name);
846
903
  const collectionConfirmed = await ConfirmationDialogs.confirmCollectionWipe(db.name, collectionNames);
847
904
  if (collectionConfirmed) {
848
905
  for (const collection of collectionsToWipe) {
@@ -853,7 +910,10 @@ async function main() {
853
910
  }
854
911
  }
855
912
  // Show wipe operation summary
856
- if (wipeStats.databases > 0 || wipeStats.collections > 0 || wipeStats.users > 0 || wipeStats.buckets > 0) {
913
+ if (wipeStats.databases > 0 ||
914
+ wipeStats.collections > 0 ||
915
+ wipeStats.users > 0 ||
916
+ wipeStats.buckets > 0) {
857
917
  operationStats.wipedDatabases = wipeStats.databases;
858
918
  operationStats.wipedCollections = wipeStats.collections;
859
919
  operationStats.wipedUsers = wipeStats.users;
@@ -861,12 +921,84 @@ async function main() {
861
921
  }
862
922
  }
863
923
  if (parsedArgv.push) {
864
- // PUSH: Use LOCAL config collections only (pass empty array to use config.collections)
865
- const databases = options.databases || (await fetchAllDatabases(controller.database));
866
- // Pass empty array - syncDb will use config.collections (local schema)
867
- await controller.syncDb(databases, []);
868
- operationStats.pushedDatabases = databases.length;
869
- operationStats.pushedCollections = controller.config?.collections?.length || 0;
924
+ await controller.init();
925
+ if (!controller.database || !controller.config) {
926
+ MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
927
+ return;
928
+ }
929
+ // Fetch available DBs
930
+ const availableDatabases = await fetchAllDatabases(controller.database);
931
+ if (availableDatabases.length === 0) {
932
+ MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
933
+ return;
934
+ }
935
+ // Determine selected DBs
936
+ let selectedDbIds = [];
937
+ if (parsedArgv.dbIds) {
938
+ selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
939
+ }
940
+ else {
941
+ selectedDbIds = await SelectionDialogs.selectDatabases(availableDatabases, controller.config.databases || [], { showSelectAll: false, allowNewOnly: false, defaultSelected: [] });
942
+ }
943
+ if (selectedDbIds.length === 0) {
944
+ MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
945
+ return;
946
+ }
947
+ // Build DatabaseSelection[] with tableIds per DB
948
+ const databaseSelections = [];
949
+ const allConfigItems = controller.config.collections || controller.config.tables || [];
950
+ for (const dbId of selectedDbIds) {
951
+ const db = availableDatabases.find(d => d.$id === dbId);
952
+ if (!db)
953
+ continue;
954
+ // Filter config items eligible for this DB according to databaseId/databaseIds rule
955
+ const eligibleConfigItems = allConfigItems.filter(item => {
956
+ const one = item.databaseId;
957
+ const many = item.databaseIds;
958
+ if (Array.isArray(many) && many.length > 0)
959
+ return many.includes(dbId);
960
+ if (one)
961
+ return one === dbId;
962
+ return true; // eligible everywhere if unspecified
963
+ });
964
+ // Fetch available tables from remote for selection context
965
+ const availableTables = await fetchAllCollections(dbId, controller.database);
966
+ // Determine selected table IDs
967
+ let selectedTableIds = [];
968
+ if (parsedArgv.collectionIds) {
969
+ const ids = parsedArgv.collectionIds.split(/[,\s]+/).filter(Boolean);
970
+ // Only allow IDs that are in eligible config items
971
+ const eligibleIds = new Set(eligibleConfigItems.map((c) => c.$id || c.id));
972
+ selectedTableIds = ids.filter(id => eligibleIds.has(id));
973
+ }
974
+ else {
975
+ selectedTableIds = await SelectionDialogs.selectTablesForDatabase(dbId, db.name, availableTables, eligibleConfigItems, { showSelectAll: false, allowNewOnly: true, defaultSelected: [] });
976
+ }
977
+ databaseSelections.push({
978
+ databaseId: db.$id,
979
+ databaseName: db.name,
980
+ tableIds: selectedTableIds,
981
+ tableNames: [],
982
+ isNew: false,
983
+ });
984
+ }
985
+ if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
986
+ MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
987
+ return;
988
+ }
989
+ const pushSummary = {
990
+ databases: databaseSelections.length,
991
+ collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
992
+ details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
993
+ };
994
+ const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
995
+ if (!confirmed) {
996
+ MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
997
+ return;
998
+ }
999
+ await controller.selectivePush(databaseSelections, []);
1000
+ operationStats.pushedDatabases = databaseSelections.length;
1001
+ operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
870
1002
  }
871
1003
  else if (parsedArgv.sync) {
872
1004
  // Enhanced SYNC: Pull from remote with intelligent configuration detection
@@ -905,7 +1037,9 @@ async function main() {
905
1037
  MessageFormatter.info(`Starting database transfer from ${parsedArgv.fromDbId} to ${parsedArgv.toDbId}`, { prefix: "Transfer" });
906
1038
  fromDb = (await controller.getDatabasesByIds([parsedArgv.fromDbId]))?.[0];
907
1039
  if (!fromDb) {
908
- MessageFormatter.error("Source database not found", undefined, { prefix: "Transfer" });
1040
+ MessageFormatter.error("Source database not found", undefined, {
1041
+ prefix: "Transfer",
1042
+ });
909
1043
  return;
910
1044
  }
911
1045
  if (isRemote) {
@@ -920,14 +1054,18 @@ async function main() {
920
1054
  const remoteDbs = await fetchAllDatabases(targetDatabases);
921
1055
  toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
922
1056
  if (!toDb) {
923
- MessageFormatter.error("Target database not found", undefined, { prefix: "Transfer" });
1057
+ MessageFormatter.error("Target database not found", undefined, {
1058
+ prefix: "Transfer",
1059
+ });
924
1060
  return;
925
1061
  }
926
1062
  }
927
1063
  else {
928
1064
  toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId]))?.[0];
929
1065
  if (!toDb) {
930
- MessageFormatter.error("Target database not found", undefined, { prefix: "Transfer" });
1066
+ MessageFormatter.error("Target database not found", undefined, {
1067
+ prefix: "Transfer",
1068
+ });
931
1069
  return;
932
1070
  }
933
1071
  }