appwrite-utils-cli 1.7.6 → 1.7.8

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 (35) hide show
  1. package/SELECTION_DIALOGS.md +146 -0
  2. package/dist/adapters/DatabaseAdapter.d.ts +1 -0
  3. package/dist/adapters/LegacyAdapter.js +15 -3
  4. package/dist/adapters/TablesDBAdapter.js +15 -3
  5. package/dist/cli/commands/databaseCommands.js +90 -23
  6. package/dist/collections/wipeOperations.d.ts +2 -2
  7. package/dist/collections/wipeOperations.js +37 -139
  8. package/dist/main.js +175 -4
  9. package/dist/migrations/appwriteToX.d.ts +27 -2
  10. package/dist/migrations/appwriteToX.js +293 -69
  11. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  12. package/dist/migrations/yaml/generateImportSchemas.js +23 -8
  13. package/dist/shared/schemaGenerator.js +25 -12
  14. package/dist/shared/selectionDialogs.d.ts +214 -0
  15. package/dist/shared/selectionDialogs.js +516 -0
  16. package/dist/utils/configDiscovery.d.ts +4 -4
  17. package/dist/utils/configDiscovery.js +66 -30
  18. package/dist/utils/yamlConverter.d.ts +1 -0
  19. package/dist/utils/yamlConverter.js +26 -3
  20. package/dist/utilsController.d.ts +6 -1
  21. package/dist/utilsController.js +91 -2
  22. package/package.json +1 -1
  23. package/src/adapters/DatabaseAdapter.ts +2 -1
  24. package/src/adapters/LegacyAdapter.ts +95 -82
  25. package/src/adapters/TablesDBAdapter.ts +62 -47
  26. package/src/cli/commands/databaseCommands.ts +134 -34
  27. package/src/collections/wipeOperations.ts +62 -224
  28. package/src/main.ts +276 -34
  29. package/src/migrations/appwriteToX.ts +385 -90
  30. package/src/migrations/yaml/generateImportSchemas.ts +26 -8
  31. package/src/shared/schemaGenerator.ts +29 -12
  32. package/src/shared/selectionDialogs.ts +716 -0
  33. package/src/utils/configDiscovery.ts +83 -39
  34. package/src/utils/yamlConverter.ts +29 -3
  35. package/src/utilsController.ts +116 -4
package/dist/main.js CHANGED
@@ -13,6 +13,8 @@ import chalk from "chalk";
13
13
  import { listSpecifications } from "./functions/methods.js";
14
14
  import { MessageFormatter } from "./shared/messageFormatter.js";
15
15
  import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
16
+ import { SelectionDialogs } from "./shared/selectionDialogs.js";
17
+ import { logger } from "./shared/logging.js";
16
18
  import path from "path";
17
19
  import fs from "fs";
18
20
  import { createRequire } from "node:module";
@@ -23,6 +25,153 @@ const require = createRequire(import.meta.url);
23
25
  if (!globalThis.require) {
24
26
  globalThis.require = require;
25
27
  }
28
+ /**
29
+ * Enhanced sync function with intelligent configuration detection and selection dialogs
30
+ */
31
+ async function performEnhancedSync(controller, parsedArgv) {
32
+ try {
33
+ MessageFormatter.banner("Enhanced Sync", "Intelligent configuration detection and selection");
34
+ if (!controller.config) {
35
+ MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Sync" });
36
+ return null;
37
+ }
38
+ // Get all available databases from remote
39
+ const availableDatabases = await fetchAllDatabases(controller.database);
40
+ if (availableDatabases.length === 0) {
41
+ MessageFormatter.warning("No databases found in remote project", { prefix: "Sync" });
42
+ return null;
43
+ }
44
+ // Get existing configuration
45
+ const configuredDatabases = controller.config.databases || [];
46
+ const configuredBuckets = controller.config.buckets || [];
47
+ // Check if we have existing configuration
48
+ const hasExistingConfig = configuredDatabases.length > 0 || configuredBuckets.length > 0;
49
+ let syncExisting = false;
50
+ let modifyConfiguration = true;
51
+ if (hasExistingConfig) {
52
+ // Prompt about existing configuration
53
+ const response = await SelectionDialogs.promptForExistingConfig([
54
+ ...configuredDatabases,
55
+ ...configuredBuckets
56
+ ]);
57
+ syncExisting = response.syncExisting;
58
+ modifyConfiguration = response.modifyConfiguration;
59
+ if (syncExisting && !modifyConfiguration) {
60
+ // Just sync existing configuration without changes
61
+ MessageFormatter.info("Syncing existing configuration without modifications", { prefix: "Sync" });
62
+ // Convert configured databases to DatabaseSelection format
63
+ const databaseSelections = configuredDatabases.map(db => ({
64
+ databaseId: db.$id,
65
+ databaseName: db.name,
66
+ tableIds: [], // Tables will be populated from collections config
67
+ tableNames: [],
68
+ isNew: false
69
+ }));
70
+ // Convert configured buckets to BucketSelection format
71
+ const bucketSelections = configuredBuckets.map(bucket => ({
72
+ bucketId: bucket.$id,
73
+ bucketName: bucket.name,
74
+ databaseId: undefined,
75
+ databaseName: undefined,
76
+ isNew: false
77
+ }));
78
+ const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
79
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
80
+ if (!confirmed) {
81
+ MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
82
+ return null;
83
+ }
84
+ // Perform sync with existing configuration
85
+ await controller.selectiveSync(databaseSelections, bucketSelections);
86
+ return selectionSummary;
87
+ }
88
+ }
89
+ if (!modifyConfiguration) {
90
+ MessageFormatter.info("No configuration changes requested", { prefix: "Sync" });
91
+ return null;
92
+ }
93
+ // Allow new items selection based on user choice
94
+ const allowNewOnly = !syncExisting;
95
+ // Select databases
96
+ const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, {
97
+ showSelectAll: true,
98
+ allowNewOnly,
99
+ defaultSelected: syncExisting ? configuredDatabases.map(db => db.$id) : []
100
+ });
101
+ if (selectedDatabaseIds.length === 0) {
102
+ MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
103
+ return null;
104
+ }
105
+ // For each selected database, get available tables and select them
106
+ const tableSelectionsMap = new Map();
107
+ const availableTablesMap = new Map();
108
+ for (const databaseId of selectedDatabaseIds) {
109
+ const database = availableDatabases.find(db => db.$id === databaseId);
110
+ SelectionDialogs.showProgress(`Fetching tables for database: ${database.name}`);
111
+ // Get available tables from remote
112
+ const availableTables = await fetchAllCollections(databaseId, controller.database);
113
+ availableTablesMap.set(databaseId, availableTables);
114
+ // Get configured tables for this database
115
+ // Note: Collections are stored globally in the config, not per database
116
+ const configuredTables = controller.config.collections || [];
117
+ // Select tables for this database
118
+ const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(databaseId, database.name, availableTables, configuredTables, {
119
+ showSelectAll: true,
120
+ allowNewOnly,
121
+ defaultSelected: syncExisting ? configuredTables.map((t) => t.$id) : []
122
+ });
123
+ tableSelectionsMap.set(databaseId, selectedTableIds);
124
+ if (selectedTableIds.length === 0) {
125
+ MessageFormatter.warning(`No tables selected for database: ${database.name}`, { prefix: "Sync" });
126
+ }
127
+ }
128
+ // Select buckets
129
+ let selectedBucketIds = [];
130
+ // Get available buckets from remote
131
+ if (controller.storage) {
132
+ try {
133
+ // Note: We need to implement fetchAllBuckets or use storage.listBuckets
134
+ // For now, we'll use configured buckets as available
135
+ SelectionDialogs.showProgress("Fetching storage buckets...");
136
+ // Create a mock availableBuckets array - in real implementation,
137
+ // you'd fetch this from the Appwrite API
138
+ const availableBuckets = configuredBuckets; // Placeholder
139
+ selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, {
140
+ showSelectAll: true,
141
+ allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
142
+ groupByDatabase: true,
143
+ defaultSelected: syncExisting ? configuredBuckets.map(b => b.$id) : []
144
+ });
145
+ }
146
+ catch (error) {
147
+ MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
148
+ logger.warn("Failed to fetch buckets during sync", { error });
149
+ }
150
+ }
151
+ // Create selection objects
152
+ const databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, availableDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
153
+ const bucketSelections = SelectionDialogs.createBucketSelection(selectedBucketIds, [], // availableBuckets - would be populated from API
154
+ configuredBuckets, availableDatabases);
155
+ // Show final confirmation
156
+ const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
157
+ const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
158
+ if (!confirmed) {
159
+ MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
160
+ return null;
161
+ }
162
+ // Perform the selective sync
163
+ await controller.selectiveSync(databaseSelections, bucketSelections);
164
+ MessageFormatter.success("Enhanced sync completed successfully", { prefix: "Sync" });
165
+ return selectionSummary;
166
+ }
167
+ catch (error) {
168
+ SelectionDialogs.showError("Enhanced sync failed", error instanceof Error ? error : new Error(String(error)));
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * Performs selective sync with the given database and bucket selections
174
+ */
26
175
  /**
27
176
  * Checks if the migration from collections to tables should be allowed
28
177
  * Returns an object with:
@@ -143,6 +292,15 @@ const argv = yargs(hideBin(process.argv))
143
292
  .option("sync", {
144
293
  type: "boolean",
145
294
  description: "Pull and synchronize your local config with the remote Appwrite project schema",
295
+ })
296
+ .option("autoSync", {
297
+ alias: ["auto"],
298
+ type: "boolean",
299
+ description: "Skip prompts and sync all databases, tables, and buckets (current behavior)"
300
+ })
301
+ .option("selectBuckets", {
302
+ type: "boolean",
303
+ description: "Force bucket selection dialog even if buckets are already configured"
146
304
  })
147
305
  .option("endpoint", {
148
306
  type: "string",
@@ -711,10 +869,23 @@ async function main() {
711
869
  operationStats.pushedCollections = controller.config?.collections?.length || 0;
712
870
  }
713
871
  else if (parsedArgv.sync) {
714
- // SYNC: Pull from remote
715
- const databases = options.databases || (await fetchAllDatabases(controller.database));
716
- await controller.synchronizeConfigurations(databases);
717
- operationStats.syncedDatabases = databases.length;
872
+ // Enhanced SYNC: Pull from remote with intelligent configuration detection
873
+ if (parsedArgv.autoSync) {
874
+ // Legacy behavior: sync everything without prompts
875
+ MessageFormatter.info("Using auto-sync mode (legacy behavior)", { prefix: "Sync" });
876
+ const databases = options.databases || (await fetchAllDatabases(controller.database));
877
+ await controller.synchronizeConfigurations(databases);
878
+ operationStats.syncedDatabases = databases.length;
879
+ }
880
+ else {
881
+ // Enhanced sync flow with selection dialogs
882
+ const syncResult = await performEnhancedSync(controller, parsedArgv);
883
+ if (syncResult) {
884
+ operationStats.syncedDatabases = syncResult.databases.length;
885
+ operationStats.syncedCollections = syncResult.totalTables;
886
+ operationStats.syncedBuckets = syncResult.buckets.length;
887
+ }
888
+ }
718
889
  }
719
890
  if (options.generateSchemas) {
720
891
  await controller.generateSchemas();
@@ -1,5 +1,7 @@
1
1
  import { Storage, type Models } from "node-appwrite";
2
2
  import { type AppwriteConfig } from "appwrite-utils";
3
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
4
+ import type { DatabaseSelection, BucketSelection } from "../shared/selectionDialogs.js";
3
5
  export declare class AppwriteToX {
4
6
  config: AppwriteConfig;
5
7
  storage: Storage;
@@ -102,7 +104,14 @@ export declare class AppwriteToX {
102
104
  } | undefined;
103
105
  })[]>;
104
106
  appwriteFolderPath: string;
107
+ adapter?: DatabaseAdapter;
108
+ apiMode?: 'legacy' | 'tablesdb';
109
+ databaseApiModes: Map<string, "legacy" | "tablesdb">;
105
110
  constructor(config: AppwriteConfig, appwriteFolderPath: string, storage: Storage);
111
+ /**
112
+ * Initialize adapter for database operations with API mode detection
113
+ */
114
+ private initializeAdapter;
106
115
  private ensureClientInitialized;
107
116
  parsePermissionString: (permissionString: string) => {
108
117
  permission: string;
@@ -116,6 +125,22 @@ export declare class AppwriteToX {
116
125
  target: string;
117
126
  })[];
118
127
  updateCollectionConfigAttributes: (collection: Models.Collection) => void;
119
- appwriteSync(config: AppwriteConfig, databases?: Models.Database[]): Promise<void>;
120
- toSchemas(databases?: Models.Database[]): Promise<void>;
128
+ /**
129
+ * Fetch collections/tables using the appropriate adapter or legacy client
130
+ */
131
+ private fetchCollectionsOrTables;
132
+ /**
133
+ * Get collection/table using the appropriate adapter or legacy client
134
+ */
135
+ private getCollectionOrTable;
136
+ /**
137
+ * Detect API mode for a specific database by testing adapter capabilities
138
+ */
139
+ private detectDatabaseApiMode;
140
+ /**
141
+ * Get API mode context for schema generation
142
+ */
143
+ private getSchemaGeneratorApiContext;
144
+ appwriteSync(config: AppwriteConfig, databases?: Models.Database[], databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
145
+ toSchemas(databases?: Models.Database[], databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
121
146
  }