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
@@ -1,1263 +0,0 @@
1
- import {
2
- Client,
3
- Databases,
4
- Query,
5
- Storage,
6
- Users,
7
- type Models,
8
- } from "node-appwrite";
9
- import {
10
- type AppwriteConfig,
11
- type AppwriteFunction,
12
- type Specification,
13
- } from "appwrite-utils";
14
- import {
15
- findAppwriteConfig,
16
- findFunctionsDir,
17
- } from "./utils/loadConfigs.js";
18
- import { normalizeFunctionName, validateFunctionDirectory } from 'appwrite-utils-helpers';
19
- import { UsersController } from "./users/methods.js";
20
- import { AppwriteToX } from "./migrations/appwriteToX.js";
21
- import { ImportController } from "./migrations/importController.js";
22
- import { ImportDataActions } from "./migrations/importDataActions.js";
23
- import {
24
- ensureDatabasesExist,
25
- wipeOtherDatabases,
26
- ensureCollectionsExist,
27
- } from "./databases/setup.js";
28
- import {
29
- createOrUpdateCollections,
30
- createOrUpdateCollectionsViaAdapter,
31
- wipeDatabase,
32
- generateSchemas,
33
- fetchAllCollections,
34
- wipeCollection,
35
- } from "./collections/methods.js";
36
- import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
37
- import {
38
- backupDatabase,
39
- ensureDatabaseConfigBucketsExist,
40
- ensureGlobalBucketsExist,
41
- wipeDocumentStorage,
42
- } from "./storage/methods.js";
43
- import path from "path";
44
- import {
45
- type AfterImportActions,
46
- type ConverterFunctions,
47
- converterFunctions,
48
- validationRules,
49
- type ValidationRules,
50
- } from "appwrite-utils";
51
- import { afterImportActions } from "./migrations/afterImportActions.js";
52
- import {
53
- transferDatabaseLocalToLocal,
54
- transferDatabaseLocalToRemote,
55
- transferStorageLocalToLocal,
56
- transferStorageLocalToRemote,
57
- transferUsersLocalToRemote,
58
- type TransferOptions,
59
- } from "./migrations/transfer.js";
60
- import { getClient, getClientWithAuth } from "appwrite-utils-helpers";
61
- import { getAdapterFromConfig } from "appwrite-utils-helpers";
62
- import type { DatabaseAdapter } from 'appwrite-utils-helpers';
63
- import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie, type SessionAuthInfo } from "appwrite-utils-helpers";
64
- import { fetchAllDatabases } from "./databases/methods.js";
65
- import {
66
- listFunctions,
67
- updateFunctionSpecifications,
68
- } from "./functions/methods.js";
69
- import chalk from "chalk";
70
- import { deployLocalFunction } from "./functions/deployments.js";
71
- import fs from "node:fs";
72
- import {
73
- configureLogging,
74
- updateLogger,
75
- logger,
76
- MessageFormatter,
77
- Messages,
78
- SchemaGenerator,
79
- findYamlConfig,
80
- validateCollectionsTablesConfig,
81
- reportValidationResults,
82
- validateWithStrictMode,
83
- ConfigManager,
84
- type ValidationResult
85
- } from "appwrite-utils-helpers";
86
- import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
87
- import { ClientFactory } from "appwrite-utils-helpers";
88
- import type { DatabaseSelection, BucketSelection } from "./shared/selectionDialogs.js";
89
- import { clearProcessingState, processQueue } from "./shared/operationQueue.js";
90
-
91
- export interface SetupOptions {
92
- databases?: Models.Database[];
93
- collections?: string[];
94
- doBackup?: boolean;
95
- wipeDatabase?: boolean;
96
- wipeCollections?: boolean;
97
- wipeDocumentStorage?: boolean;
98
- wipeUsers?: boolean;
99
- transferUsers?: boolean;
100
- generateSchemas?: boolean;
101
- importData?: boolean;
102
- checkDuplicates?: boolean;
103
- shouldWriteFile?: boolean;
104
- }
105
-
106
- export interface ControllerInitOptions {
107
- validate?: boolean;
108
- strictMode?: boolean;
109
- useSession?: boolean;
110
- sessionCookie?: string;
111
- preferJson?: boolean;
112
- overrides?: {
113
- appwriteEndpoint?: string;
114
- appwriteProject?: string;
115
- appwriteKey?: string;
116
- };
117
- }
118
-
119
- export class UtilsController {
120
- // ──────────────────────────────────────────────────
121
- // SINGLETON PATTERN
122
- // ──────────────────────────────────────────────────
123
- private static instance: UtilsController | null = null;
124
- private isInitialized: boolean = false;
125
-
126
- /**
127
- * Get the UtilsController singleton instance
128
- */
129
- public static getInstance(
130
- currentUserDir: string,
131
- directConfig?: {
132
- appwriteEndpoint?: string;
133
- appwriteProject?: string;
134
- appwriteKey?: string;
135
- }
136
- ): UtilsController {
137
- // Clear instance if currentUserDir has changed
138
- if (UtilsController.instance &&
139
- UtilsController.instance.currentUserDir !== currentUserDir) {
140
- logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
141
- UtilsController.clearInstance();
142
- }
143
-
144
- // Clear instance if directConfig endpoint or project has changed
145
- if (UtilsController.instance && directConfig) {
146
- const existingConfig = UtilsController.instance.config;
147
- if (existingConfig) {
148
- const endpointChanged = directConfig.appwriteEndpoint &&
149
- existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
150
- const projectChanged = directConfig.appwriteProject &&
151
- existingConfig.appwriteProject !== directConfig.appwriteProject;
152
-
153
- if (endpointChanged || projectChanged) {
154
- logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
155
- UtilsController.clearInstance();
156
- }
157
- }
158
- }
159
-
160
- if (!UtilsController.instance) {
161
- UtilsController.instance = new UtilsController(currentUserDir, directConfig);
162
- }
163
-
164
- return UtilsController.instance;
165
- }
166
-
167
- /**
168
- * Clear the singleton instance (useful for testing)
169
- */
170
- public static clearInstance(): void {
171
- UtilsController.instance = null;
172
- }
173
-
174
- // ──────────────────────────────────────────────────
175
- // INSTANCE FIELDS
176
- // ──────────────────────────────────────────────────
177
- private appwriteFolderPath?: string;
178
- private appwriteConfigPath?: string;
179
- private currentUserDir: string;
180
- public config?: AppwriteConfig;
181
- public appwriteServer?: Client;
182
- public database?: Databases;
183
- public storage?: Storage;
184
- public adapter?: DatabaseAdapter;
185
- public converterDefinitions: ConverterFunctions = converterFunctions;
186
- public validityRuleDefinitions: ValidationRules = validationRules;
187
- public afterImportActionsDefinitions: AfterImportActions = afterImportActions;
188
-
189
- constructor(
190
- currentUserDir: string,
191
- directConfig?: {
192
- appwriteEndpoint?: string;
193
- appwriteProject?: string;
194
- appwriteKey?: string;
195
- }
196
- ) {
197
- this.currentUserDir = currentUserDir;
198
- const basePath = currentUserDir;
199
-
200
- if (directConfig) {
201
- let hasErrors = false;
202
- if (!directConfig.appwriteEndpoint) {
203
- MessageFormatter.error("Appwrite endpoint is required", undefined, { prefix: "Config" });
204
- hasErrors = true;
205
- }
206
- if (!directConfig.appwriteProject) {
207
- MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
208
- hasErrors = true;
209
- }
210
- // Check authentication: either API key or session auth is required
211
- const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
212
- hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
213
-
214
- if (!directConfig.appwriteKey && !hasValidSession) {
215
- MessageFormatter.error(
216
- "Authentication required: provide an API key or login with 'appwrite login'",
217
- undefined,
218
- { prefix: "Config" }
219
- );
220
- hasErrors = true;
221
- } else if (!directConfig.appwriteKey && hasValidSession) {
222
- MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
223
- } else if (directConfig.appwriteKey && hasValidSession) {
224
- MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
225
- }
226
- if (!hasErrors) {
227
- // Only set config if we have all required fields
228
- this.appwriteFolderPath = basePath;
229
- this.config = {
230
- appwriteEndpoint: directConfig.appwriteEndpoint!,
231
- appwriteProject: directConfig.appwriteProject!,
232
- appwriteKey: directConfig.appwriteKey || "",
233
- appwriteClient: null,
234
- apiMode: "auto", // Default to auto-detect for dual API support
235
- authMethod: "auto", // Default to auto-detect authentication method
236
- enableBackups: false,
237
- backupInterval: 0,
238
- backupRetention: 0,
239
- enableBackupCleanup: false,
240
- enableMockData: false,
241
- documentBucketId: "",
242
- usersCollectionName: "",
243
- databases: [],
244
- buckets: [],
245
- functions: [],
246
- logging: {
247
- enabled: false,
248
- level: "info",
249
- console: false,
250
- },
251
- };
252
- }
253
- } else {
254
- // Try to find config file
255
- const appwriteConfigFound = findAppwriteConfig(basePath);
256
- if (!appwriteConfigFound) {
257
- MessageFormatter.warning(
258
- "No appwriteConfig.ts found and no direct configuration provided",
259
- { prefix: "Config" }
260
- );
261
- return;
262
- }
263
- this.appwriteConfigPath = appwriteConfigFound;
264
- this.appwriteFolderPath = appwriteConfigFound; // For YAML configs, findAppwriteConfig already returns the correct directory
265
- }
266
- }
267
-
268
- async init(options: ControllerInitOptions = {}) {
269
- const { validate = false, strictMode = false, preferJson = false, useSession, sessionCookie, overrides } = options;
270
- const configManager = ConfigManager.getInstance();
271
-
272
- // Load config if not already loaded
273
- if (!configManager.hasConfig()) {
274
- await configManager.loadConfig({
275
- configDir: this.currentUserDir,
276
- validate,
277
- strictMode,
278
- preferJson,
279
- useSession,
280
- explicitSessionCookie: sessionCookie,
281
- overrides,
282
- });
283
- }
284
-
285
- const config = configManager.getConfig();
286
-
287
- // Configure logging based on config
288
- if (config.logging) {
289
- configureLogging(config.logging);
290
- updateLogger();
291
- }
292
-
293
- // Create client and adapter (session already in config from ConfigManager)
294
- const { client, adapter } = await ClientFactory.createFromConfig(config);
295
-
296
- this.appwriteServer = client;
297
- this.adapter = adapter;
298
- this.config = config;
299
-
300
- // Update config.apiMode from adapter if it's auto or not set
301
- if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
302
- this.config.apiMode = adapter.getApiMode();
303
- logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
304
- }
305
-
306
- this.database = new Databases(this.appwriteServer);
307
- this.storage = new Storage(this.appwriteServer);
308
- this.config.appwriteClient = this.appwriteServer;
309
-
310
- // Log only on FIRST initialization to avoid spam
311
- if (!this.isInitialized) {
312
- const apiMode = adapter.getApiMode();
313
- const configApiMode = this.config.apiMode;
314
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
315
- this.isInitialized = true;
316
- } else {
317
- logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
318
- }
319
- }
320
-
321
- async reloadConfig() {
322
- const configManager = ConfigManager.getInstance();
323
-
324
- // Session preservation is automatic in ConfigManager
325
- const config = await configManager.reloadConfig();
326
-
327
- // Configure logging based on updated config
328
- if (config.logging) {
329
- configureLogging(config.logging);
330
- updateLogger();
331
- }
332
-
333
- // Recreate client and adapter
334
- const { client, adapter } = await ClientFactory.createFromConfig(config);
335
-
336
- this.appwriteServer = client;
337
- this.adapter = adapter;
338
- this.config = config;
339
- this.database = new Databases(this.appwriteServer);
340
- this.storage = new Storage(this.appwriteServer);
341
- this.config.appwriteClient = this.appwriteServer;
342
-
343
- logger.debug("Config reloaded, adapter refreshed", { prefix: "UtilsController" });
344
- }
345
-
346
-
347
- async ensureDatabaseConfigBucketsExist(databases: Models.Database[] = []) {
348
- await this.init();
349
- if (!this.storage) {
350
- MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
351
- return;
352
- }
353
- if (!this.config) {
354
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
355
- return;
356
- }
357
- await ensureDatabaseConfigBucketsExist(
358
- this.storage,
359
- this.config,
360
- databases
361
- );
362
- }
363
-
364
- async pushGlobalBuckets(selectedBucketIds?: string[]) {
365
- await this.init();
366
- if (!this.storage) {
367
- MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
368
- return;
369
- }
370
- if (!this.config) {
371
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
372
- return;
373
- }
374
- await ensureGlobalBucketsExist(this.storage, this.config, selectedBucketIds);
375
- }
376
-
377
- async ensureDatabasesExist(databases?: Models.Database[]) {
378
- await this.init();
379
- if (!this.config) {
380
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
381
- return;
382
- }
383
- await this.ensureDatabaseConfigBucketsExist(databases);
384
- await ensureDatabasesExist(this.config, databases);
385
- }
386
-
387
- async ensureCollectionsExist(
388
- database: Models.Database,
389
- collections?: Models.Collection[]
390
- ) {
391
- await this.init();
392
- if (!this.config) {
393
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
394
- return;
395
- }
396
- await ensureCollectionsExist(this.config, database, collections);
397
- }
398
-
399
- async getDatabasesByIds(ids: string[]) {
400
- await this.init();
401
- if (!this.database) {
402
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
403
- return;
404
- }
405
- if (ids.length === 0) return [];
406
- const dbs = await this.database.list([
407
- Query.limit(500),
408
- Query.equal("$id", ids),
409
- ]);
410
- return dbs.databases;
411
- }
412
-
413
- async fetchAllBuckets(): Promise<{ buckets: Models.Bucket[] }> {
414
- await this.init();
415
- if (!this.storage) {
416
- MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
417
- return { buckets: [] };
418
- }
419
-
420
- try {
421
- const result = await this.storage.listBuckets([
422
- Query.limit(1000) // Increase limit to get all buckets
423
- ]);
424
-
425
- MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
426
- return result;
427
- } catch (error: any) {
428
- MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
429
- return { buckets: [] };
430
- }
431
- }
432
-
433
- async wipeOtherDatabases(databasesToKeep: Models.Database[]) {
434
- await this.init();
435
- if (!this.database) {
436
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
437
- return;
438
- }
439
- await wipeOtherDatabases(this.database, databasesToKeep);
440
- }
441
-
442
- async wipeUsers() {
443
- await this.init();
444
- if (!this.config || !this.database) {
445
- MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
446
- return;
447
- }
448
- const usersController = new UsersController(this.config, this.database);
449
- await usersController.wipeUsers();
450
- }
451
-
452
- async backupDatabase(database: Models.Database, format: 'json' | 'zip' = 'json') {
453
- await this.init();
454
- if (!this.database || !this.storage || !this.config) {
455
- MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
456
- return;
457
- }
458
- await backupDatabase(
459
- this.config,
460
- this.database,
461
- database.$id,
462
- this.storage,
463
- format
464
- );
465
- }
466
-
467
- async listAllFunctions() {
468
- await this.init();
469
- if (!this.appwriteServer) {
470
- MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
471
- return [];
472
- }
473
- const { functions } = await listFunctions(this.appwriteServer, [
474
- Query.limit(1000),
475
- ]);
476
- return functions;
477
- }
478
-
479
- async findFunctionDirectories() {
480
- if (!this.appwriteFolderPath) {
481
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
482
- return new Map();
483
- }
484
- const functionsDir = findFunctionsDir(this.appwriteFolderPath);
485
- if (!functionsDir) {
486
- MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
487
- return new Map();
488
- }
489
-
490
- const functionDirMap = new Map<string, string>();
491
- const entries = fs.readdirSync(functionsDir, { withFileTypes: true });
492
-
493
- for (const entry of entries) {
494
- if (entry.isDirectory()) {
495
- const functionPath = path.join(functionsDir, entry.name);
496
-
497
- // Validate it's a function directory
498
- if (!validateFunctionDirectory(functionPath)) {
499
- continue; // Skip invalid directories
500
- }
501
-
502
- // Match with config functions using normalized names
503
- if (this.config?.functions) {
504
- const normalizedEntryName = normalizeFunctionName(entry.name);
505
- const matchingFunc = this.config.functions.find(
506
- (f) => normalizeFunctionName(f.name) === normalizedEntryName
507
- );
508
- if (matchingFunc) {
509
- functionDirMap.set(matchingFunc.name, functionPath);
510
- }
511
- }
512
- }
513
- }
514
- return functionDirMap;
515
- }
516
-
517
- async deployFunction(
518
- functionName: string,
519
- functionPath?: string,
520
- functionConfig?: AppwriteFunction
521
- ) {
522
- await this.init();
523
- if (!this.appwriteServer) {
524
- MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
525
- return;
526
- }
527
-
528
- if (!functionConfig) {
529
- functionConfig = this.config?.functions?.find(
530
- (f) => f.name === functionName
531
- );
532
- }
533
- if (!functionConfig) {
534
- MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
535
- return;
536
- }
537
-
538
- await deployLocalFunction(
539
- this.appwriteServer,
540
- functionName,
541
- functionConfig,
542
- functionPath,
543
- this.appwriteFolderPath
544
- );
545
- }
546
-
547
- async syncFunctions() {
548
- await this.init();
549
- if (!this.appwriteServer) {
550
- MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
551
- return;
552
- }
553
-
554
- const localFunctions = this.config?.functions || [];
555
- const remoteFunctions = await listFunctions(this.appwriteServer, [
556
- Query.limit(1000),
557
- ]);
558
-
559
- for (const localFunction of localFunctions) {
560
- MessageFormatter.progress(`Syncing function ${localFunction.name}...`, { prefix: "Functions" });
561
- await this.deployFunction(localFunction.name);
562
- }
563
-
564
- MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
565
- }
566
-
567
- async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
568
- await this.init();
569
- if (!this.database || !this.config) throw new Error("Database not initialized");
570
- try {
571
- // Session is already in config from ConfigManager
572
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
573
- if (apiMode === 'tablesdb') {
574
- await wipeAllTables(adapter, database.$id);
575
- } else {
576
- await wipeDatabase(this.database, database.$id);
577
- }
578
- } catch {
579
- await wipeDatabase(this.database, database.$id);
580
- }
581
- if (wipeBucket) {
582
- await this.wipeBucketFromDatabase(database);
583
- }
584
- }
585
-
586
- async wipeBucketFromDatabase(database: Models.Database) {
587
- // Check configured bucket in database config
588
- const configuredBucket = this.config?.databases?.find(
589
- (db) => db.$id === database.$id
590
- )?.bucket;
591
- if (configuredBucket?.$id) {
592
- await this.wipeDocumentStorage(configuredBucket.$id);
593
- }
594
-
595
- // Also check for document bucket ID pattern
596
- if (this.config?.documentBucketId) {
597
- const documentBucketId = `${this.config.documentBucketId}_${database.$id
598
- .toLowerCase()
599
- .trim()
600
- .replace(/\s+/g, "")}`;
601
- try {
602
- await this.wipeDocumentStorage(documentBucketId);
603
- } catch (error: any) {
604
- // Ignore if bucket doesn't exist
605
- if (error?.type !== "storage_bucket_not_found") {
606
- throw error;
607
- }
608
- }
609
- }
610
- }
611
-
612
- async wipeCollection(
613
- database: Models.Database,
614
- collection: Models.Collection
615
- ) {
616
- await this.init();
617
- if (!this.database || !this.config) throw new Error("Database not initialized");
618
- try {
619
- // Session is already in config from ConfigManager
620
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
621
- if (apiMode === 'tablesdb') {
622
- await wipeTableRows(adapter, database.$id, collection.$id);
623
- } else {
624
- await wipeCollection(this.database, database.$id, collection.$id);
625
- }
626
- } catch {
627
- await wipeCollection(this.database, database.$id, collection.$id);
628
- }
629
- }
630
-
631
- async wipeDocumentStorage(bucketId: string) {
632
- await this.init();
633
- if (!this.storage) throw new Error("Storage not initialized");
634
- await wipeDocumentStorage(this.storage, bucketId);
635
- }
636
-
637
- async createOrUpdateCollectionsForDatabases(
638
- databases: Models.Database[],
639
- collections: Models.Collection[] = []
640
- ) {
641
- await this.init();
642
- if (!this.database || !this.config)
643
- throw new Error("Database or config not initialized");
644
- for (const database of databases) {
645
- await this.createOrUpdateCollections(database, undefined, collections);
646
- }
647
- }
648
-
649
- async createOrUpdateCollections(
650
- database: Models.Database,
651
- deletedCollections?: { collectionId: string; collectionName: string }[],
652
- collections: Models.Collection[] = []
653
- ) {
654
- await this.init();
655
- if (!this.database || !this.config)
656
- throw new Error("Database or config not initialized");
657
-
658
- // Ensure apiMode is properly set from adapter
659
- if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
660
- this.config.apiMode = this.adapter.getApiMode();
661
- logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
662
- }
663
-
664
- // Ensure we don't carry state between databases in a multi-db push
665
- // This resets processed sets and name->id mapping per database
666
- try {
667
- clearProcessingState();
668
- } catch {}
669
-
670
- // Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
671
- if (this.adapter) {
672
- logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
673
- prefix: "UtilsController",
674
- apiMode: this.adapter.getApiMode()
675
- });
676
- await createOrUpdateCollectionsViaAdapter(
677
- this.adapter,
678
- database.$id,
679
- this.config,
680
- deletedCollections,
681
- collections
682
- );
683
- } else {
684
- // Fallback if adapter is unavailable for some reason
685
- logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
686
- await createOrUpdateCollections(
687
- this.database,
688
- database.$id,
689
- this.config,
690
- deletedCollections,
691
- collections
692
- );
693
- }
694
-
695
- // Safety net: Process any remaining queued operations to complete relationship sync
696
- try {
697
- MessageFormatter.info(`🔄 Processing final operation queue for database ${database.$id}`, { prefix: "UtilsController" });
698
- await processQueue(this.adapter || this.database!, database.$id);
699
- MessageFormatter.info(`✅ Operation queue processing completed`, { prefix: "UtilsController" });
700
- } catch (error) {
701
- MessageFormatter.error(`Failed to process operation queue`, error instanceof Error ? error : new Error(String(error)), { prefix: 'UtilsController' });
702
- }
703
- }
704
-
705
- async generateSchemas() {
706
- // Schema generation doesn't need Appwrite connection, just config
707
- if (!this.config) {
708
- MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
709
- try {
710
- const configManager = ConfigManager.getInstance();
711
-
712
- // Load config if not already loaded
713
- if (!configManager.hasConfig()) {
714
- await configManager.loadConfig({
715
- configDir: this.currentUserDir,
716
- validate: false,
717
- strictMode: false,
718
- });
719
- }
720
-
721
- this.config = configManager.getConfig();
722
- MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
723
- } catch (error) {
724
- MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
725
- return;
726
- }
727
- }
728
-
729
- if (!this.appwriteFolderPath) {
730
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
731
- return;
732
- }
733
-
734
- await generateSchemas(this.config, this.appwriteFolderPath);
735
- }
736
-
737
- async importData(options: SetupOptions = {}) {
738
- await this.init();
739
- if (!this.database) {
740
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
741
- return;
742
- }
743
- if (!this.storage) {
744
- MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
745
- return;
746
- }
747
- if (!this.config) {
748
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
749
- return;
750
- }
751
- if (!this.appwriteFolderPath) {
752
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
753
- return;
754
- }
755
-
756
- const importDataActions = new ImportDataActions(
757
- this.database,
758
- this.storage,
759
- this.config,
760
- this.converterDefinitions,
761
- this.validityRuleDefinitions,
762
- this.afterImportActionsDefinitions
763
- );
764
-
765
- const importController = new ImportController(
766
- this.config,
767
- this.database,
768
- this.storage,
769
- this.appwriteFolderPath,
770
- importDataActions,
771
- options,
772
- options.databases
773
- );
774
- await importController.run(options.collections);
775
- }
776
-
777
- async synchronizeConfigurations(
778
- databases?: Models.Database[],
779
- config?: AppwriteConfig,
780
- databaseSelections?: DatabaseSelection[],
781
- bucketSelections?: BucketSelection[]
782
- ) {
783
- await this.init();
784
- if (!this.storage) {
785
- MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
786
- return;
787
- }
788
- const configToUse = config || this.config;
789
- if (!configToUse) {
790
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
791
- return;
792
- }
793
- if (!this.appwriteFolderPath) {
794
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
795
- return;
796
- }
797
-
798
- // If selections are provided, filter the databases accordingly
799
- let filteredDatabases = databases;
800
- if (databaseSelections && databaseSelections.length > 0) {
801
- // Convert selections to Models.Database format
802
- filteredDatabases = [];
803
- const allDatabases = databases ? databases : await fetchAllDatabases(this.database!);
804
-
805
- for (const selection of databaseSelections) {
806
- const database = allDatabases.find(db => db.$id === selection.databaseId);
807
- if (database) {
808
- filteredDatabases.push(database);
809
- } else {
810
- MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
811
- }
812
- }
813
-
814
- MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
815
- }
816
-
817
- const appwriteToX = new AppwriteToX(
818
- configToUse,
819
- this.appwriteFolderPath,
820
- this.storage
821
- );
822
- await appwriteToX.toSchemas(filteredDatabases);
823
-
824
- // Update the controller's config with the synchronized collections
825
- this.config = appwriteToX.updatedConfig;
826
-
827
- // Write the updated config back to disk
828
- const generator = new SchemaGenerator(this.config, this.appwriteFolderPath);
829
- const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
830
- const isYamlProject = !!yamlConfigPath;
831
- await generator.updateConfig(this.config, isYamlProject);
832
-
833
- // Regenerate JSON schemas to reflect any table terminology fixes
834
- try {
835
- MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
836
- await createImportSchemas(this.appwriteFolderPath);
837
- MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
838
- } catch (error) {
839
- // Log error but don't fail the sync process
840
- const errorMessage = error instanceof Error ? error.message : String(error);
841
- MessageFormatter.warning(
842
- `Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`,
843
- { prefix: "Sync" }
844
- );
845
- logger.warn("Schema regeneration failed during sync:", error);
846
- }
847
- }
848
-
849
- async selectivePull(
850
- databaseSelections: DatabaseSelection[],
851
- bucketSelections: BucketSelection[]
852
- ): Promise<void> {
853
- await this.init();
854
- if (!this.database) {
855
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
856
- return;
857
- }
858
-
859
- MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
860
-
861
- // Convert database selections to Models.Database format
862
- const selectedDatabases: Models.Database[] = [];
863
-
864
- for (const dbSelection of databaseSelections) {
865
- // Get the full database object from the controller
866
- const databases = await fetchAllDatabases(this.database);
867
- const database = databases.find(db => db.$id === dbSelection.databaseId);
868
-
869
- if (database) {
870
- selectedDatabases.push(database);
871
- MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
872
-
873
- // Log selected tables for this database
874
- if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
875
- MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
876
- }
877
- } else {
878
- MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
879
- }
880
- }
881
-
882
- if (selectedDatabases.length === 0) {
883
- MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
884
- return;
885
- }
886
-
887
- // Log bucket selections if provided
888
- if (bucketSelections && bucketSelections.length > 0) {
889
- MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
890
- for (const bucketSelection of bucketSelections) {
891
- const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
892
- MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
893
- }
894
- }
895
-
896
- // Perform selective sync using the enhanced synchronizeConfigurations method
897
- await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
898
-
899
- MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
900
- }
901
-
902
- async selectivePush(
903
- databaseSelections: DatabaseSelection[],
904
- bucketSelections: BucketSelection[]
905
- ): Promise<void> {
906
- await this.init();
907
- if (!this.database) {
908
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
909
- return;
910
- }
911
-
912
- // Always reload config from disk so pushes use current local YAML/Ts definitions
913
- try {
914
- await this.reloadConfig();
915
- MessageFormatter.info("Reloaded config from disk for push", { prefix: "Controller" });
916
- } catch (e) {
917
- // Non-fatal; continue with existing config
918
- MessageFormatter.warning("Could not reload config; continuing with current in-memory config", { prefix: "Controller" });
919
- }
920
-
921
- MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
922
-
923
- // Convert database selections to Models.Database format
924
- const selectedDatabases: Models.Database[] = [];
925
- const serverDatabases = await fetchAllDatabases(this.database);
926
- const configuredDatabases = this.config?.databases || [];
927
-
928
- for (const dbSelection of databaseSelections) {
929
- // First try to find on server
930
- const serverDb = serverDatabases.find(db => db.$id === dbSelection.databaseId);
931
-
932
- if (serverDb) {
933
- selectedDatabases.push(serverDb);
934
- MessageFormatter.info(`Selected database: ${serverDb.name} (${serverDb.$id})`, { prefix: "Controller" });
935
- } else {
936
- // Database doesn't exist on server - check if it's in local config
937
- const configDb = configuredDatabases.find((db: any) => db.$id === dbSelection.databaseId);
938
-
939
- if (configDb) {
940
- // Create a pseudo-database object that ensureDatabasesExist will create
941
- const dbId = configDb.$id;
942
- selectedDatabases.push({
943
- $id: dbId,
944
- name: configDb.name || dbId,
945
- $createdAt: new Date().toISOString(),
946
- $updatedAt: new Date().toISOString(),
947
- enabled: true,
948
- } as Models.Database);
949
- MessageFormatter.info(`Selected database: ${configDb.name || dbId} (${dbId}) [will be created]`, { prefix: "Controller" });
950
- } else {
951
- MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found in server or local config`, { prefix: "Controller" });
952
- continue;
953
- }
954
- }
955
-
956
- // Log selected tables for this database
957
- if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
958
- MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
959
- }
960
- }
961
-
962
- if (selectedDatabases.length === 0 && (!bucketSelections || bucketSelections.length === 0)) {
963
- MessageFormatter.warning("No valid databases or buckets selected for push", { prefix: "Controller" });
964
- return;
965
- }
966
-
967
- // Push global/root-level buckets if any were selected
968
- if (bucketSelections && bucketSelections.length > 0) {
969
- MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
970
- for (const bucketSelection of bucketSelections) {
971
- const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
972
- MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
973
- }
974
- const selectedGlobalBucketIds = bucketSelections.map(bs => bs.bucketId);
975
- await this.pushGlobalBuckets(selectedGlobalBucketIds);
976
- }
977
-
978
- // Database + tables push
979
- if (selectedDatabases.length > 0) {
980
- const databaseCollectionsMap = new Map<string, any[]>();
981
-
982
- const allCollections = [
983
- ...(this.config?.collections || []),
984
- ...(this.config?.tables || [])
985
- ];
986
-
987
- for (const dbSelection of databaseSelections) {
988
- const collectionsForDatabase: any[] = [];
989
-
990
- for (const collection of allCollections) {
991
- const collectionId = collection.$id || (collection as any).id;
992
- if (dbSelection.tableIds.includes(collectionId)) {
993
- collectionsForDatabase.push(collection);
994
- const source = (collection as any)._isFromTablesDir ? 'tables/' : 'collections/';
995
- MessageFormatter.info(` - Selected: ${collection.name || collectionId} → ${dbSelection.databaseId} [${source}]`, { prefix: "Controller" });
996
- }
997
- }
998
-
999
- databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
1000
- }
1001
-
1002
- const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
1003
- .reduce((total, collections) => total + collections.length, 0);
1004
-
1005
- MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
1006
-
1007
- await this.ensureDatabasesExist(selectedDatabases);
1008
- await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
1009
-
1010
- for (const database of selectedDatabases) {
1011
- const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
1012
- if (collectionsForThisDatabase.length > 0) {
1013
- MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} tables to database ${database.$id} (${database.name})`, { prefix: "Controller" });
1014
- await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
1015
- } else {
1016
- MessageFormatter.info(`No tables selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
1017
- }
1018
- }
1019
- }
1020
-
1021
- MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
1022
- }
1023
-
1024
- async syncDb(
1025
- databases: Models.Database[] = [],
1026
- collections: Models.Collection[] = []
1027
- ) {
1028
- await this.init();
1029
- if (!this.database) {
1030
- MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
1031
- return;
1032
- }
1033
- if (databases.length === 0) {
1034
- const allDatabases = await fetchAllDatabases(this.database);
1035
- databases = allDatabases;
1036
- }
1037
- // Ensure DBs exist
1038
- await this.ensureDatabasesExist(databases);
1039
- await this.ensureDatabaseConfigBucketsExist(databases);
1040
-
1041
- await this.createOrUpdateCollectionsForDatabases(databases, collections);
1042
- }
1043
-
1044
- getAppwriteFolderPath() {
1045
- return this.appwriteFolderPath;
1046
- }
1047
-
1048
- async transferData(options: TransferOptions): Promise<void> {
1049
- let sourceClient = this.database;
1050
- let targetClient: Databases | undefined;
1051
- let sourceDatabases: Models.Database[] = [];
1052
- let targetDatabases: Models.Database[] = [];
1053
-
1054
- if (!sourceClient) {
1055
- MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
1056
- return;
1057
- }
1058
-
1059
- if (options.isRemote) {
1060
- if (
1061
- !options.transferEndpoint ||
1062
- !options.transferProject ||
1063
- !options.transferKey
1064
- ) {
1065
- MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
1066
- return;
1067
- }
1068
-
1069
- const remoteClient = getClient(
1070
- options.transferEndpoint,
1071
- options.transferProject,
1072
- options.transferKey
1073
- );
1074
-
1075
- targetClient = new Databases(remoteClient);
1076
- sourceDatabases = await fetchAllDatabases(sourceClient);
1077
- targetDatabases = await fetchAllDatabases(targetClient);
1078
- } else {
1079
- targetClient = sourceClient;
1080
- sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
1081
- }
1082
-
1083
- // Always perform database transfer if databases are specified
1084
- if (options.fromDb && options.targetDb) {
1085
- const fromDb = sourceDatabases.find(
1086
- (db) => db.$id === options.fromDb!.$id
1087
- );
1088
- const targetDb = targetDatabases.find(
1089
- (db) => db.$id === options.targetDb!.$id
1090
- );
1091
-
1092
- if (!fromDb || !targetDb) {
1093
- MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
1094
- return;
1095
- }
1096
-
1097
- if (options.isRemote && targetClient) {
1098
- await transferDatabaseLocalToRemote(
1099
- sourceClient,
1100
- options.transferEndpoint!,
1101
- options.transferProject!,
1102
- options.transferKey!,
1103
- fromDb.$id,
1104
- targetDb.$id,
1105
- options.collections
1106
- );
1107
- } else {
1108
- await transferDatabaseLocalToLocal(
1109
- sourceClient,
1110
- fromDb.$id,
1111
- targetDb.$id,
1112
- options.collections,
1113
- this.adapter
1114
- );
1115
- }
1116
- }
1117
-
1118
- if (options.transferUsers) {
1119
- if (!options.isRemote) {
1120
- MessageFormatter.warning(
1121
- "User transfer is only supported for remote transfers. Skipping...",
1122
- { prefix: "Controller" }
1123
- );
1124
- } else if (!this.appwriteServer) {
1125
- MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
1126
- return;
1127
- } else {
1128
- MessageFormatter.progress("Starting user transfer...", { prefix: "Transfer" });
1129
- const localUsers = new Users(this.appwriteServer);
1130
- await transferUsersLocalToRemote(
1131
- localUsers,
1132
- options.transferEndpoint!,
1133
- options.transferProject!,
1134
- options.transferKey!
1135
- );
1136
- MessageFormatter.success("User transfer completed", { prefix: "Transfer" });
1137
- }
1138
- }
1139
-
1140
- // Handle storage transfer
1141
- if (this.storage && (options.sourceBucket || options.fromDb)) {
1142
- const sourceBucketId =
1143
- options.sourceBucket?.$id ||
1144
- (options.fromDb &&
1145
- this.config?.documentBucketId &&
1146
- `${this.config.documentBucketId}_${options.fromDb.$id
1147
- .toLowerCase()
1148
- .trim()
1149
- .replace(/\s+/g, "")}`);
1150
-
1151
- const targetBucketId =
1152
- options.targetBucket?.$id ||
1153
- (options.targetDb &&
1154
- this.config?.documentBucketId &&
1155
- `${this.config.documentBucketId}_${options.targetDb.$id
1156
- .toLowerCase()
1157
- .trim()
1158
- .replace(/\s+/g, "")}`);
1159
-
1160
- if (sourceBucketId && targetBucketId) {
1161
- MessageFormatter.progress(
1162
- `Starting storage transfer from ${sourceBucketId} to ${targetBucketId}`,
1163
- { prefix: "Transfer" }
1164
- );
1165
-
1166
- if (options.isRemote) {
1167
- await transferStorageLocalToRemote(
1168
- this.storage,
1169
- options.transferEndpoint!,
1170
- options.transferProject!,
1171
- options.transferKey!,
1172
- sourceBucketId,
1173
- targetBucketId
1174
- );
1175
- } else {
1176
- await transferStorageLocalToLocal(
1177
- this.storage,
1178
- sourceBucketId,
1179
- targetBucketId
1180
- );
1181
- }
1182
- }
1183
- }
1184
-
1185
- MessageFormatter.success("Transfer completed", { prefix: "Transfer" });
1186
- }
1187
-
1188
- async updateFunctionSpecifications(
1189
- functionId: string,
1190
- specification: Specification
1191
- ) {
1192
- await this.init();
1193
- if (!this.appwriteServer)
1194
- throw new Error("Appwrite server not initialized");
1195
- MessageFormatter.progress(
1196
- `Updating function specifications for ${functionId} to ${specification}`,
1197
- { prefix: "Functions" }
1198
- );
1199
- await updateFunctionSpecifications(
1200
- this.appwriteServer,
1201
- functionId,
1202
- specification
1203
- );
1204
- MessageFormatter.success(
1205
- `Successfully updated function specifications for ${functionId} to ${specification}`,
1206
- { prefix: "Functions" }
1207
- );
1208
- }
1209
-
1210
- /**
1211
- * Validates the current configuration for collections/tables conflicts
1212
- */
1213
- async validateConfiguration(strictMode: boolean = false): Promise<ValidationResult> {
1214
- await this.init();
1215
- if (!this.config) {
1216
- throw new Error("Configuration not loaded");
1217
- }
1218
-
1219
- MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
1220
-
1221
- const validation = strictMode
1222
- ? validateWithStrictMode(this.config, strictMode)
1223
- : validateCollectionsTablesConfig(this.config);
1224
-
1225
- reportValidationResults(validation, { verbose: true });
1226
-
1227
- if (validation.isValid) {
1228
- MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
1229
- } else {
1230
- MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
1231
- }
1232
-
1233
- return validation;
1234
- }
1235
-
1236
- /**
1237
- * Get current session information for debugging/logging purposes
1238
- * Delegates to ConfigManager for session info
1239
- */
1240
- public async getSessionInfo(): Promise<{
1241
- hasSession: boolean;
1242
- authMethod?: string;
1243
- email?: string;
1244
- expiresAt?: string;
1245
- }> {
1246
- const configManager = ConfigManager.getInstance();
1247
-
1248
- try {
1249
- const authStatus = await configManager.getAuthStatus();
1250
- return {
1251
- hasSession: authStatus.hasValidSession,
1252
- authMethod: authStatus.authMethod,
1253
- email: authStatus.sessionInfo?.email,
1254
- expiresAt: authStatus.sessionInfo?.expiresAt
1255
- };
1256
- } catch (error) {
1257
- // If config not loaded, return empty status
1258
- return {
1259
- hasSession: false
1260
- };
1261
- }
1262
- }
1263
- }