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
@@ -12,11 +12,10 @@ import {
12
12
  type Specification,
13
13
  } from "appwrite-utils";
14
14
  import {
15
- loadConfig,
16
- loadConfigWithPath,
17
15
  findAppwriteConfig,
18
16
  findFunctionsDir,
19
17
  } from "./utils/loadConfigs.js";
18
+ import { normalizeFunctionName, validateFunctionDirectory } from './functions/pathResolution.js';
20
19
  import { UsersController } from "./users/methods.js";
21
20
  import { AppwriteToX } from "./migrations/appwriteToX.js";
22
21
  import { ImportController } from "./migrations/importController.js";
@@ -28,6 +27,7 @@ import {
28
27
  } from "./databases/setup.js";
29
28
  import {
30
29
  createOrUpdateCollections,
30
+ createOrUpdateCollectionsViaAdapter,
31
31
  wipeDatabase,
32
32
  generateSchemas,
33
33
  fetchAllCollections,
@@ -116,9 +116,33 @@ export class UtilsController {
116
116
  appwriteKey?: string;
117
117
  }
118
118
  ): UtilsController {
119
+ // Clear instance if currentUserDir has changed
120
+ if (UtilsController.instance &&
121
+ UtilsController.instance.currentUserDir !== currentUserDir) {
122
+ logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
123
+ UtilsController.clearInstance();
124
+ }
125
+
126
+ // Clear instance if directConfig endpoint or project has changed
127
+ if (UtilsController.instance && directConfig) {
128
+ const existingConfig = UtilsController.instance.config;
129
+ if (existingConfig) {
130
+ const endpointChanged = directConfig.appwriteEndpoint &&
131
+ existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
132
+ const projectChanged = directConfig.appwriteProject &&
133
+ existingConfig.appwriteProject !== directConfig.appwriteProject;
134
+
135
+ if (endpointChanged || projectChanged) {
136
+ logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
137
+ UtilsController.clearInstance();
138
+ }
139
+ }
140
+ }
141
+
119
142
  if (!UtilsController.instance) {
120
143
  UtilsController.instance = new UtilsController(currentUserDir, directConfig);
121
144
  }
145
+
122
146
  return UtilsController.instance;
123
147
  }
124
148
 
@@ -250,6 +274,13 @@ export class UtilsController {
250
274
  this.appwriteServer = client;
251
275
  this.adapter = adapter;
252
276
  this.config = config;
277
+
278
+ // Update config.apiMode from adapter if it's auto or not set
279
+ if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
280
+ this.config.apiMode = adapter.getApiMode();
281
+ logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
282
+ }
283
+
253
284
  this.database = new Databases(this.appwriteServer);
254
285
  this.storage = new Storage(this.appwriteServer);
255
286
  this.config.appwriteClient = this.appwriteServer;
@@ -257,7 +288,8 @@ export class UtilsController {
257
288
  // Log only on FIRST initialization to avoid spam
258
289
  if (!this.isInitialized) {
259
290
  const apiMode = adapter.getApiMode();
260
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, { prefix: "Adapter" });
291
+ const configApiMode = this.config.apiMode;
292
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
261
293
  this.isInitialized = true;
262
294
  } else {
263
295
  logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
@@ -426,10 +458,17 @@ export class UtilsController {
426
458
  for (const entry of entries) {
427
459
  if (entry.isDirectory()) {
428
460
  const functionPath = path.join(functionsDir, entry.name);
429
- // Match with config functions by name
461
+
462
+ // Validate it's a function directory
463
+ if (!validateFunctionDirectory(functionPath)) {
464
+ continue; // Skip invalid directories
465
+ }
466
+
467
+ // Match with config functions using normalized names
430
468
  if (this.config?.functions) {
469
+ const normalizedEntryName = normalizeFunctionName(entry.name);
431
470
  const matchingFunc = this.config.functions.find(
432
- (f) => f.name.toLowerCase() === entry.name.toLowerCase()
471
+ (f) => normalizeFunctionName(f.name) === normalizedEntryName
433
472
  );
434
473
  if (matchingFunc) {
435
474
  functionDirMap.set(matchingFunc.name, functionPath);
@@ -571,48 +610,76 @@ export class UtilsController {
571
610
  }
572
611
  }
573
612
 
574
- async createOrUpdateCollections(
575
- database: Models.Database,
576
- deletedCollections?: { collectionId: string; collectionName: string }[],
577
- collections: Models.Collection[] = []
578
- ) {
613
+ async createOrUpdateCollections(
614
+ database: Models.Database,
615
+ deletedCollections?: { collectionId: string; collectionName: string }[],
616
+ collections: Models.Collection[] = []
617
+ ) {
579
618
  await this.init();
580
619
  if (!this.database || !this.config)
581
620
  throw new Error("Database or config not initialized");
582
- await createOrUpdateCollections(
583
- this.database,
584
- database.$id,
585
- this.config,
586
- deletedCollections,
587
- collections
588
- );
589
- }
621
+
622
+ // Ensure apiMode is properly set from adapter
623
+ if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
624
+ this.config.apiMode = this.adapter.getApiMode();
625
+ logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
626
+ }
627
+
628
+ // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
629
+ if (this.adapter) {
630
+ logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
631
+ prefix: "UtilsController",
632
+ apiMode: this.adapter.getApiMode()
633
+ });
634
+ await createOrUpdateCollectionsViaAdapter(
635
+ this.adapter,
636
+ database.$id,
637
+ this.config,
638
+ deletedCollections,
639
+ collections
640
+ );
641
+ } else {
642
+ // Fallback if adapter is unavailable for some reason
643
+ logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
644
+ await createOrUpdateCollections(
645
+ this.database,
646
+ database.$id,
647
+ this.config,
648
+ deletedCollections,
649
+ collections
650
+ );
651
+ }
652
+ }
590
653
 
591
654
  async generateSchemas() {
592
655
  // Schema generation doesn't need Appwrite connection, just config
593
656
  if (!this.config) {
594
- if (this.appwriteFolderPath && this.appwriteConfigPath) {
595
- MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
596
- try {
597
- const { config, actualConfigPath } = await loadConfigWithPath(
598
- this.appwriteFolderPath,
599
- { validate: false, strictMode: false, reportValidation: false }
600
- );
601
- this.config = config;
602
- MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
603
- } catch (error) {
604
- MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
605
- return;
657
+ MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
658
+ try {
659
+ const configManager = ConfigManager.getInstance();
660
+
661
+ // Load config if not already loaded
662
+ if (!configManager.hasConfig()) {
663
+ await configManager.loadConfig({
664
+ configDir: this.currentUserDir,
665
+ validate: false,
666
+ strictMode: false,
667
+ });
606
668
  }
607
- } else {
608
- MessageFormatter.error("No configuration available", undefined, { prefix: "Controller" });
669
+
670
+ this.config = configManager.getConfig();
671
+ MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
672
+ } catch (error) {
673
+ MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
609
674
  return;
610
675
  }
611
676
  }
677
+
612
678
  if (!this.appwriteFolderPath) {
613
679
  MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
614
680
  return;
615
681
  }
682
+
616
683
  await generateSchemas(this.config, this.appwriteFolderPath);
617
684
  }
618
685
 
@@ -728,7 +795,7 @@ export class UtilsController {
728
795
  }
729
796
  }
730
797
 
731
- async selectiveSync(
798
+ async selectivePull(
732
799
  databaseSelections: DatabaseSelection[],
733
800
  bucketSelections: BucketSelection[]
734
801
  ): Promise<void> {
@@ -738,7 +805,7 @@ export class UtilsController {
738
805
  return;
739
806
  }
740
807
 
741
- MessageFormatter.progress("Starting selective sync...", { prefix: "Controller" });
808
+ MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
742
809
 
743
810
  // Convert database selections to Models.Database format
744
811
  const selectedDatabases: Models.Database[] = [];
@@ -762,7 +829,7 @@ export class UtilsController {
762
829
  }
763
830
 
764
831
  if (selectedDatabases.length === 0) {
765
- MessageFormatter.warning("No valid databases selected for sync", { prefix: "Controller" });
832
+ MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
766
833
  return;
767
834
  }
768
835
 
@@ -778,7 +845,106 @@ export class UtilsController {
778
845
  // Perform selective sync using the enhanced synchronizeConfigurations method
779
846
  await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
780
847
 
781
- MessageFormatter.success("Selective sync completed successfully!", { prefix: "Controller" });
848
+ MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
849
+ }
850
+
851
+ async selectivePush(
852
+ databaseSelections: DatabaseSelection[],
853
+ bucketSelections: BucketSelection[]
854
+ ): Promise<void> {
855
+ await this.init();
856
+ if (!this.database) {
857
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
858
+ return;
859
+ }
860
+
861
+ MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
862
+
863
+ // Convert database selections to Models.Database format
864
+ const selectedDatabases: Models.Database[] = [];
865
+
866
+ for (const dbSelection of databaseSelections) {
867
+ // Get the full database object from the controller
868
+ const databases = await fetchAllDatabases(this.database);
869
+ const database = databases.find(db => db.$id === dbSelection.databaseId);
870
+
871
+ if (database) {
872
+ selectedDatabases.push(database);
873
+ MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
874
+
875
+ // Log selected tables for this database
876
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
877
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
878
+ }
879
+ } else {
880
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
881
+ }
882
+ }
883
+
884
+ if (selectedDatabases.length === 0) {
885
+ MessageFormatter.warning("No valid databases selected for push", { prefix: "Controller" });
886
+ return;
887
+ }
888
+
889
+ // Log bucket selections if provided
890
+ if (bucketSelections && bucketSelections.length > 0) {
891
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
892
+ for (const bucketSelection of bucketSelections) {
893
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
894
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
895
+ }
896
+ }
897
+
898
+ // PUSH OPERATION: Push local configuration to Appwrite
899
+ // Build database-specific collection mappings from databaseSelections
900
+ const databaseCollectionsMap = new Map<string, any[]>();
901
+
902
+ // Get all collections/tables from config (they're at the root level, not nested in databases)
903
+ const allCollections = this.config?.collections || this.config?.tables || [];
904
+
905
+ // Create database-specific collection mapping to preserve relationships
906
+ for (const dbSelection of databaseSelections) {
907
+ const collectionsForDatabase: any[] = [];
908
+
909
+ MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
910
+
911
+ // Filter collections that were selected for THIS specific database
912
+ for (const collection of allCollections) {
913
+ const collectionId = collection.$id || (collection as any).id;
914
+
915
+ // Check if this collection was selected for THIS database
916
+ if (dbSelection.tableIds.includes(collectionId)) {
917
+ collectionsForDatabase.push(collection);
918
+ MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId}`, { prefix: "Controller" });
919
+ }
920
+ }
921
+
922
+ databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
923
+ MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
924
+ }
925
+
926
+ // Calculate total collections for logging
927
+ const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
928
+ .reduce((total, collections) => total + collections.length, 0);
929
+
930
+ MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
931
+
932
+ // Ensure databases exist
933
+ await this.ensureDatabasesExist(selectedDatabases);
934
+ await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
935
+
936
+ // Create/update collections with database-specific context
937
+ for (const database of selectedDatabases) {
938
+ const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
939
+ if (collectionsForThisDatabase.length > 0) {
940
+ MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
941
+ await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
942
+ } else {
943
+ MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
944
+ }
945
+ }
946
+
947
+ MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
782
948
  }
783
949
 
784
950
  async syncDb(
@@ -1,14 +0,0 @@
1
- import type { AppwriteConfig, Attribute } from "appwrite-utils";
2
- export declare class SchemaGenerator {
3
- private relationshipMap;
4
- private config;
5
- private appwriteFolderPath;
6
- constructor(config: AppwriteConfig, appwriteFolderPath: string);
7
- private resolveCollectionName;
8
- updateTsSchemas(): void;
9
- private extractRelationships;
10
- private addRelationship;
11
- generateSchemas(): void;
12
- createSchemaStringV4: (name: string, attributes: Attribute[]) => string;
13
- typeToZod: (attribute: Attribute) => string;
14
- }