appwrite-utils-cli 1.11.0 → 1.12.0

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 (250) hide show
  1. package/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -1
  2. package/dist/adapters/index.js +10 -0
  3. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  4. package/dist/backups/operations/bucketBackup.js +197 -0
  5. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  6. package/dist/backups/operations/collectionBackup.js +201 -0
  7. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  8. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  9. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  10. package/dist/backups/schemas/bucketManifest.js +33 -0
  11. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  12. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  13. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  14. package/dist/backups/tracking/centralizedTracking.js +274 -0
  15. package/dist/cli/commands/configCommands.d.ts +8 -0
  16. package/dist/cli/commands/configCommands.js +210 -0
  17. package/dist/cli/commands/databaseCommands.d.ts +14 -0
  18. package/dist/cli/commands/databaseCommands.js +696 -0
  19. package/dist/cli/commands/functionCommands.d.ts +7 -0
  20. package/dist/cli/commands/functionCommands.js +330 -0
  21. package/dist/cli/commands/importFileCommands.d.ts +7 -0
  22. package/dist/cli/commands/importFileCommands.js +674 -0
  23. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  24. package/dist/cli/commands/schemaCommands.js +169 -0
  25. package/dist/cli/commands/storageCommands.d.ts +5 -0
  26. package/dist/cli/commands/storageCommands.js +142 -0
  27. package/dist/cli/commands/transferCommands.d.ts +5 -0
  28. package/dist/cli/commands/transferCommands.js +382 -0
  29. package/dist/collections/columns.d.ts +13 -0
  30. package/dist/collections/columns.js +1339 -0
  31. package/dist/collections/indexes.d.ts +12 -0
  32. package/dist/collections/indexes.js +215 -0
  33. package/dist/collections/methods.d.ts +19 -0
  34. package/dist/collections/methods.js +605 -0
  35. package/dist/collections/tableOperations.d.ts +87 -0
  36. package/dist/collections/tableOperations.js +466 -0
  37. package/dist/collections/transferOperations.d.ts +8 -0
  38. package/dist/collections/transferOperations.js +411 -0
  39. package/dist/collections/wipeOperations.d.ts +17 -0
  40. package/dist/collections/wipeOperations.js +306 -0
  41. package/dist/databases/methods.d.ts +6 -0
  42. package/dist/databases/methods.js +35 -0
  43. package/dist/databases/setup.d.ts +5 -0
  44. package/dist/databases/setup.js +45 -0
  45. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  46. package/dist/examples/yamlTerminologyExample.js +272 -0
  47. package/dist/functions/deployments.d.ts +4 -0
  48. package/dist/functions/deployments.js +146 -0
  49. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  50. package/dist/functions/fnConfigDiscovery.js +108 -0
  51. package/dist/functions/methods.d.ts +16 -0
  52. package/dist/functions/methods.js +174 -0
  53. package/dist/init.d.ts +2 -0
  54. package/dist/init.js +57 -0
  55. package/dist/interactiveCLI.d.ts +36 -0
  56. package/dist/interactiveCLI.js +952 -0
  57. package/dist/main.d.ts +2 -0
  58. package/dist/main.js +1125 -0
  59. package/dist/migrations/afterImportActions.d.ts +17 -0
  60. package/dist/migrations/afterImportActions.js +305 -0
  61. package/dist/migrations/appwriteToX.d.ts +211 -0
  62. package/dist/migrations/appwriteToX.js +493 -0
  63. package/dist/migrations/comprehensiveTransfer.d.ts +147 -0
  64. package/dist/migrations/comprehensiveTransfer.js +1315 -0
  65. package/dist/migrations/dataLoader.d.ts +755 -0
  66. package/dist/migrations/dataLoader.js +1272 -0
  67. package/dist/migrations/importController.d.ts +25 -0
  68. package/dist/migrations/importController.js +283 -0
  69. package/dist/migrations/importDataActions.d.ts +50 -0
  70. package/dist/migrations/importDataActions.js +230 -0
  71. package/dist/migrations/relationships.d.ts +29 -0
  72. package/dist/migrations/relationships.js +203 -0
  73. package/dist/migrations/services/DataTransformationService.d.ts +55 -0
  74. package/dist/migrations/services/DataTransformationService.js +158 -0
  75. package/dist/migrations/services/FileHandlerService.d.ts +75 -0
  76. package/dist/migrations/services/FileHandlerService.js +236 -0
  77. package/dist/migrations/services/ImportOrchestrator.d.ts +99 -0
  78. package/dist/migrations/services/ImportOrchestrator.js +493 -0
  79. package/dist/migrations/services/RateLimitManager.d.ts +138 -0
  80. package/dist/migrations/services/RateLimitManager.js +279 -0
  81. package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
  82. package/dist/migrations/services/RelationshipResolver.js +332 -0
  83. package/dist/migrations/services/UserMappingService.d.ts +109 -0
  84. package/dist/migrations/services/UserMappingService.js +277 -0
  85. package/dist/migrations/services/ValidationService.d.ts +74 -0
  86. package/dist/migrations/services/ValidationService.js +260 -0
  87. package/dist/migrations/transfer.d.ts +30 -0
  88. package/dist/migrations/transfer.js +661 -0
  89. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +131 -0
  90. package/dist/migrations/yaml/YamlImportConfigLoader.js +383 -0
  91. package/dist/migrations/yaml/YamlImportIntegration.d.ts +93 -0
  92. package/dist/migrations/yaml/YamlImportIntegration.js +341 -0
  93. package/dist/migrations/yaml/generateImportSchemas.d.ts +30 -0
  94. package/dist/migrations/yaml/generateImportSchemas.js +1327 -0
  95. package/dist/schemas/authUser.d.ts +24 -0
  96. package/dist/schemas/authUser.js +17 -0
  97. package/dist/setup.d.ts +2 -0
  98. package/{src/setup.ts → dist/setup.js} +0 -3
  99. package/dist/setupCommands.d.ts +58 -0
  100. package/dist/setupCommands.js +489 -0
  101. package/dist/setupController.d.ts +9 -0
  102. package/dist/setupController.js +34 -0
  103. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  104. package/dist/shared/backupMetadataSchema.js +38 -0
  105. package/dist/shared/backupTracking.d.ts +18 -0
  106. package/dist/shared/backupTracking.js +176 -0
  107. package/dist/shared/confirmationDialogs.d.ts +75 -0
  108. package/dist/shared/confirmationDialogs.js +236 -0
  109. package/dist/shared/migrationHelpers.d.ts +61 -0
  110. package/dist/shared/migrationHelpers.js +145 -0
  111. package/{src/shared/operationLogger.ts → dist/shared/operationLogger.d.ts} +1 -11
  112. package/dist/shared/operationLogger.js +12 -0
  113. package/dist/shared/operationQueue.d.ts +40 -0
  114. package/dist/shared/operationQueue.js +310 -0
  115. package/dist/shared/operationsTable.d.ts +26 -0
  116. package/dist/shared/operationsTable.js +287 -0
  117. package/dist/shared/operationsTableSchema.d.ts +48 -0
  118. package/dist/shared/operationsTableSchema.js +35 -0
  119. package/dist/shared/progressManager.d.ts +62 -0
  120. package/dist/shared/progressManager.js +215 -0
  121. package/dist/shared/relationshipExtractor.d.ts +56 -0
  122. package/dist/shared/relationshipExtractor.js +138 -0
  123. package/dist/shared/selectionDialogs.d.ts +220 -0
  124. package/dist/shared/selectionDialogs.js +588 -0
  125. package/dist/storage/backupCompression.d.ts +20 -0
  126. package/dist/storage/backupCompression.js +67 -0
  127. package/dist/storage/methods.d.ts +44 -0
  128. package/dist/storage/methods.js +475 -0
  129. package/dist/storage/schemas.d.ts +842 -0
  130. package/dist/storage/schemas.js +175 -0
  131. package/dist/tables/indexManager.d.ts +65 -0
  132. package/dist/tables/indexManager.js +294 -0
  133. package/{src/types.ts → dist/types.d.ts} +1 -6
  134. package/dist/types.js +3 -0
  135. package/dist/users/methods.d.ts +16 -0
  136. package/dist/users/methods.js +276 -0
  137. package/dist/utils/configMigration.d.ts +1 -0
  138. package/dist/utils/configMigration.js +261 -0
  139. package/dist/utils/index.js +2 -0
  140. package/dist/utils/loadConfigs.d.ts +50 -0
  141. package/dist/utils/loadConfigs.js +357 -0
  142. package/dist/utils/setupFiles.d.ts +4 -0
  143. package/dist/utils/setupFiles.js +1190 -0
  144. package/dist/utilsController.d.ts +114 -0
  145. package/dist/utilsController.js +898 -0
  146. package/package.json +6 -3
  147. package/CHANGELOG.md +0 -35
  148. package/CONFIG_TODO.md +0 -1189
  149. package/SELECTION_DIALOGS.md +0 -146
  150. package/SERVICE_IMPLEMENTATION_REPORT.md +0 -462
  151. package/scripts/copy-templates.ts +0 -23
  152. package/src/backups/operations/bucketBackup.ts +0 -277
  153. package/src/backups/operations/collectionBackup.ts +0 -310
  154. package/src/backups/operations/comprehensiveBackup.ts +0 -342
  155. package/src/backups/schemas/bucketManifest.ts +0 -78
  156. package/src/backups/schemas/comprehensiveManifest.ts +0 -76
  157. package/src/backups/tracking/centralizedTracking.ts +0 -352
  158. package/src/cli/commands/configCommands.ts +0 -265
  159. package/src/cli/commands/databaseCommands.ts +0 -931
  160. package/src/cli/commands/functionCommands.ts +0 -419
  161. package/src/cli/commands/importFileCommands.ts +0 -815
  162. package/src/cli/commands/schemaCommands.ts +0 -200
  163. package/src/cli/commands/storageCommands.ts +0 -151
  164. package/src/cli/commands/transferCommands.ts +0 -454
  165. package/src/collections/attributes.ts.backup +0 -1555
  166. package/src/collections/columns.ts +0 -2025
  167. package/src/collections/indexes.ts +0 -350
  168. package/src/collections/methods.ts +0 -714
  169. package/src/collections/tableOperations.ts +0 -542
  170. package/src/collections/transferOperations.ts +0 -589
  171. package/src/collections/wipeOperations.ts +0 -449
  172. package/src/databases/methods.ts +0 -49
  173. package/src/databases/setup.ts +0 -77
  174. package/src/examples/yamlTerminologyExample.ts +0 -346
  175. package/src/functions/deployments.ts +0 -221
  176. package/src/functions/fnConfigDiscovery.ts +0 -103
  177. package/src/functions/methods.ts +0 -284
  178. package/src/init.ts +0 -62
  179. package/src/interactiveCLI.ts +0 -1201
  180. package/src/main.ts +0 -1517
  181. package/src/migrations/afterImportActions.ts +0 -579
  182. package/src/migrations/appwriteToX.ts +0 -668
  183. package/src/migrations/comprehensiveTransfer.ts +0 -2285
  184. package/src/migrations/dataLoader.ts +0 -1729
  185. package/src/migrations/importController.ts +0 -440
  186. package/src/migrations/importDataActions.ts +0 -315
  187. package/src/migrations/relationships.ts +0 -333
  188. package/src/migrations/services/DataTransformationService.ts +0 -196
  189. package/src/migrations/services/FileHandlerService.ts +0 -311
  190. package/src/migrations/services/ImportOrchestrator.ts +0 -675
  191. package/src/migrations/services/RateLimitManager.ts +0 -363
  192. package/src/migrations/services/RelationshipResolver.ts +0 -461
  193. package/src/migrations/services/UserMappingService.ts +0 -345
  194. package/src/migrations/services/ValidationService.ts +0 -349
  195. package/src/migrations/transfer.ts +0 -1113
  196. package/src/migrations/yaml/YamlImportConfigLoader.ts +0 -439
  197. package/src/migrations/yaml/YamlImportIntegration.ts +0 -446
  198. package/src/migrations/yaml/generateImportSchemas.ts +0 -1354
  199. package/src/schemas/authUser.ts +0 -23
  200. package/src/setupCommands.ts +0 -602
  201. package/src/setupController.ts +0 -43
  202. package/src/shared/backupMetadataSchema.ts +0 -93
  203. package/src/shared/backupTracking.ts +0 -211
  204. package/src/shared/confirmationDialogs.ts +0 -327
  205. package/src/shared/migrationHelpers.ts +0 -232
  206. package/src/shared/operationQueue.ts +0 -376
  207. package/src/shared/operationsTable.ts +0 -338
  208. package/src/shared/operationsTableSchema.ts +0 -60
  209. package/src/shared/progressManager.ts +0 -278
  210. package/src/shared/relationshipExtractor.ts +0 -214
  211. package/src/shared/selectionDialogs.ts +0 -802
  212. package/src/storage/backupCompression.ts +0 -88
  213. package/src/storage/methods.ts +0 -711
  214. package/src/storage/schemas.ts +0 -205
  215. package/src/tables/indexManager.ts +0 -409
  216. package/src/types/node-appwrite-tablesdb.d.ts +0 -44
  217. package/src/users/methods.ts +0 -358
  218. package/src/utils/configMigration.ts +0 -348
  219. package/src/utils/loadConfigs.ts +0 -457
  220. package/src/utils/setupFiles.ts +0 -1236
  221. package/src/utilsController.ts +0 -1263
  222. package/tests/README.md +0 -497
  223. package/tests/adapters/AdapterFactory.test.ts +0 -277
  224. package/tests/integration/syncOperations.test.ts +0 -463
  225. package/tests/jest.config.js +0 -25
  226. package/tests/migration/configMigration.test.ts +0 -546
  227. package/tests/setup.ts +0 -62
  228. package/tests/testUtils.ts +0 -340
  229. package/tests/utils/loadConfigs.test.ts +0 -350
  230. package/tests/validation/configValidation.test.ts +0 -412
  231. package/tsconfig.json +0 -44
  232. /package/{src → dist}/functions/templates/count-docs-in-collection/README.md +0 -0
  233. /package/{src → dist}/functions/templates/count-docs-in-collection/src/main.ts +0 -0
  234. /package/{src → dist}/functions/templates/count-docs-in-collection/src/request.ts +0 -0
  235. /package/{src → dist}/functions/templates/hono-typescript/README.md +0 -0
  236. /package/{src → dist}/functions/templates/hono-typescript/src/adapters/request.ts +0 -0
  237. /package/{src → dist}/functions/templates/hono-typescript/src/adapters/response.ts +0 -0
  238. /package/{src → dist}/functions/templates/hono-typescript/src/app.ts +0 -0
  239. /package/{src → dist}/functions/templates/hono-typescript/src/context.ts +0 -0
  240. /package/{src → dist}/functions/templates/hono-typescript/src/main.ts +0 -0
  241. /package/{src → dist}/functions/templates/hono-typescript/src/middleware/appwrite.ts +0 -0
  242. /package/{src → dist}/functions/templates/typescript-node/README.md +0 -0
  243. /package/{src → dist}/functions/templates/typescript-node/src/context.ts +0 -0
  244. /package/{src → dist}/functions/templates/typescript-node/src/main.ts +0 -0
  245. /package/{src → dist}/functions/templates/uv/README.md +0 -0
  246. /package/{src → dist}/functions/templates/uv/pyproject.toml +0 -0
  247. /package/{src → dist}/functions/templates/uv/src/__init__.py +0 -0
  248. /package/{src → dist}/functions/templates/uv/src/context.py +0 -0
  249. /package/{src → dist}/functions/templates/uv/src/main.py +0 -0
  250. /package/{src/utils/index.ts → dist/utils/index.d.ts} +0 -0
@@ -0,0 +1,898 @@
1
+ import { Client, Databases, Query, Storage, Users, } from "node-appwrite";
2
+ import {} from "appwrite-utils";
3
+ import { findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
4
+ import { normalizeFunctionName, validateFunctionDirectory } from 'appwrite-utils-helpers';
5
+ import { UsersController } from "./users/methods.js";
6
+ import { AppwriteToX } from "./migrations/appwriteToX.js";
7
+ import { ImportController } from "./migrations/importController.js";
8
+ import { ImportDataActions } from "./migrations/importDataActions.js";
9
+ import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
10
+ import { createOrUpdateCollections, createOrUpdateCollectionsViaAdapter, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
11
+ import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
12
+ import { backupDatabase, ensureDatabaseConfigBucketsExist, ensureGlobalBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
13
+ import path from "path";
14
+ import { converterFunctions, validationRules, } from "appwrite-utils";
15
+ import { afterImportActions } from "./migrations/afterImportActions.js";
16
+ import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, transferUsersLocalToRemote, } from "./migrations/transfer.js";
17
+ import { getClient, getClientWithAuth } from "appwrite-utils-helpers";
18
+ import { getAdapterFromConfig } from "appwrite-utils-helpers";
19
+ import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie } from "appwrite-utils-helpers";
20
+ import { fetchAllDatabases } from "./databases/methods.js";
21
+ import { listFunctions, updateFunctionSpecifications, } from "./functions/methods.js";
22
+ import chalk from "chalk";
23
+ import { deployLocalFunction } from "./functions/deployments.js";
24
+ import fs from "node:fs";
25
+ import { configureLogging, updateLogger, logger, MessageFormatter, Messages, SchemaGenerator, findYamlConfig, validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode, ConfigManager } from "appwrite-utils-helpers";
26
+ import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
27
+ import { ClientFactory } from "appwrite-utils-helpers";
28
+ import { clearProcessingState, processQueue } from "./shared/operationQueue.js";
29
+ export class UtilsController {
30
+ // ──────────────────────────────────────────────────
31
+ // SINGLETON PATTERN
32
+ // ──────────────────────────────────────────────────
33
+ static instance = null;
34
+ isInitialized = false;
35
+ /**
36
+ * Get the UtilsController singleton instance
37
+ */
38
+ static getInstance(currentUserDir, directConfig) {
39
+ // Clear instance if currentUserDir has changed
40
+ if (UtilsController.instance &&
41
+ UtilsController.instance.currentUserDir !== currentUserDir) {
42
+ logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
43
+ UtilsController.clearInstance();
44
+ }
45
+ // Clear instance if directConfig endpoint or project has changed
46
+ if (UtilsController.instance && directConfig) {
47
+ const existingConfig = UtilsController.instance.config;
48
+ if (existingConfig) {
49
+ const endpointChanged = directConfig.appwriteEndpoint &&
50
+ existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
51
+ const projectChanged = directConfig.appwriteProject &&
52
+ existingConfig.appwriteProject !== directConfig.appwriteProject;
53
+ if (endpointChanged || projectChanged) {
54
+ logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
55
+ UtilsController.clearInstance();
56
+ }
57
+ }
58
+ }
59
+ if (!UtilsController.instance) {
60
+ UtilsController.instance = new UtilsController(currentUserDir, directConfig);
61
+ }
62
+ return UtilsController.instance;
63
+ }
64
+ /**
65
+ * Clear the singleton instance (useful for testing)
66
+ */
67
+ static clearInstance() {
68
+ UtilsController.instance = null;
69
+ }
70
+ // ──────────────────────────────────────────────────
71
+ // INSTANCE FIELDS
72
+ // ──────────────────────────────────────────────────
73
+ appwriteFolderPath;
74
+ appwriteConfigPath;
75
+ currentUserDir;
76
+ config;
77
+ appwriteServer;
78
+ database;
79
+ storage;
80
+ adapter;
81
+ converterDefinitions = converterFunctions;
82
+ validityRuleDefinitions = validationRules;
83
+ afterImportActionsDefinitions = afterImportActions;
84
+ constructor(currentUserDir, directConfig) {
85
+ this.currentUserDir = currentUserDir;
86
+ const basePath = currentUserDir;
87
+ if (directConfig) {
88
+ let hasErrors = false;
89
+ if (!directConfig.appwriteEndpoint) {
90
+ MessageFormatter.error("Appwrite endpoint is required", undefined, { prefix: "Config" });
91
+ hasErrors = true;
92
+ }
93
+ if (!directConfig.appwriteProject) {
94
+ MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
95
+ hasErrors = true;
96
+ }
97
+ // Check authentication: either API key or session auth is required
98
+ const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
99
+ hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
100
+ if (!directConfig.appwriteKey && !hasValidSession) {
101
+ MessageFormatter.error("Authentication required: provide an API key or login with 'appwrite login'", undefined, { prefix: "Config" });
102
+ hasErrors = true;
103
+ }
104
+ else if (!directConfig.appwriteKey && hasValidSession) {
105
+ MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
106
+ }
107
+ else if (directConfig.appwriteKey && hasValidSession) {
108
+ MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
109
+ }
110
+ if (!hasErrors) {
111
+ // Only set config if we have all required fields
112
+ this.appwriteFolderPath = basePath;
113
+ this.config = {
114
+ appwriteEndpoint: directConfig.appwriteEndpoint,
115
+ appwriteProject: directConfig.appwriteProject,
116
+ appwriteKey: directConfig.appwriteKey || "",
117
+ appwriteClient: null,
118
+ apiMode: "auto", // Default to auto-detect for dual API support
119
+ authMethod: "auto", // Default to auto-detect authentication method
120
+ enableBackups: false,
121
+ backupInterval: 0,
122
+ backupRetention: 0,
123
+ enableBackupCleanup: false,
124
+ enableMockData: false,
125
+ documentBucketId: "",
126
+ usersCollectionName: "",
127
+ databases: [],
128
+ buckets: [],
129
+ functions: [],
130
+ logging: {
131
+ enabled: false,
132
+ level: "info",
133
+ console: false,
134
+ },
135
+ };
136
+ }
137
+ }
138
+ else {
139
+ // Try to find config file
140
+ const appwriteConfigFound = findAppwriteConfig(basePath);
141
+ if (!appwriteConfigFound) {
142
+ MessageFormatter.warning("No appwriteConfig.ts found and no direct configuration provided", { prefix: "Config" });
143
+ return;
144
+ }
145
+ this.appwriteConfigPath = appwriteConfigFound;
146
+ this.appwriteFolderPath = appwriteConfigFound; // For YAML configs, findAppwriteConfig already returns the correct directory
147
+ }
148
+ }
149
+ async init(options = {}) {
150
+ const { validate = false, strictMode = false, preferJson = false, useSession, sessionCookie, overrides } = options;
151
+ const configManager = ConfigManager.getInstance();
152
+ // Load config if not already loaded
153
+ if (!configManager.hasConfig()) {
154
+ await configManager.loadConfig({
155
+ configDir: this.currentUserDir,
156
+ validate,
157
+ strictMode,
158
+ preferJson,
159
+ useSession,
160
+ explicitSessionCookie: sessionCookie,
161
+ overrides,
162
+ });
163
+ }
164
+ const config = configManager.getConfig();
165
+ // Configure logging based on config
166
+ if (config.logging) {
167
+ configureLogging(config.logging);
168
+ updateLogger();
169
+ }
170
+ // Create client and adapter (session already in config from ConfigManager)
171
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
172
+ this.appwriteServer = client;
173
+ this.adapter = adapter;
174
+ this.config = config;
175
+ // Update config.apiMode from adapter if it's auto or not set
176
+ if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
177
+ this.config.apiMode = adapter.getApiMode();
178
+ logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
179
+ }
180
+ this.database = new Databases(this.appwriteServer);
181
+ this.storage = new Storage(this.appwriteServer);
182
+ this.config.appwriteClient = this.appwriteServer;
183
+ // Log only on FIRST initialization to avoid spam
184
+ if (!this.isInitialized) {
185
+ const apiMode = adapter.getApiMode();
186
+ const configApiMode = this.config.apiMode;
187
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
188
+ this.isInitialized = true;
189
+ }
190
+ else {
191
+ logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
192
+ }
193
+ }
194
+ async reloadConfig() {
195
+ const configManager = ConfigManager.getInstance();
196
+ // Session preservation is automatic in ConfigManager
197
+ const config = await configManager.reloadConfig();
198
+ // Configure logging based on updated config
199
+ if (config.logging) {
200
+ configureLogging(config.logging);
201
+ updateLogger();
202
+ }
203
+ // Recreate client and adapter
204
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
205
+ this.appwriteServer = client;
206
+ this.adapter = adapter;
207
+ this.config = config;
208
+ this.database = new Databases(this.appwriteServer);
209
+ this.storage = new Storage(this.appwriteServer);
210
+ this.config.appwriteClient = this.appwriteServer;
211
+ logger.debug("Config reloaded, adapter refreshed", { prefix: "UtilsController" });
212
+ }
213
+ async ensureDatabaseConfigBucketsExist(databases = []) {
214
+ await this.init();
215
+ if (!this.storage) {
216
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
217
+ return;
218
+ }
219
+ if (!this.config) {
220
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
221
+ return;
222
+ }
223
+ await ensureDatabaseConfigBucketsExist(this.storage, this.config, databases);
224
+ }
225
+ async pushGlobalBuckets(selectedBucketIds) {
226
+ await this.init();
227
+ if (!this.storage) {
228
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
229
+ return;
230
+ }
231
+ if (!this.config) {
232
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
233
+ return;
234
+ }
235
+ await ensureGlobalBucketsExist(this.storage, this.config, selectedBucketIds);
236
+ }
237
+ async ensureDatabasesExist(databases) {
238
+ await this.init();
239
+ if (!this.config) {
240
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
241
+ return;
242
+ }
243
+ await this.ensureDatabaseConfigBucketsExist(databases);
244
+ await ensureDatabasesExist(this.config, databases);
245
+ }
246
+ async ensureCollectionsExist(database, collections) {
247
+ await this.init();
248
+ if (!this.config) {
249
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
250
+ return;
251
+ }
252
+ await ensureCollectionsExist(this.config, database, collections);
253
+ }
254
+ async getDatabasesByIds(ids) {
255
+ await this.init();
256
+ if (!this.database) {
257
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
258
+ return;
259
+ }
260
+ if (ids.length === 0)
261
+ return [];
262
+ const dbs = await this.database.list([
263
+ Query.limit(500),
264
+ Query.equal("$id", ids),
265
+ ]);
266
+ return dbs.databases;
267
+ }
268
+ async fetchAllBuckets() {
269
+ await this.init();
270
+ if (!this.storage) {
271
+ MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
272
+ return { buckets: [] };
273
+ }
274
+ try {
275
+ const result = await this.storage.listBuckets([
276
+ Query.limit(1000) // Increase limit to get all buckets
277
+ ]);
278
+ MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
279
+ return result;
280
+ }
281
+ catch (error) {
282
+ MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
283
+ return { buckets: [] };
284
+ }
285
+ }
286
+ async wipeOtherDatabases(databasesToKeep) {
287
+ await this.init();
288
+ if (!this.database) {
289
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
290
+ return;
291
+ }
292
+ await wipeOtherDatabases(this.database, databasesToKeep);
293
+ }
294
+ async wipeUsers() {
295
+ await this.init();
296
+ if (!this.config || !this.database) {
297
+ MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
298
+ return;
299
+ }
300
+ const usersController = new UsersController(this.config, this.database);
301
+ await usersController.wipeUsers();
302
+ }
303
+ async backupDatabase(database, format = 'json') {
304
+ await this.init();
305
+ if (!this.database || !this.storage || !this.config) {
306
+ MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
307
+ return;
308
+ }
309
+ await backupDatabase(this.config, this.database, database.$id, this.storage, format);
310
+ }
311
+ async listAllFunctions() {
312
+ await this.init();
313
+ if (!this.appwriteServer) {
314
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
315
+ return [];
316
+ }
317
+ const { functions } = await listFunctions(this.appwriteServer, [
318
+ Query.limit(1000),
319
+ ]);
320
+ return functions;
321
+ }
322
+ async findFunctionDirectories() {
323
+ if (!this.appwriteFolderPath) {
324
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
325
+ return new Map();
326
+ }
327
+ const functionsDir = findFunctionsDir(this.appwriteFolderPath);
328
+ if (!functionsDir) {
329
+ MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
330
+ return new Map();
331
+ }
332
+ const functionDirMap = new Map();
333
+ const entries = fs.readdirSync(functionsDir, { withFileTypes: true });
334
+ for (const entry of entries) {
335
+ if (entry.isDirectory()) {
336
+ const functionPath = path.join(functionsDir, entry.name);
337
+ // Validate it's a function directory
338
+ if (!validateFunctionDirectory(functionPath)) {
339
+ continue; // Skip invalid directories
340
+ }
341
+ // Match with config functions using normalized names
342
+ if (this.config?.functions) {
343
+ const normalizedEntryName = normalizeFunctionName(entry.name);
344
+ const matchingFunc = this.config.functions.find((f) => normalizeFunctionName(f.name) === normalizedEntryName);
345
+ if (matchingFunc) {
346
+ functionDirMap.set(matchingFunc.name, functionPath);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ return functionDirMap;
352
+ }
353
+ async deployFunction(functionName, functionPath, functionConfig) {
354
+ await this.init();
355
+ if (!this.appwriteServer) {
356
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
357
+ return;
358
+ }
359
+ if (!functionConfig) {
360
+ functionConfig = this.config?.functions?.find((f) => f.name === functionName);
361
+ }
362
+ if (!functionConfig) {
363
+ MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
364
+ return;
365
+ }
366
+ await deployLocalFunction(this.appwriteServer, functionName, functionConfig, functionPath, this.appwriteFolderPath);
367
+ }
368
+ async syncFunctions() {
369
+ await this.init();
370
+ if (!this.appwriteServer) {
371
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
372
+ return;
373
+ }
374
+ const localFunctions = this.config?.functions || [];
375
+ const remoteFunctions = await listFunctions(this.appwriteServer, [
376
+ Query.limit(1000),
377
+ ]);
378
+ for (const localFunction of localFunctions) {
379
+ MessageFormatter.progress(`Syncing function ${localFunction.name}...`, { prefix: "Functions" });
380
+ await this.deployFunction(localFunction.name);
381
+ }
382
+ MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
383
+ }
384
+ async wipeDatabase(database, wipeBucket = false) {
385
+ await this.init();
386
+ if (!this.database || !this.config)
387
+ throw new Error("Database not initialized");
388
+ try {
389
+ // Session is already in config from ConfigManager
390
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
391
+ if (apiMode === 'tablesdb') {
392
+ await wipeAllTables(adapter, database.$id);
393
+ }
394
+ else {
395
+ await wipeDatabase(this.database, database.$id);
396
+ }
397
+ }
398
+ catch {
399
+ await wipeDatabase(this.database, database.$id);
400
+ }
401
+ if (wipeBucket) {
402
+ await this.wipeBucketFromDatabase(database);
403
+ }
404
+ }
405
+ async wipeBucketFromDatabase(database) {
406
+ // Check configured bucket in database config
407
+ const configuredBucket = this.config?.databases?.find((db) => db.$id === database.$id)?.bucket;
408
+ if (configuredBucket?.$id) {
409
+ await this.wipeDocumentStorage(configuredBucket.$id);
410
+ }
411
+ // Also check for document bucket ID pattern
412
+ if (this.config?.documentBucketId) {
413
+ const documentBucketId = `${this.config.documentBucketId}_${database.$id
414
+ .toLowerCase()
415
+ .trim()
416
+ .replace(/\s+/g, "")}`;
417
+ try {
418
+ await this.wipeDocumentStorage(documentBucketId);
419
+ }
420
+ catch (error) {
421
+ // Ignore if bucket doesn't exist
422
+ if (error?.type !== "storage_bucket_not_found") {
423
+ throw error;
424
+ }
425
+ }
426
+ }
427
+ }
428
+ async wipeCollection(database, collection) {
429
+ await this.init();
430
+ if (!this.database || !this.config)
431
+ throw new Error("Database not initialized");
432
+ try {
433
+ // Session is already in config from ConfigManager
434
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
435
+ if (apiMode === 'tablesdb') {
436
+ await wipeTableRows(adapter, database.$id, collection.$id);
437
+ }
438
+ else {
439
+ await wipeCollection(this.database, database.$id, collection.$id);
440
+ }
441
+ }
442
+ catch {
443
+ await wipeCollection(this.database, database.$id, collection.$id);
444
+ }
445
+ }
446
+ async wipeDocumentStorage(bucketId) {
447
+ await this.init();
448
+ if (!this.storage)
449
+ throw new Error("Storage not initialized");
450
+ await wipeDocumentStorage(this.storage, bucketId);
451
+ }
452
+ async createOrUpdateCollectionsForDatabases(databases, collections = []) {
453
+ await this.init();
454
+ if (!this.database || !this.config)
455
+ throw new Error("Database or config not initialized");
456
+ for (const database of databases) {
457
+ await this.createOrUpdateCollections(database, undefined, collections);
458
+ }
459
+ }
460
+ async createOrUpdateCollections(database, deletedCollections, collections = []) {
461
+ await this.init();
462
+ if (!this.database || !this.config)
463
+ throw new Error("Database or config not initialized");
464
+ // Ensure apiMode is properly set from adapter
465
+ if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
466
+ this.config.apiMode = this.adapter.getApiMode();
467
+ logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
468
+ }
469
+ // Ensure we don't carry state between databases in a multi-db push
470
+ // This resets processed sets and name->id mapping per database
471
+ try {
472
+ clearProcessingState();
473
+ }
474
+ catch { }
475
+ // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
476
+ if (this.adapter) {
477
+ logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
478
+ prefix: "UtilsController",
479
+ apiMode: this.adapter.getApiMode()
480
+ });
481
+ await createOrUpdateCollectionsViaAdapter(this.adapter, database.$id, this.config, deletedCollections, collections);
482
+ }
483
+ else {
484
+ // Fallback if adapter is unavailable for some reason
485
+ logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
486
+ await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
487
+ }
488
+ // Safety net: Process any remaining queued operations to complete relationship sync
489
+ try {
490
+ MessageFormatter.info(`🔄 Processing final operation queue for database ${database.$id}`, { prefix: "UtilsController" });
491
+ await processQueue(this.adapter || this.database, database.$id);
492
+ MessageFormatter.info(`✅ Operation queue processing completed`, { prefix: "UtilsController" });
493
+ }
494
+ catch (error) {
495
+ MessageFormatter.error(`Failed to process operation queue`, error instanceof Error ? error : new Error(String(error)), { prefix: 'UtilsController' });
496
+ }
497
+ }
498
+ async generateSchemas() {
499
+ // Schema generation doesn't need Appwrite connection, just config
500
+ if (!this.config) {
501
+ MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
502
+ try {
503
+ const configManager = ConfigManager.getInstance();
504
+ // Load config if not already loaded
505
+ if (!configManager.hasConfig()) {
506
+ await configManager.loadConfig({
507
+ configDir: this.currentUserDir,
508
+ validate: false,
509
+ strictMode: false,
510
+ });
511
+ }
512
+ this.config = configManager.getConfig();
513
+ MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
514
+ }
515
+ catch (error) {
516
+ MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
517
+ return;
518
+ }
519
+ }
520
+ if (!this.appwriteFolderPath) {
521
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
522
+ return;
523
+ }
524
+ await generateSchemas(this.config, this.appwriteFolderPath);
525
+ }
526
+ async importData(options = {}) {
527
+ await this.init();
528
+ if (!this.database) {
529
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
530
+ return;
531
+ }
532
+ if (!this.storage) {
533
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
534
+ return;
535
+ }
536
+ if (!this.config) {
537
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
538
+ return;
539
+ }
540
+ if (!this.appwriteFolderPath) {
541
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
542
+ return;
543
+ }
544
+ const importDataActions = new ImportDataActions(this.database, this.storage, this.config, this.converterDefinitions, this.validityRuleDefinitions, this.afterImportActionsDefinitions);
545
+ const importController = new ImportController(this.config, this.database, this.storage, this.appwriteFolderPath, importDataActions, options, options.databases);
546
+ await importController.run(options.collections);
547
+ }
548
+ async synchronizeConfigurations(databases, config, databaseSelections, bucketSelections) {
549
+ await this.init();
550
+ if (!this.storage) {
551
+ MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
552
+ return;
553
+ }
554
+ const configToUse = config || this.config;
555
+ if (!configToUse) {
556
+ MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
557
+ return;
558
+ }
559
+ if (!this.appwriteFolderPath) {
560
+ MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
561
+ return;
562
+ }
563
+ // If selections are provided, filter the databases accordingly
564
+ let filteredDatabases = databases;
565
+ if (databaseSelections && databaseSelections.length > 0) {
566
+ // Convert selections to Models.Database format
567
+ filteredDatabases = [];
568
+ const allDatabases = databases ? databases : await fetchAllDatabases(this.database);
569
+ for (const selection of databaseSelections) {
570
+ const database = allDatabases.find(db => db.$id === selection.databaseId);
571
+ if (database) {
572
+ filteredDatabases.push(database);
573
+ }
574
+ else {
575
+ MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
576
+ }
577
+ }
578
+ MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
579
+ }
580
+ const appwriteToX = new AppwriteToX(configToUse, this.appwriteFolderPath, this.storage);
581
+ await appwriteToX.toSchemas(filteredDatabases);
582
+ // Update the controller's config with the synchronized collections
583
+ this.config = appwriteToX.updatedConfig;
584
+ // Write the updated config back to disk
585
+ const generator = new SchemaGenerator(this.config, this.appwriteFolderPath);
586
+ const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
587
+ const isYamlProject = !!yamlConfigPath;
588
+ await generator.updateConfig(this.config, isYamlProject);
589
+ // Regenerate JSON schemas to reflect any table terminology fixes
590
+ try {
591
+ MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
592
+ await createImportSchemas(this.appwriteFolderPath);
593
+ MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
594
+ }
595
+ catch (error) {
596
+ // Log error but don't fail the sync process
597
+ const errorMessage = error instanceof Error ? error.message : String(error);
598
+ MessageFormatter.warning(`Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`, { prefix: "Sync" });
599
+ logger.warn("Schema regeneration failed during sync:", error);
600
+ }
601
+ }
602
+ async selectivePull(databaseSelections, bucketSelections) {
603
+ await this.init();
604
+ if (!this.database) {
605
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
606
+ return;
607
+ }
608
+ MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
609
+ // Convert database selections to Models.Database format
610
+ const selectedDatabases = [];
611
+ for (const dbSelection of databaseSelections) {
612
+ // Get the full database object from the controller
613
+ const databases = await fetchAllDatabases(this.database);
614
+ const database = databases.find(db => db.$id === dbSelection.databaseId);
615
+ if (database) {
616
+ selectedDatabases.push(database);
617
+ MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
618
+ // Log selected tables for this database
619
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
620
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
621
+ }
622
+ }
623
+ else {
624
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
625
+ }
626
+ }
627
+ if (selectedDatabases.length === 0) {
628
+ MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
629
+ return;
630
+ }
631
+ // Log bucket selections if provided
632
+ if (bucketSelections && bucketSelections.length > 0) {
633
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
634
+ for (const bucketSelection of bucketSelections) {
635
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
636
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
637
+ }
638
+ }
639
+ // Perform selective sync using the enhanced synchronizeConfigurations method
640
+ await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
641
+ MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
642
+ }
643
+ async selectivePush(databaseSelections, bucketSelections) {
644
+ await this.init();
645
+ if (!this.database) {
646
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
647
+ return;
648
+ }
649
+ // Always reload config from disk so pushes use current local YAML/Ts definitions
650
+ try {
651
+ await this.reloadConfig();
652
+ MessageFormatter.info("Reloaded config from disk for push", { prefix: "Controller" });
653
+ }
654
+ catch (e) {
655
+ // Non-fatal; continue with existing config
656
+ MessageFormatter.warning("Could not reload config; continuing with current in-memory config", { prefix: "Controller" });
657
+ }
658
+ MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
659
+ // Convert database selections to Models.Database format
660
+ const selectedDatabases = [];
661
+ const serverDatabases = await fetchAllDatabases(this.database);
662
+ const configuredDatabases = this.config?.databases || [];
663
+ for (const dbSelection of databaseSelections) {
664
+ // First try to find on server
665
+ const serverDb = serverDatabases.find(db => db.$id === dbSelection.databaseId);
666
+ if (serverDb) {
667
+ selectedDatabases.push(serverDb);
668
+ MessageFormatter.info(`Selected database: ${serverDb.name} (${serverDb.$id})`, { prefix: "Controller" });
669
+ }
670
+ else {
671
+ // Database doesn't exist on server - check if it's in local config
672
+ const configDb = configuredDatabases.find((db) => db.$id === dbSelection.databaseId);
673
+ if (configDb) {
674
+ // Create a pseudo-database object that ensureDatabasesExist will create
675
+ const dbId = configDb.$id;
676
+ selectedDatabases.push({
677
+ $id: dbId,
678
+ name: configDb.name || dbId,
679
+ $createdAt: new Date().toISOString(),
680
+ $updatedAt: new Date().toISOString(),
681
+ enabled: true,
682
+ });
683
+ MessageFormatter.info(`Selected database: ${configDb.name || dbId} (${dbId}) [will be created]`, { prefix: "Controller" });
684
+ }
685
+ else {
686
+ MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found in server or local config`, { prefix: "Controller" });
687
+ continue;
688
+ }
689
+ }
690
+ // Log selected tables for this database
691
+ if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
692
+ MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
693
+ }
694
+ }
695
+ if (selectedDatabases.length === 0 && (!bucketSelections || bucketSelections.length === 0)) {
696
+ MessageFormatter.warning("No valid databases or buckets selected for push", { prefix: "Controller" });
697
+ return;
698
+ }
699
+ // Push global/root-level buckets if any were selected
700
+ if (bucketSelections && bucketSelections.length > 0) {
701
+ MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
702
+ for (const bucketSelection of bucketSelections) {
703
+ const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
704
+ MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
705
+ }
706
+ const selectedGlobalBucketIds = bucketSelections.map(bs => bs.bucketId);
707
+ await this.pushGlobalBuckets(selectedGlobalBucketIds);
708
+ }
709
+ // Database + tables push
710
+ if (selectedDatabases.length > 0) {
711
+ const databaseCollectionsMap = new Map();
712
+ const allCollections = [
713
+ ...(this.config?.collections || []),
714
+ ...(this.config?.tables || [])
715
+ ];
716
+ for (const dbSelection of databaseSelections) {
717
+ const collectionsForDatabase = [];
718
+ for (const collection of allCollections) {
719
+ const collectionId = collection.$id || collection.id;
720
+ if (dbSelection.tableIds.includes(collectionId)) {
721
+ collectionsForDatabase.push(collection);
722
+ const source = collection._isFromTablesDir ? 'tables/' : 'collections/';
723
+ MessageFormatter.info(` - Selected: ${collection.name || collectionId} → ${dbSelection.databaseId} [${source}]`, { prefix: "Controller" });
724
+ }
725
+ }
726
+ databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
727
+ }
728
+ const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
729
+ .reduce((total, collections) => total + collections.length, 0);
730
+ MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
731
+ await this.ensureDatabasesExist(selectedDatabases);
732
+ await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
733
+ for (const database of selectedDatabases) {
734
+ const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
735
+ if (collectionsForThisDatabase.length > 0) {
736
+ MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} tables to database ${database.$id} (${database.name})`, { prefix: "Controller" });
737
+ await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
738
+ }
739
+ else {
740
+ MessageFormatter.info(`No tables selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
741
+ }
742
+ }
743
+ }
744
+ MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
745
+ }
746
+ async syncDb(databases = [], collections = []) {
747
+ await this.init();
748
+ if (!this.database) {
749
+ MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
750
+ return;
751
+ }
752
+ if (databases.length === 0) {
753
+ const allDatabases = await fetchAllDatabases(this.database);
754
+ databases = allDatabases;
755
+ }
756
+ // Ensure DBs exist
757
+ await this.ensureDatabasesExist(databases);
758
+ await this.ensureDatabaseConfigBucketsExist(databases);
759
+ await this.createOrUpdateCollectionsForDatabases(databases, collections);
760
+ }
761
+ getAppwriteFolderPath() {
762
+ return this.appwriteFolderPath;
763
+ }
764
+ async transferData(options) {
765
+ let sourceClient = this.database;
766
+ let targetClient;
767
+ let sourceDatabases = [];
768
+ let targetDatabases = [];
769
+ if (!sourceClient) {
770
+ MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
771
+ return;
772
+ }
773
+ if (options.isRemote) {
774
+ if (!options.transferEndpoint ||
775
+ !options.transferProject ||
776
+ !options.transferKey) {
777
+ MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
778
+ return;
779
+ }
780
+ const remoteClient = getClient(options.transferEndpoint, options.transferProject, options.transferKey);
781
+ targetClient = new Databases(remoteClient);
782
+ sourceDatabases = await fetchAllDatabases(sourceClient);
783
+ targetDatabases = await fetchAllDatabases(targetClient);
784
+ }
785
+ else {
786
+ targetClient = sourceClient;
787
+ sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
788
+ }
789
+ // Always perform database transfer if databases are specified
790
+ if (options.fromDb && options.targetDb) {
791
+ const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb.$id);
792
+ const targetDb = targetDatabases.find((db) => db.$id === options.targetDb.$id);
793
+ if (!fromDb || !targetDb) {
794
+ MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
795
+ return;
796
+ }
797
+ if (options.isRemote && targetClient) {
798
+ await transferDatabaseLocalToRemote(sourceClient, options.transferEndpoint, options.transferProject, options.transferKey, fromDb.$id, targetDb.$id, options.collections);
799
+ }
800
+ else {
801
+ await transferDatabaseLocalToLocal(sourceClient, fromDb.$id, targetDb.$id, options.collections, this.adapter);
802
+ }
803
+ }
804
+ if (options.transferUsers) {
805
+ if (!options.isRemote) {
806
+ MessageFormatter.warning("User transfer is only supported for remote transfers. Skipping...", { prefix: "Controller" });
807
+ }
808
+ else if (!this.appwriteServer) {
809
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
810
+ return;
811
+ }
812
+ else {
813
+ MessageFormatter.progress("Starting user transfer...", { prefix: "Transfer" });
814
+ const localUsers = new Users(this.appwriteServer);
815
+ await transferUsersLocalToRemote(localUsers, options.transferEndpoint, options.transferProject, options.transferKey);
816
+ MessageFormatter.success("User transfer completed", { prefix: "Transfer" });
817
+ }
818
+ }
819
+ // Handle storage transfer
820
+ if (this.storage && (options.sourceBucket || options.fromDb)) {
821
+ const sourceBucketId = options.sourceBucket?.$id ||
822
+ (options.fromDb &&
823
+ this.config?.documentBucketId &&
824
+ `${this.config.documentBucketId}_${options.fromDb.$id
825
+ .toLowerCase()
826
+ .trim()
827
+ .replace(/\s+/g, "")}`);
828
+ const targetBucketId = options.targetBucket?.$id ||
829
+ (options.targetDb &&
830
+ this.config?.documentBucketId &&
831
+ `${this.config.documentBucketId}_${options.targetDb.$id
832
+ .toLowerCase()
833
+ .trim()
834
+ .replace(/\s+/g, "")}`);
835
+ if (sourceBucketId && targetBucketId) {
836
+ MessageFormatter.progress(`Starting storage transfer from ${sourceBucketId} to ${targetBucketId}`, { prefix: "Transfer" });
837
+ if (options.isRemote) {
838
+ await transferStorageLocalToRemote(this.storage, options.transferEndpoint, options.transferProject, options.transferKey, sourceBucketId, targetBucketId);
839
+ }
840
+ else {
841
+ await transferStorageLocalToLocal(this.storage, sourceBucketId, targetBucketId);
842
+ }
843
+ }
844
+ }
845
+ MessageFormatter.success("Transfer completed", { prefix: "Transfer" });
846
+ }
847
+ async updateFunctionSpecifications(functionId, specification) {
848
+ await this.init();
849
+ if (!this.appwriteServer)
850
+ throw new Error("Appwrite server not initialized");
851
+ MessageFormatter.progress(`Updating function specifications for ${functionId} to ${specification}`, { prefix: "Functions" });
852
+ await updateFunctionSpecifications(this.appwriteServer, functionId, specification);
853
+ MessageFormatter.success(`Successfully updated function specifications for ${functionId} to ${specification}`, { prefix: "Functions" });
854
+ }
855
+ /**
856
+ * Validates the current configuration for collections/tables conflicts
857
+ */
858
+ async validateConfiguration(strictMode = false) {
859
+ await this.init();
860
+ if (!this.config) {
861
+ throw new Error("Configuration not loaded");
862
+ }
863
+ MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
864
+ const validation = strictMode
865
+ ? validateWithStrictMode(this.config, strictMode)
866
+ : validateCollectionsTablesConfig(this.config);
867
+ reportValidationResults(validation, { verbose: true });
868
+ if (validation.isValid) {
869
+ MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
870
+ }
871
+ else {
872
+ MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
873
+ }
874
+ return validation;
875
+ }
876
+ /**
877
+ * Get current session information for debugging/logging purposes
878
+ * Delegates to ConfigManager for session info
879
+ */
880
+ async getSessionInfo() {
881
+ const configManager = ConfigManager.getInstance();
882
+ try {
883
+ const authStatus = await configManager.getAuthStatus();
884
+ return {
885
+ hasSession: authStatus.hasValidSession,
886
+ authMethod: authStatus.authMethod,
887
+ email: authStatus.sessionInfo?.email,
888
+ expiresAt: authStatus.sessionInfo?.expiresAt
889
+ };
890
+ }
891
+ catch (error) {
892
+ // If config not loaded, return empty status
893
+ return {
894
+ hasSession: false
895
+ };
896
+ }
897
+ }
898
+ }