appwrite-utils-cli 1.7.9 → 1.8.2

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 (70) 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 +4 -3
  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/yamlConfig.d.ts +221 -88
  18. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  19. package/dist/examples/yamlTerminologyExample.js +6 -3
  20. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  21. package/dist/functions/fnConfigDiscovery.js +108 -0
  22. package/dist/interactiveCLI.js +18 -15
  23. package/dist/main.js +211 -73
  24. package/dist/migrations/appwriteToX.d.ts +88 -23
  25. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  26. package/dist/migrations/comprehensiveTransfer.js +83 -6
  27. package/dist/migrations/dataLoader.d.ts +227 -69
  28. package/dist/migrations/dataLoader.js +3 -3
  29. package/dist/migrations/importController.js +3 -3
  30. package/dist/migrations/relationships.d.ts +8 -2
  31. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  32. package/dist/migrations/transfer.js +159 -37
  33. package/dist/shared/attributeMapper.d.ts +20 -0
  34. package/dist/shared/attributeMapper.js +203 -0
  35. package/dist/shared/selectionDialogs.js +8 -4
  36. package/dist/storage/schemas.d.ts +354 -92
  37. package/dist/utils/configDiscovery.js +4 -3
  38. package/dist/utils/versionDetection.d.ts +0 -4
  39. package/dist/utils/versionDetection.js +41 -173
  40. package/dist/utils/yamlConverter.js +89 -16
  41. package/dist/utils/yamlLoader.d.ts +1 -1
  42. package/dist/utils/yamlLoader.js +6 -2
  43. package/dist/utilsController.js +56 -19
  44. package/package.json +4 -4
  45. package/src/adapters/AdapterFactory.ts +119 -143
  46. package/src/adapters/DatabaseAdapter.ts +18 -3
  47. package/src/adapters/LegacyAdapter.ts +236 -105
  48. package/src/adapters/TablesDBAdapter.ts +773 -643
  49. package/src/cli/commands/databaseCommands.ts +13 -12
  50. package/src/cli/commands/functionCommands.ts +23 -14
  51. package/src/collections/attributes.ts +2054 -1611
  52. package/src/collections/methods.ts +208 -293
  53. package/src/collections/tableOperations.ts +506 -0
  54. package/src/collections/transferOperations.ts +218 -144
  55. package/src/examples/yamlTerminologyExample.ts +10 -5
  56. package/src/functions/fnConfigDiscovery.ts +103 -0
  57. package/src/interactiveCLI.ts +25 -20
  58. package/src/main.ts +549 -194
  59. package/src/migrations/comprehensiveTransfer.ts +126 -50
  60. package/src/migrations/dataLoader.ts +3 -3
  61. package/src/migrations/importController.ts +3 -3
  62. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  63. package/src/migrations/transfer.ts +148 -131
  64. package/src/shared/attributeMapper.ts +229 -0
  65. package/src/shared/selectionDialogs.ts +29 -25
  66. package/src/utils/configDiscovery.ts +9 -3
  67. package/src/utils/versionDetection.ts +74 -228
  68. package/src/utils/yamlConverter.ts +94 -17
  69. package/src/utils/yamlLoader.ts +11 -4
  70. package/src/utilsController.ts +80 -30
package/src/main.ts CHANGED
@@ -21,16 +21,26 @@ import type { SyncSelectionSummary, DatabaseSelection, BucketSelection } from ".
21
21
  import path from "path";
22
22
  import fs from "fs";
23
23
  import { createRequire } from "node:module";
24
- import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig } from "./utils/projectConfig.js";
25
- import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus } from "./utils/sessionAuth.js";
26
- import { findYamlConfig, loadYamlConfigWithSession } from "./config/yamlConfig.js";
24
+ import {
25
+ loadAppwriteProjectConfig,
26
+ findAppwriteProjectConfig,
27
+ projectConfigToAppwriteConfig,
28
+ } from "./utils/projectConfig.js";
29
+ import {
30
+ hasSessionAuth,
31
+ getAvailableSessions,
32
+ getAuthenticationStatus,
33
+ } from "./utils/sessionAuth.js";
34
+ import {
35
+ findYamlConfig,
36
+ loadYamlConfigWithSession,
37
+ } from "./config/yamlConfig.js";
27
38
 
28
39
  const require = createRequire(import.meta.url);
29
40
  if (!(globalThis as any).require) {
30
41
  (globalThis as any).require = require;
31
42
  }
32
43
 
33
-
34
44
  interface CliOptions {
35
45
  config?: string;
36
46
  it?: boolean;
@@ -42,7 +52,7 @@ interface CliOptions {
42
52
  generate?: boolean;
43
53
  import?: boolean;
44
54
  backup?: boolean;
45
- backupFormat?: 'json' | 'zip';
55
+ backupFormat?: "json" | "zip";
46
56
  comprehensiveBackup?: boolean;
47
57
  trackingDatabaseId?: string;
48
58
  parallelDownloads?: number;
@@ -170,15 +180,15 @@ async function performEnhancedSync(
170
180
  const allowNewOnly = !syncExisting;
171
181
 
172
182
  // Select databases
173
- const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
174
- availableDatabases,
175
- configuredDatabases,
176
- {
177
- showSelectAll: true,
178
- allowNewOnly,
179
- defaultSelected: syncExisting ? configuredDatabases.map(db => db.$id) : []
180
- }
181
- );
183
+ const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
184
+ availableDatabases,
185
+ configuredDatabases,
186
+ {
187
+ showSelectAll: false,
188
+ allowNewOnly,
189
+ defaultSelected: []
190
+ }
191
+ );
182
192
 
183
193
  if (selectedDatabaseIds.length === 0) {
184
194
  MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
@@ -203,17 +213,17 @@ async function performEnhancedSync(
203
213
  const configuredTables = controller.config.collections || [];
204
214
 
205
215
  // Select tables for this database
206
- const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
207
- databaseId,
208
- database.name,
209
- availableTables,
210
- configuredTables,
211
- {
212
- showSelectAll: true,
213
- allowNewOnly,
214
- defaultSelected: syncExisting ? configuredTables.map((t: any) => t.$id) : []
215
- }
216
- );
216
+ const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
217
+ databaseId,
218
+ database.name,
219
+ availableTables,
220
+ configuredTables,
221
+ {
222
+ showSelectAll: false,
223
+ allowNewOnly,
224
+ defaultSelected: []
225
+ }
226
+ );
217
227
 
218
228
  tableSelectionsMap.set(databaseId, selectedTableIds);
219
229
 
@@ -236,17 +246,17 @@ async function performEnhancedSync(
236
246
  // you'd fetch this from the Appwrite API
237
247
  const availableBuckets = configuredBuckets; // Placeholder
238
248
 
239
- selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
240
- selectedDatabaseIds,
241
- availableBuckets,
242
- configuredBuckets,
243
- {
244
- showSelectAll: true,
245
- allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
246
- groupByDatabase: true,
247
- defaultSelected: syncExisting ? configuredBuckets.map(b => b.$id) : []
248
- }
249
- );
249
+ selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
250
+ selectedDatabaseIds,
251
+ availableBuckets,
252
+ configuredBuckets,
253
+ {
254
+ showSelectAll: false,
255
+ allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
256
+ groupByDatabase: true,
257
+ defaultSelected: []
258
+ }
259
+ );
250
260
  } catch (error) {
251
261
  MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
252
262
  logger.warn("Failed to fetch buckets during sync", { error });
@@ -303,7 +313,10 @@ async function performEnhancedSync(
303
313
  * - allowed: boolean indicating if migration should proceed
304
314
  * - reason: string explaining why migration was blocked (if not allowed)
305
315
  */
306
- function checkMigrationConditions(configPath: string): { allowed: boolean; reason?: string } {
316
+ function checkMigrationConditions(configPath: string): {
317
+ allowed: boolean;
318
+ reason?: string;
319
+ } {
307
320
  const collectionsPath = path.join(configPath, "collections");
308
321
  const tablesPath = path.join(configPath, "tables");
309
322
 
@@ -311,32 +324,34 @@ function checkMigrationConditions(configPath: string): { allowed: boolean; reaso
311
324
  if (!fs.existsSync(collectionsPath)) {
312
325
  return {
313
326
  allowed: false,
314
- reason: "No collections/ folder found. Migration requires existing collections to migrate."
327
+ reason:
328
+ "No collections/ folder found. Migration requires existing collections to migrate.",
315
329
  };
316
330
  }
317
331
 
318
332
  // Check if collections/ folder has YAML files
319
- const collectionFiles = fs.readdirSync(collectionsPath).filter(file =>
320
- file.endsWith(".yaml") || file.endsWith(".yml")
321
- );
333
+ const collectionFiles = fs
334
+ .readdirSync(collectionsPath)
335
+ .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
322
336
 
323
337
  if (collectionFiles.length === 0) {
324
338
  return {
325
339
  allowed: false,
326
- reason: "No YAML files found in collections/ folder. Migration requires existing collection YAML files."
340
+ reason:
341
+ "No YAML files found in collections/ folder. Migration requires existing collection YAML files.",
327
342
  };
328
343
  }
329
344
 
330
345
  // Check if tables/ folder exists and has YAML files
331
346
  if (fs.existsSync(tablesPath)) {
332
- const tableFiles = fs.readdirSync(tablesPath).filter(file =>
333
- file.endsWith(".yaml") || file.endsWith(".yml")
334
- );
347
+ const tableFiles = fs
348
+ .readdirSync(tablesPath)
349
+ .filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
335
350
 
336
351
  if (tableFiles.length > 0) {
337
352
  return {
338
353
  allowed: false,
339
- reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`
354
+ reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`,
340
355
  };
341
356
  }
342
357
  }
@@ -357,12 +372,14 @@ const argv = yargs(hideBin(process.argv))
357
372
  })
358
373
  .option("dbIds", {
359
374
  type: "string",
360
- description: "Comma-separated list of database IDs to target (e.g., 'db1,db2,db3')",
375
+ description:
376
+ "Comma-separated list of database IDs to target (e.g., 'db1,db2,db3')",
361
377
  })
362
378
  .option("collectionIds", {
363
379
  alias: ["collIds", "tableIds", "tables"],
364
380
  type: "string",
365
- description: "Comma-separated list of collection/table IDs to target (e.g., 'users,posts')",
381
+ description:
382
+ "Comma-separated list of collection/table IDs to target (e.g., 'users,posts')",
366
383
  })
367
384
  .option("bucketIds", {
368
385
  type: "string",
@@ -384,11 +401,13 @@ const argv = yargs(hideBin(process.argv))
384
401
  })
385
402
  .option("generate", {
386
403
  type: "boolean",
387
- description: "Generate TypeScript schemas and types from your Appwrite database schemas",
404
+ description:
405
+ "Generate TypeScript schemas and types from your Appwrite database schemas",
388
406
  })
389
407
  .option("import", {
390
408
  type: "boolean",
391
- description: "Import data from importData/ directory into your Appwrite databases",
409
+ description:
410
+ "Import data from importData/ directory into your Appwrite databases",
392
411
  })
393
412
  .option("backup", {
394
413
  type: "boolean",
@@ -407,21 +426,25 @@ const argv = yargs(hideBin(process.argv))
407
426
  .option("comprehensiveBackup", {
408
427
  alias: ["comprehensive", "backup-all"],
409
428
  type: "boolean",
410
- description: "🚀 Create comprehensive backup of ALL databases and ALL storage buckets",
429
+ description:
430
+ "🚀 Create comprehensive backup of ALL databases and ALL storage buckets",
411
431
  })
412
432
  .option("trackingDatabaseId", {
413
433
  alias: ["tracking-db"],
414
434
  type: "string",
415
- description: "Database ID to use for centralized backup tracking (interactive prompt if not specified)",
435
+ description:
436
+ "Database ID to use for centralized backup tracking (interactive prompt if not specified)",
416
437
  })
417
438
  .option("parallelDownloads", {
418
439
  type: "number",
419
440
  default: 10,
420
- description: "Number of parallel file downloads for bucket backups (default: 10)",
441
+ description:
442
+ "Number of parallel file downloads for bucket backups (default: 10)",
421
443
  })
422
444
  .option("writeData", {
423
445
  type: "boolean",
424
- description: "Output converted import data to files for validation before importing",
446
+ description:
447
+ "Output converted import data to files for validation before importing",
425
448
  })
426
449
  .option("push", {
427
450
  type: "boolean",
@@ -456,7 +479,8 @@ const argv = yargs(hideBin(process.argv))
456
479
  })
457
480
  .option("transfer", {
458
481
  type: "boolean",
459
- description: "Transfer documents and files between databases, collections, or projects",
482
+ description:
483
+ "Transfer documents and files between databases, collections, or projects",
460
484
  })
461
485
  .option("fromDbId", {
462
486
  alias: ["fromDb", "sourceDbId", "sourceDb"],
@@ -500,7 +524,8 @@ const argv = yargs(hideBin(process.argv))
500
524
  })
501
525
  .option("setup", {
502
526
  type: "boolean",
503
- description: "Initialize project with configuration files and directory structure",
527
+ description:
528
+ "Initialize project with configuration files and directory structure",
504
529
  })
505
530
  .option("updateFunctionSpec", {
506
531
  type: "boolean",
@@ -527,27 +552,32 @@ const argv = yargs(hideBin(process.argv))
527
552
  .option("migrateConfig", {
528
553
  alias: ["migrate"],
529
554
  type: "boolean",
530
- description: "Migrate appwriteConfig.ts to .appwrite structure with YAML configuration",
555
+ description:
556
+ "Migrate appwriteConfig.ts to .appwrite structure with YAML configuration",
531
557
  })
532
558
  .option("generateConstants", {
533
559
  alias: ["constants"],
534
- type: "boolean",
535
- description: "Generate cross-language constants file with database, collection, bucket, and function IDs",
560
+ type: "boolean",
561
+ description:
562
+ "Generate cross-language constants file with database, collection, bucket, and function IDs",
536
563
  })
537
564
  .option("constantsLanguages", {
538
565
  type: "string",
539
- description: "Comma-separated list of languages for constants (typescript,javascript,python,php,dart,json,env)",
566
+ description:
567
+ "Comma-separated list of languages for constants (typescript,javascript,python,php,dart,json,env)",
540
568
  default: "typescript",
541
569
  })
542
570
  .option("constantsOutput", {
543
571
  type: "string",
544
- description: "Output directory for generated constants files (default: config-folder/constants)",
572
+ description:
573
+ "Output directory for generated constants files (default: config-folder/constants)",
545
574
  default: "auto",
546
575
  })
547
576
  .option("migrateCollectionsToTables", {
548
577
  alias: ["migrate-collections"],
549
578
  type: "boolean",
550
- description: "Migrate collections to tables format for TablesDB API compatibility",
579
+ description:
580
+ "Migrate collections to tables format for TablesDB API compatibility",
551
581
  })
552
582
  .option("useSession", {
553
583
  alias: ["session"],
@@ -563,7 +593,7 @@ const argv = yargs(hideBin(process.argv))
563
593
  async function main() {
564
594
  const startTime = Date.now();
565
595
  const operationStats: Record<string, number> = {};
566
-
596
+
567
597
  // Early session detection for better user guidance
568
598
  const availableSessions = getAvailableSessions();
569
599
  let hasAnyValidSessions = availableSessions.length > 0;
@@ -576,17 +606,43 @@ async function main() {
576
606
  let directConfig: any = undefined;
577
607
 
578
608
  // Show authentication status on startup if no config provided
579
- if (!argv.config && !argv.endpoint && !argv.projectId && !argv.apiKey && !argv.useSession && !argv.sessionCookie) {
609
+ if (
610
+ !argv.config &&
611
+ !argv.endpoint &&
612
+ !argv.projectId &&
613
+ !argv.apiKey &&
614
+ !argv.useSession &&
615
+ !argv.sessionCookie
616
+ ) {
580
617
  if (hasAnyValidSessions) {
581
- MessageFormatter.info(`Found ${availableSessions.length} available session(s)`, { prefix: "Auth" });
582
- availableSessions.forEach(session => {
583
- MessageFormatter.info(` \u2022 ${session.projectId} (${session.email || 'unknown'}) at ${session.endpoint}`, { prefix: "Auth" });
618
+ MessageFormatter.info(
619
+ `Found ${availableSessions.length} available session(s)`,
620
+ { prefix: "Auth" }
621
+ );
622
+ availableSessions.forEach((session) => {
623
+ MessageFormatter.info(
624
+ ` \u2022 ${session.projectId} (${session.email || "unknown"}) at ${
625
+ session.endpoint
626
+ }`,
627
+ { prefix: "Auth" }
628
+ );
584
629
  });
585
- MessageFormatter.info("Use --session to enable session authentication", { prefix: "Auth" });
630
+ MessageFormatter.info(
631
+ "Use --session to enable session authentication",
632
+ { prefix: "Auth" }
633
+ );
586
634
  } else {
587
- MessageFormatter.info("No active Appwrite sessions found", { prefix: "Auth" });
588
- MessageFormatter.info("\u2022 Run 'appwrite login' to authenticate with session", { prefix: "Auth" });
589
- MessageFormatter.info("\u2022 Or provide --apiKey for API key authentication", { prefix: "Auth" });
635
+ MessageFormatter.info("No active Appwrite sessions found", {
636
+ prefix: "Auth",
637
+ });
638
+ MessageFormatter.info(
639
+ "\u2022 Run 'appwrite login' to authenticate with session",
640
+ { prefix: "Auth" }
641
+ );
642
+ MessageFormatter.info(
643
+ "\u2022 Or provide --apiKey for API key authentication",
644
+ { prefix: "Auth" }
645
+ );
590
646
  }
591
647
  }
592
648
 
@@ -596,12 +652,21 @@ async function main() {
596
652
  const projectConfig = loadAppwriteProjectConfig(projectConfigPath);
597
653
  if (projectConfig) {
598
654
  directConfig = projectConfigToAppwriteConfig(projectConfig);
599
- MessageFormatter.info(`Loaded project configuration from ${projectConfigPath}`, { prefix: "CLI" });
655
+ MessageFormatter.info(
656
+ `Loaded project configuration from ${projectConfigPath}`,
657
+ { prefix: "CLI" }
658
+ );
600
659
  }
601
660
  }
602
661
 
603
662
  // Priority 2: CLI arguments override project config
604
- if (argv.endpoint || argv.projectId || argv.apiKey || argv.useSession || argv.sessionCookie) {
663
+ if (
664
+ argv.endpoint ||
665
+ argv.projectId ||
666
+ argv.apiKey ||
667
+ argv.useSession ||
668
+ argv.sessionCookie
669
+ ) {
605
670
  directConfig = {
606
671
  ...directConfig,
607
672
  appwriteEndpoint: argv.endpoint || directConfig?.appwriteEndpoint,
@@ -614,31 +679,64 @@ async function main() {
614
679
  let sessionAuthAvailable = false;
615
680
 
616
681
  if (directConfig?.appwriteEndpoint && directConfig?.appwriteProject) {
617
- sessionAuthAvailable = hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
682
+ sessionAuthAvailable = hasSessionAuth(
683
+ directConfig.appwriteEndpoint,
684
+ directConfig.appwriteProject
685
+ );
618
686
  }
619
687
 
620
688
  if (argv.useSession || argv.sessionCookie) {
621
689
  if (argv.sessionCookie) {
622
690
  // Explicit session cookie provided
623
- MessageFormatter.info("Using explicit session cookie for authentication", { prefix: "Auth" });
691
+ MessageFormatter.info(
692
+ "Using explicit session cookie for authentication",
693
+ { prefix: "Auth" }
694
+ );
624
695
  } else if (sessionAuthAvailable) {
625
- MessageFormatter.info("Session authentication detected and will be used", { prefix: "Auth" });
696
+ MessageFormatter.info(
697
+ "Session authentication detected and will be used",
698
+ { prefix: "Auth" }
699
+ );
626
700
  } else {
627
- MessageFormatter.warning("Session authentication requested but no valid session found", { prefix: "Auth" });
701
+ MessageFormatter.warning(
702
+ "Session authentication requested but no valid session found",
703
+ { prefix: "Auth" }
704
+ );
628
705
  const availableSessions = getAvailableSessions();
629
706
  if (availableSessions.length > 0) {
630
- MessageFormatter.info(`Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
631
- MessageFormatter.info("Use --session flag to enable session authentication", { prefix: "Auth" });
707
+ MessageFormatter.info(
708
+ `Available sessions: ${availableSessions
709
+ .map((s) => `${s.projectId} (${s.email || "unknown"})`)
710
+ .join(", ")}`,
711
+ { prefix: "Auth" }
712
+ );
713
+ MessageFormatter.info(
714
+ "Use --session flag to enable session authentication",
715
+ { prefix: "Auth" }
716
+ );
632
717
  } else {
633
- MessageFormatter.warning("No Appwrite CLI sessions found. Please run 'appwrite login' first.", { prefix: "Auth" });
718
+ MessageFormatter.warning(
719
+ "No Appwrite CLI sessions found. Please run 'appwrite login' first.",
720
+ { prefix: "Auth" }
721
+ );
634
722
  }
635
- MessageFormatter.error("Session authentication requested but not available", undefined, { prefix: "Auth" });
723
+ MessageFormatter.error(
724
+ "Session authentication requested but not available",
725
+ undefined,
726
+ { prefix: "Auth" }
727
+ );
636
728
  return; // Exit early if session auth was requested but not available
637
729
  }
638
730
  } else if (sessionAuthAvailable && !argv.apiKey) {
639
731
  // Auto-detect session authentication when no API key is provided
640
- MessageFormatter.info("Session authentication detected - no API key required", { prefix: "Auth" });
641
- MessageFormatter.info("Use --session flag to explicitly enable session authentication", { prefix: "Auth" });
732
+ MessageFormatter.info(
733
+ "Session authentication detected - no API key required",
734
+ { prefix: "Auth" }
735
+ );
736
+ MessageFormatter.info(
737
+ "Use --session flag to explicitly enable session authentication",
738
+ { prefix: "Auth" }
739
+ );
642
740
  }
643
741
 
644
742
  // Enhanced session authentication support:
@@ -647,22 +745,40 @@ async function main() {
647
745
  // 3. Auto-detect session authentication when possible
648
746
  let finalDirectConfig = directConfig;
649
747
 
650
- if ((argv.useSession || argv.sessionCookie) &&
651
- (!directConfig || !directConfig.appwriteEndpoint || !directConfig.appwriteProject)) {
748
+ if (
749
+ (argv.useSession || argv.sessionCookie) &&
750
+ (!directConfig ||
751
+ !directConfig.appwriteEndpoint ||
752
+ !directConfig.appwriteProject)
753
+ ) {
652
754
  // Don't pass incomplete directConfig - let UtilsController load YAML config normally
653
755
  finalDirectConfig = null;
654
- } else if (finalDirectConfig && !finalDirectConfig.appwriteKey && !argv.useSession && !argv.sessionCookie) {
756
+ } else if (
757
+ finalDirectConfig &&
758
+ !finalDirectConfig.appwriteKey &&
759
+ !argv.useSession &&
760
+ !argv.sessionCookie
761
+ ) {
655
762
  // Auto-detect session authentication when no API key provided
656
763
  if (sessionAuthAvailable) {
657
- MessageFormatter.info("No API key provided, but session authentication is available", { prefix: "Auth" });
658
- MessageFormatter.info("Automatically using session authentication (add --session to suppress this message)", { prefix: "Auth" });
764
+ MessageFormatter.info(
765
+ "No API key provided, but session authentication is available",
766
+ { prefix: "Auth" }
767
+ );
768
+ MessageFormatter.info(
769
+ "Automatically using session authentication (add --session to suppress this message)",
770
+ { prefix: "Auth" }
771
+ );
659
772
  // Implicitly enable session authentication
660
773
  argv.useSession = true;
661
774
  }
662
775
  }
663
776
 
664
777
  // Create controller with session authentication support using singleton
665
- const controller = UtilsController.getInstance(process.cwd(), finalDirectConfig);
778
+ const controller = UtilsController.getInstance(
779
+ process.cwd(),
780
+ finalDirectConfig
781
+ );
666
782
 
667
783
  // Pass session authentication options to the controller
668
784
  const initOptions: any = {};
@@ -687,43 +803,57 @@ async function main() {
687
803
  }
688
804
 
689
805
  if (argv.generateConstants) {
690
- const { ConstantsGenerator } = await import("./utils/constantsGenerator.js");
691
- type SupportedLanguage = import("./utils/constantsGenerator.js").SupportedLanguage;
692
-
806
+ const { ConstantsGenerator } = await import(
807
+ "./utils/constantsGenerator.js"
808
+ );
809
+ type SupportedLanguage =
810
+ import("./utils/constantsGenerator.js").SupportedLanguage;
811
+
693
812
  if (!controller.config) {
694
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Constants" });
813
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
814
+ prefix: "Constants",
815
+ });
695
816
  return;
696
817
  }
697
818
 
698
- const languages = argv.constantsLanguages!.split(",").map(l => l.trim()) as SupportedLanguage[];
699
-
819
+ const languages = argv
820
+ .constantsLanguages!.split(",")
821
+ .map((l) => l.trim()) as SupportedLanguage[];
822
+
700
823
  // Determine output directory - use config folder/constants by default, or custom path if specified
701
824
  let outputDir: string;
702
825
  if (argv.constantsOutput === "auto") {
703
826
  // Default case: use config directory + constants, fallback to current directory
704
827
  const configPath = controller.getAppwriteFolderPath();
705
- outputDir = configPath
828
+ outputDir = configPath
706
829
  ? path.join(configPath, "constants")
707
830
  : path.join(process.cwd(), "constants");
708
831
  } else {
709
832
  // Custom output directory specified
710
833
  outputDir = argv.constantsOutput!;
711
834
  }
712
-
713
- MessageFormatter.info(`Generating constants for languages: ${languages.join(", ")}`, { prefix: "Constants" });
714
-
835
+
836
+ MessageFormatter.info(
837
+ `Generating constants for languages: ${languages.join(", ")}`,
838
+ { prefix: "Constants" }
839
+ );
840
+
715
841
  const generator = new ConstantsGenerator(controller.config);
716
842
  await generator.generateFiles(languages, outputDir);
717
-
843
+
718
844
  operationStats.generatedConstants = languages.length;
719
- MessageFormatter.success(`Constants generated in ${outputDir}`, { prefix: "Constants" });
845
+ MessageFormatter.success(`Constants generated in ${outputDir}`, {
846
+ prefix: "Constants",
847
+ });
720
848
  return;
721
849
  }
722
850
 
723
851
  if (argv.migrateCollectionsToTables) {
724
852
  try {
725
853
  if (!controller.config) {
726
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Migration" });
854
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
855
+ prefix: "Migration",
856
+ });
727
857
  return;
728
858
  }
729
859
 
@@ -735,8 +865,15 @@ async function main() {
735
865
  if (fs.existsSync(defaultPath)) {
736
866
  configPath = defaultPath;
737
867
  } else {
738
- MessageFormatter.error("Could not determine configuration folder path", undefined, { prefix: "Migration" });
739
- MessageFormatter.info("Make sure you have a .appwrite/ folder in your current directory", { prefix: "Migration" });
868
+ MessageFormatter.error(
869
+ "Could not determine configuration folder path",
870
+ undefined,
871
+ { prefix: "Migration" }
872
+ );
873
+ MessageFormatter.info(
874
+ "Make sure you have a .appwrite/ folder in your current directory",
875
+ { prefix: "Migration" }
876
+ );
740
877
  return;
741
878
  }
742
879
  }
@@ -744,32 +881,62 @@ async function main() {
744
881
  // Check if migration conditions are met
745
882
  const migrationCheck = checkMigrationConditions(configPath);
746
883
  if (!migrationCheck.allowed) {
747
- MessageFormatter.error(`Migration not allowed: ${migrationCheck.reason}`, undefined, { prefix: "Migration" });
748
- MessageFormatter.info("Migration requirements:", { prefix: "Migration" });
749
- MessageFormatter.info(" • Configuration must be loaded (use --config or have .appwrite/ folder)", { prefix: "Migration" });
750
- MessageFormatter.info(" • collections/ folder must exist with YAML files", { prefix: "Migration" });
751
- MessageFormatter.info(" • tables/ folder must not exist or be empty", { prefix: "Migration" });
884
+ MessageFormatter.error(
885
+ `Migration not allowed: ${migrationCheck.reason}`,
886
+ undefined,
887
+ { prefix: "Migration" }
888
+ );
889
+ MessageFormatter.info("Migration requirements:", {
890
+ prefix: "Migration",
891
+ });
892
+ MessageFormatter.info(
893
+ " • Configuration must be loaded (use --config or have .appwrite/ folder)",
894
+ { prefix: "Migration" }
895
+ );
896
+ MessageFormatter.info(
897
+ " • collections/ folder must exist with YAML files",
898
+ { prefix: "Migration" }
899
+ );
900
+ MessageFormatter.info(
901
+ " • tables/ folder must not exist or be empty",
902
+ { prefix: "Migration" }
903
+ );
752
904
  return;
753
905
  }
754
906
 
755
- const { migrateCollectionsToTables } = await import("./config/configMigration.js");
907
+ const { migrateCollectionsToTables } = await import(
908
+ "./config/configMigration.js"
909
+ );
756
910
 
757
- MessageFormatter.info("Starting collections to tables migration...", { prefix: "Migration" });
911
+ MessageFormatter.info("Starting collections to tables migration...", {
912
+ prefix: "Migration",
913
+ });
758
914
  const result = migrateCollectionsToTables(controller.config, {
759
915
  strategy: "full_migration",
760
916
  validateResult: true,
761
- dryRun: false
917
+ dryRun: false,
762
918
  });
763
919
 
764
920
  if (result.success) {
765
921
  operationStats.migratedCollections = result.changes.length;
766
- MessageFormatter.success("Collections migration completed successfully", { prefix: "Migration" });
922
+ MessageFormatter.success(
923
+ "Collections migration completed successfully",
924
+ { prefix: "Migration" }
925
+ );
767
926
  } else {
768
- MessageFormatter.error(`Migration failed: ${result.errors.join(", ")}`, undefined, { prefix: "Migration" });
927
+ MessageFormatter.error(
928
+ `Migration failed: ${result.errors.join(", ")}`,
929
+ undefined,
930
+ { prefix: "Migration" }
931
+ );
769
932
  process.exit(1);
770
933
  }
771
934
  } catch (error) {
772
- MessageFormatter.error("Migration failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
935
+ MessageFormatter.error(
936
+ "Migration failed",
937
+ error instanceof Error ? error : new Error(String(error)),
938
+ { prefix: "Migration" }
939
+ );
773
940
  process.exit(1);
774
941
  }
775
942
  return;
@@ -780,17 +947,41 @@ async function main() {
780
947
  const availableSessions = getAvailableSessions();
781
948
 
782
949
  if (availableSessions.length > 0) {
783
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
784
- MessageFormatter.info("Available authentication options:", { prefix: "Auth" });
785
- MessageFormatter.info("• Session authentication: Add --session flag", { prefix: "Auth" });
786
- MessageFormatter.info(" API key authentication: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
787
- MessageFormatter.info(`• Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
950
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
951
+ prefix: "CLI",
952
+ });
953
+ MessageFormatter.info("Available authentication options:", {
954
+ prefix: "Auth",
955
+ });
956
+ MessageFormatter.info("• Session authentication: Add --session flag", {
957
+ prefix: "Auth",
958
+ });
959
+ MessageFormatter.info(
960
+ "• API key authentication: Add --apiKey YOUR_API_KEY",
961
+ { prefix: "Auth" }
962
+ );
963
+ MessageFormatter.info(
964
+ `• Available sessions: ${availableSessions
965
+ .map((s) => `${s.projectId} (${s.email || "unknown"})`)
966
+ .join(", ")}`,
967
+ { prefix: "Auth" }
968
+ );
788
969
  } else {
789
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
970
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
971
+ prefix: "CLI",
972
+ });
790
973
  MessageFormatter.info("Authentication options:", { prefix: "Auth" });
791
- MessageFormatter.info("• Login with Appwrite CLI: Run 'appwrite login' then use --session flag", { prefix: "Auth" });
792
- MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
793
- MessageFormatter.info("• Create config file: Run with --setup to initialize project configuration", { prefix: "Auth" });
974
+ MessageFormatter.info(
975
+ "• Login with Appwrite CLI: Run 'appwrite login' then use --session flag",
976
+ { prefix: "Auth" }
977
+ );
978
+ MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", {
979
+ prefix: "Auth",
980
+ });
981
+ MessageFormatter.info(
982
+ "• Create config file: Run with --setup to initialize project configuration",
983
+ { prefix: "Auth" }
984
+ );
794
985
  }
795
986
  return;
796
987
  }
@@ -803,14 +994,16 @@ async function main() {
803
994
  const { listBackups } = await import("./shared/backupTracking.js");
804
995
 
805
996
  if (!controller.config) {
806
- MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Backups" });
997
+ MessageFormatter.error("No Appwrite configuration found", undefined, {
998
+ prefix: "Backups",
999
+ });
807
1000
  return;
808
1001
  }
809
1002
 
810
1003
  const { adapter } = await AdapterFactory.create({
811
1004
  appwriteEndpoint: controller.config.appwriteEndpoint,
812
1005
  appwriteProject: controller.config.appwriteProject,
813
- appwriteKey: controller.config.appwriteKey
1006
+ appwriteKey: controller.config.appwriteKey,
814
1007
  });
815
1008
 
816
1009
  const databases = parsedArgv.dbIds
@@ -825,7 +1018,10 @@ async function main() {
825
1018
  for (const db of databases!) {
826
1019
  const backups = await listBackups(adapter, db.$id);
827
1020
 
828
- MessageFormatter.info(`\nBackups for database: ${db.name} (${db.$id})`, { prefix: "Backups" });
1021
+ MessageFormatter.info(
1022
+ `\nBackups for database: ${db.name} (${db.$id})`,
1023
+ { prefix: "Backups" }
1024
+ );
829
1025
 
830
1026
  if (backups.length === 0) {
831
1027
  MessageFormatter.info(" No backups found", { prefix: "Backups" });
@@ -834,7 +1030,11 @@ async function main() {
834
1030
  const date = new Date(backup.$createdAt).toLocaleString();
835
1031
  const size = MessageFormatter.formatBytes(backup.sizeBytes);
836
1032
  MessageFormatter.info(
837
- ` ${index + 1}. ${date} - ${backup.format.toUpperCase()} - ${size} - ${backup.collections} collections, ${backup.documents} documents`,
1033
+ ` ${
1034
+ index + 1
1035
+ }. ${date} - ${backup.format.toUpperCase()} - ${size} - ${
1036
+ backup.collections
1037
+ } collections, ${backup.documents} documents`,
838
1038
  { prefix: "Backups" }
839
1039
  );
840
1040
  });
@@ -893,12 +1093,20 @@ async function main() {
893
1093
  }
894
1094
 
895
1095
  // Add default databases if not specified (only if we need them for operations)
896
- const needsDatabases = options.doBackup || options.wipeDatabase ||
897
- options.wipeDocumentStorage || options.wipeUsers ||
898
- options.wipeCollections || options.importData ||
899
- parsedArgv.sync || parsedArgv.transfer;
1096
+ const needsDatabases =
1097
+ options.doBackup ||
1098
+ options.wipeDatabase ||
1099
+ options.wipeDocumentStorage ||
1100
+ options.wipeUsers ||
1101
+ options.wipeCollections ||
1102
+ options.importData ||
1103
+ parsedArgv.sync ||
1104
+ parsedArgv.transfer;
900
1105
 
901
- if (needsDatabases && (!options.databases || options.databases.length === 0)) {
1106
+ if (
1107
+ needsDatabases &&
1108
+ (!options.databases || options.databases.length === 0)
1109
+ ) {
902
1110
  const allDatabases = await fetchAllDatabases(controller.database!);
903
1111
  options.databases = allDatabases;
904
1112
  }
@@ -906,7 +1114,9 @@ async function main() {
906
1114
  // Add default collections if not specified
907
1115
  if (!options.collections || options.collections.length === 0) {
908
1116
  if (controller.config && controller.config.collections) {
909
- options.collections = controller.config.collections.map((c) => c.name);
1117
+ options.collections = controller.config.collections.map(
1118
+ (c: any) => c.name
1119
+ );
910
1120
  } else {
911
1121
  options.collections = [];
912
1122
  }
@@ -914,7 +1124,9 @@ async function main() {
914
1124
 
915
1125
  // Comprehensive backup (all databases + all buckets)
916
1126
  if (parsedArgv.comprehensiveBackup) {
917
- const { comprehensiveBackup } = await import("./backups/operations/comprehensiveBackup.js");
1127
+ const { comprehensiveBackup } = await import(
1128
+ "./backups/operations/comprehensiveBackup.js"
1129
+ );
918
1130
  const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
919
1131
 
920
1132
  // Get tracking database ID (interactive prompt if not specified)
@@ -925,42 +1137,55 @@ async function main() {
925
1137
  const allDatabases = await fetchAllDatabases(controller.database!);
926
1138
 
927
1139
  if (allDatabases.length === 0) {
928
- MessageFormatter.error("No databases found. Cannot create comprehensive backup without a tracking database.", undefined, { prefix: "Backup" });
1140
+ MessageFormatter.error(
1141
+ "No databases found. Cannot create comprehensive backup without a tracking database.",
1142
+ undefined,
1143
+ { prefix: "Backup" }
1144
+ );
929
1145
  return;
930
1146
  }
931
1147
 
932
1148
  if (allDatabases.length === 1) {
933
1149
  trackingDatabaseId = allDatabases[0].$id;
934
- MessageFormatter.info(`Using only available database for tracking: ${allDatabases[0].name} (${trackingDatabaseId})`, { prefix: "Backup" });
1150
+ MessageFormatter.info(
1151
+ `Using only available database for tracking: ${allDatabases[0].name} (${trackingDatabaseId})`,
1152
+ { prefix: "Backup" }
1153
+ );
935
1154
  } else {
936
1155
  // Interactive selection
937
1156
  const inquirer = (await import("inquirer")).default;
938
- const answer = await inquirer.prompt([{
939
- type: 'list',
940
- name: 'trackingDb',
941
- message: 'Select database to store backup tracking metadata:',
942
- choices: allDatabases.map(db => ({
943
- name: `${db.name} (${db.$id})`,
944
- value: db.$id
945
- }))
946
- }]);
1157
+ const answer = await inquirer.prompt([
1158
+ {
1159
+ type: "list",
1160
+ name: "trackingDb",
1161
+ message: "Select database to store backup tracking metadata:",
1162
+ choices: allDatabases.map((db) => ({
1163
+ name: `${db.name} (${db.$id})`,
1164
+ value: db.$id,
1165
+ })),
1166
+ },
1167
+ ]);
947
1168
  trackingDatabaseId = answer.trackingDb;
948
1169
  }
949
1170
  }
950
1171
 
951
1172
  // Ensure trackingDatabaseId is defined before proceeding
952
1173
  if (!trackingDatabaseId) {
953
- throw new Error('Tracking database ID is required for comprehensive backup');
1174
+ throw new Error(
1175
+ "Tracking database ID is required for comprehensive backup"
1176
+ );
954
1177
  }
955
1178
 
956
- MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, { prefix: "Backup" });
1179
+ MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, {
1180
+ prefix: "Backup",
1181
+ });
957
1182
 
958
1183
  // Create adapter for backup tracking
959
1184
  const { adapter } = await AdapterFactory.create({
960
1185
  appwriteEndpoint: controller.config!.appwriteEndpoint,
961
1186
  appwriteProject: controller.config!.appwriteProject,
962
1187
  appwriteKey: controller.config!.appwriteKey,
963
- sessionCookie: controller.config!.sessionCookie
1188
+ sessionCookie: controller.config!.sessionCookie,
964
1189
  });
965
1190
 
966
1191
  const result = await comprehensiveBackup(
@@ -970,11 +1195,11 @@ async function main() {
970
1195
  adapter,
971
1196
  {
972
1197
  trackingDatabaseId,
973
- backupFormat: parsedArgv.backupFormat || 'zip',
1198
+ backupFormat: parsedArgv.backupFormat || "zip",
974
1199
  parallelDownloads: parsedArgv.parallelDownloads || 10,
975
1200
  onProgress: (message) => {
976
1201
  MessageFormatter.info(message, { prefix: "Backup" });
977
- }
1202
+ },
978
1203
  }
979
1204
  );
980
1205
 
@@ -983,24 +1208,44 @@ async function main() {
983
1208
  operationStats.bucketsBackedUp = result.bucketBackups.length;
984
1209
  operationStats.totalBackupSize = result.totalSizeBytes;
985
1210
 
986
- if (result.status === 'completed') {
987
- MessageFormatter.success(`Comprehensive backup completed successfully (ID: ${result.backupId})`, { prefix: "Backup" });
988
- } else if (result.status === 'partial') {
989
- MessageFormatter.warning(`Comprehensive backup completed with errors (ID: ${result.backupId})`, { prefix: "Backup" });
990
- result.errors.forEach(err => MessageFormatter.warning(err, { prefix: "Backup" }));
1211
+ if (result.status === "completed") {
1212
+ MessageFormatter.success(
1213
+ `Comprehensive backup completed successfully (ID: ${result.backupId})`,
1214
+ { prefix: "Backup" }
1215
+ );
1216
+ } else if (result.status === "partial") {
1217
+ MessageFormatter.warning(
1218
+ `Comprehensive backup completed with errors (ID: ${result.backupId})`,
1219
+ { prefix: "Backup" }
1220
+ );
1221
+ result.errors.forEach((err) =>
1222
+ MessageFormatter.warning(err, { prefix: "Backup" })
1223
+ );
991
1224
  } else {
992
- MessageFormatter.error(`Comprehensive backup failed (ID: ${result.backupId})`, undefined, { prefix: "Backup" });
993
- result.errors.forEach(err => MessageFormatter.error(err, undefined, { prefix: "Backup" }));
1225
+ MessageFormatter.error(
1226
+ `Comprehensive backup failed (ID: ${result.backupId})`,
1227
+ undefined,
1228
+ { prefix: "Backup" }
1229
+ );
1230
+ result.errors.forEach((err) =>
1231
+ MessageFormatter.error(err, undefined, { prefix: "Backup" })
1232
+ );
994
1233
  }
995
1234
  }
996
1235
 
997
1236
  if (options.doBackup && options.databases) {
998
- MessageFormatter.info(`Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`, { prefix: "Backup" });
1237
+ MessageFormatter.info(
1238
+ `Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`,
1239
+ { prefix: "Backup" }
1240
+ );
999
1241
  for (const db of options.databases) {
1000
- await controller.backupDatabase(db, parsedArgv.backupFormat || 'json');
1242
+ await controller.backupDatabase(db, parsedArgv.backupFormat || "json");
1001
1243
  }
1002
1244
  operationStats.backups = options.databases.length;
1003
- MessageFormatter.success(`Backup completed for ${options.databases.length} database(s)`, { prefix: "Backup" });
1245
+ MessageFormatter.success(
1246
+ `Backup completed for ${options.databases.length} database(s)`,
1247
+ { prefix: "Backup" }
1248
+ );
1004
1249
  }
1005
1250
 
1006
1251
  if (
@@ -1010,19 +1255,22 @@ async function main() {
1010
1255
  options.wipeCollections
1011
1256
  ) {
1012
1257
  // Confirm destructive operations
1013
- const databaseNames = options.databases?.map(db => db.name) || [];
1014
- const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(databaseNames, {
1015
- includeStorage: options.wipeDocumentStorage,
1016
- includeUsers: options.wipeUsers
1017
- });
1018
-
1258
+ const databaseNames = options.databases?.map((db) => db.name) || [];
1259
+ const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(
1260
+ databaseNames,
1261
+ {
1262
+ includeStorage: options.wipeDocumentStorage,
1263
+ includeUsers: options.wipeUsers,
1264
+ }
1265
+ );
1266
+
1019
1267
  if (!confirmed) {
1020
1268
  MessageFormatter.info("Operation cancelled by user", { prefix: "CLI" });
1021
1269
  return;
1022
1270
  }
1023
-
1271
+
1024
1272
  let wipeStats = { databases: 0, collections: 0, users: 0, buckets: 0 };
1025
-
1273
+
1026
1274
  if (parsedArgv.wipe === "all") {
1027
1275
  if (options.databases) {
1028
1276
  for (const db of options.databases) {
@@ -1061,14 +1309,15 @@ async function main() {
1061
1309
  const collectionsToWipe = dbCollections.filter((c) =>
1062
1310
  options.collections!.includes(c.$id)
1063
1311
  );
1064
-
1312
+
1065
1313
  // Confirm collection wipe
1066
- const collectionNames = collectionsToWipe.map(c => c.name);
1067
- const collectionConfirmed = await ConfirmationDialogs.confirmCollectionWipe(
1068
- db.name,
1069
- collectionNames
1070
- );
1071
-
1314
+ const collectionNames = collectionsToWipe.map((c) => c.name);
1315
+ const collectionConfirmed =
1316
+ await ConfirmationDialogs.confirmCollectionWipe(
1317
+ db.name,
1318
+ collectionNames
1319
+ );
1320
+
1072
1321
  if (collectionConfirmed) {
1073
1322
  for (const collection of collectionsToWipe) {
1074
1323
  await controller.wipeCollection(db, collection);
@@ -1077,9 +1326,14 @@ async function main() {
1077
1326
  }
1078
1327
  }
1079
1328
  }
1080
-
1329
+
1081
1330
  // Show wipe operation summary
1082
- if (wipeStats.databases > 0 || wipeStats.collections > 0 || wipeStats.users > 0 || wipeStats.buckets > 0) {
1331
+ if (
1332
+ wipeStats.databases > 0 ||
1333
+ wipeStats.collections > 0 ||
1334
+ wipeStats.users > 0 ||
1335
+ wipeStats.buckets > 0
1336
+ ) {
1083
1337
  operationStats.wipedDatabases = wipeStats.databases;
1084
1338
  operationStats.wipedCollections = wipeStats.collections;
1085
1339
  operationStats.wipedUsers = wipeStats.users;
@@ -1087,16 +1341,103 @@ async function main() {
1087
1341
  }
1088
1342
  }
1089
1343
 
1090
- if (parsedArgv.push) {
1091
- // PUSH: Use LOCAL config collections only (pass empty array to use config.collections)
1092
- const databases =
1093
- options.databases || (await fetchAllDatabases(controller.database!));
1094
-
1095
- // Pass empty array - syncDb will use config.collections (local schema)
1096
- await controller.syncDb(databases, []);
1097
- operationStats.pushedDatabases = databases.length;
1098
- operationStats.pushedCollections = controller.config?.collections?.length || 0;
1099
- } else if (parsedArgv.sync) {
1344
+ if (parsedArgv.push) {
1345
+ await controller.init();
1346
+ if (!controller.database || !controller.config) {
1347
+ MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
1348
+ return;
1349
+ }
1350
+
1351
+ // Fetch available DBs
1352
+ const availableDatabases = await fetchAllDatabases(controller.database);
1353
+ if (availableDatabases.length === 0) {
1354
+ MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
1355
+ return;
1356
+ }
1357
+
1358
+ // Determine selected DBs
1359
+ let selectedDbIds: string[] = [];
1360
+ if (parsedArgv.dbIds) {
1361
+ selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
1362
+ } else {
1363
+ selectedDbIds = await SelectionDialogs.selectDatabases(
1364
+ availableDatabases,
1365
+ controller.config.databases || [],
1366
+ { showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
1367
+ );
1368
+ }
1369
+
1370
+ if (selectedDbIds.length === 0) {
1371
+ MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
1372
+ return;
1373
+ }
1374
+
1375
+ // Build DatabaseSelection[] with tableIds per DB
1376
+ const databaseSelections: DatabaseSelection[] = [];
1377
+ const allConfigItems = controller.config.collections || controller.config.tables || [];
1378
+
1379
+ for (const dbId of selectedDbIds) {
1380
+ const db = availableDatabases.find(d => d.$id === dbId);
1381
+ if (!db) continue;
1382
+
1383
+ // Filter config items eligible for this DB according to databaseId/databaseIds rule
1384
+ const eligibleConfigItems = (allConfigItems as any[]).filter(item => {
1385
+ const one = item.databaseId as string | undefined;
1386
+ const many = item.databaseIds as string[] | undefined;
1387
+ if (Array.isArray(many) && many.length > 0) return many.includes(dbId);
1388
+ if (one) return one === dbId;
1389
+ return true; // eligible everywhere if unspecified
1390
+ });
1391
+
1392
+ // Fetch available tables from remote for selection context
1393
+ const availableTables = await fetchAllCollections(dbId, controller.database);
1394
+
1395
+ // Determine selected table IDs
1396
+ let selectedTableIds: string[] = [];
1397
+ if (parsedArgv.collectionIds) {
1398
+ const ids = parsedArgv.collectionIds.split(/[,\s]+/).filter(Boolean);
1399
+ // Only allow IDs that are in eligible config items
1400
+ const eligibleIds = new Set(eligibleConfigItems.map((c: any) => c.$id || c.id));
1401
+ selectedTableIds = ids.filter(id => eligibleIds.has(id));
1402
+ } else {
1403
+ selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
1404
+ dbId,
1405
+ db.name,
1406
+ availableTables,
1407
+ eligibleConfigItems,
1408
+ { showSelectAll: false, allowNewOnly: true, defaultSelected: [] }
1409
+ );
1410
+ }
1411
+
1412
+ databaseSelections.push({
1413
+ databaseId: db.$id,
1414
+ databaseName: db.name,
1415
+ tableIds: selectedTableIds,
1416
+ tableNames: [],
1417
+ isNew: false,
1418
+ });
1419
+ }
1420
+
1421
+ if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
1422
+ MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
1423
+ return;
1424
+ }
1425
+
1426
+ const pushSummary: Record<string, string | number | string[]> = {
1427
+ databases: databaseSelections.length,
1428
+ collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
1429
+ details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
1430
+ };
1431
+ const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
1432
+ if (!confirmed) {
1433
+ MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
1434
+ return;
1435
+ }
1436
+
1437
+ await controller.selectivePush(databaseSelections, []);
1438
+ operationStats.pushedDatabases = databaseSelections.length;
1439
+ operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
1440
+ } else if (parsedArgv.sync) {
1100
1441
  // Enhanced SYNC: Pull from remote with intelligent configuration detection
1101
1442
  if (parsedArgv.autoSync) {
1102
1443
  // Legacy behavior: sync everything without prompts
@@ -1142,7 +1483,9 @@ async function main() {
1142
1483
  await controller.getDatabasesByIds([parsedArgv.fromDbId])
1143
1484
  )?.[0];
1144
1485
  if (!fromDb) {
1145
- MessageFormatter.error("Source database not found", undefined, { prefix: "Transfer" });
1486
+ MessageFormatter.error("Source database not found", undefined, {
1487
+ prefix: "Transfer",
1488
+ });
1146
1489
  return;
1147
1490
  }
1148
1491
  if (isRemote) {
@@ -1163,19 +1506,27 @@ async function main() {
1163
1506
  const remoteDbs = await fetchAllDatabases(targetDatabases);
1164
1507
  toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
1165
1508
  if (!toDb) {
1166
- MessageFormatter.error("Target database not found", undefined, { prefix: "Transfer" });
1509
+ MessageFormatter.error("Target database not found", undefined, {
1510
+ prefix: "Transfer",
1511
+ });
1167
1512
  return;
1168
1513
  }
1169
1514
  } else {
1170
1515
  toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId]))?.[0];
1171
1516
  if (!toDb) {
1172
- MessageFormatter.error("Target database not found", undefined, { prefix: "Transfer" });
1517
+ MessageFormatter.error("Target database not found", undefined, {
1518
+ prefix: "Transfer",
1519
+ });
1173
1520
  return;
1174
1521
  }
1175
1522
  }
1176
1523
 
1177
1524
  if (!fromDb || !toDb) {
1178
- MessageFormatter.error("Source or target database not found", undefined, { prefix: "Transfer" });
1525
+ MessageFormatter.error(
1526
+ "Source or target database not found",
1527
+ undefined,
1528
+ { prefix: "Transfer" }
1529
+ );
1179
1530
  return;
1180
1531
  }
1181
1532
  }
@@ -1225,11 +1576,15 @@ async function main() {
1225
1576
  await controller.transferData(transferOptions);
1226
1577
  operationStats.transfers = 1;
1227
1578
  }
1228
-
1579
+
1229
1580
  // Show final operation summary if any operations were performed
1230
1581
  if (Object.keys(operationStats).length > 0) {
1231
1582
  const duration = Date.now() - startTime;
1232
- MessageFormatter.operationSummary("CLI Operations", operationStats, duration);
1583
+ MessageFormatter.operationSummary(
1584
+ "CLI Operations",
1585
+ operationStats,
1586
+ duration
1587
+ );
1233
1588
  }
1234
1589
  }
1235
1590
  }