appwrite-utils-cli 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +251 -29
  3. package/dist/adapters/AdapterFactory.d.ts +10 -3
  4. package/dist/adapters/AdapterFactory.js +213 -17
  5. package/dist/adapters/TablesDBAdapter.js +60 -17
  6. package/dist/backups/operations/bucketBackup.d.ts +19 -0
  7. package/dist/backups/operations/bucketBackup.js +197 -0
  8. package/dist/backups/operations/collectionBackup.d.ts +30 -0
  9. package/dist/backups/operations/collectionBackup.js +201 -0
  10. package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
  11. package/dist/backups/operations/comprehensiveBackup.js +238 -0
  12. package/dist/backups/schemas/bucketManifest.d.ts +93 -0
  13. package/dist/backups/schemas/bucketManifest.js +33 -0
  14. package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
  15. package/dist/backups/schemas/comprehensiveManifest.js +32 -0
  16. package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
  17. package/dist/backups/tracking/centralizedTracking.js +274 -0
  18. package/dist/cli/commands/configCommands.d.ts +8 -0
  19. package/dist/cli/commands/configCommands.js +160 -0
  20. package/dist/cli/commands/databaseCommands.d.ts +13 -0
  21. package/dist/cli/commands/databaseCommands.js +479 -0
  22. package/dist/cli/commands/functionCommands.d.ts +7 -0
  23. package/dist/cli/commands/functionCommands.js +289 -0
  24. package/dist/cli/commands/schemaCommands.d.ts +7 -0
  25. package/dist/cli/commands/schemaCommands.js +134 -0
  26. package/dist/cli/commands/transferCommands.d.ts +5 -0
  27. package/dist/cli/commands/transferCommands.js +384 -0
  28. package/dist/collections/attributes.d.ts +5 -4
  29. package/dist/collections/attributes.js +539 -246
  30. package/dist/collections/indexes.js +39 -37
  31. package/dist/collections/methods.d.ts +2 -16
  32. package/dist/collections/methods.js +90 -538
  33. package/dist/collections/transferOperations.d.ts +7 -0
  34. package/dist/collections/transferOperations.js +331 -0
  35. package/dist/collections/wipeOperations.d.ts +16 -0
  36. package/dist/collections/wipeOperations.js +328 -0
  37. package/dist/config/configMigration.d.ts +87 -0
  38. package/dist/config/configMigration.js +390 -0
  39. package/dist/config/configValidation.d.ts +66 -0
  40. package/dist/config/configValidation.js +358 -0
  41. package/dist/config/yamlConfig.d.ts +455 -1
  42. package/dist/config/yamlConfig.js +145 -52
  43. package/dist/databases/methods.js +3 -2
  44. package/dist/databases/setup.d.ts +1 -2
  45. package/dist/databases/setup.js +9 -87
  46. package/dist/examples/yamlTerminologyExample.d.ts +42 -0
  47. package/dist/examples/yamlTerminologyExample.js +269 -0
  48. package/dist/functions/deployments.js +11 -10
  49. package/dist/functions/methods.d.ts +1 -1
  50. package/dist/functions/methods.js +5 -4
  51. package/dist/init.js +9 -9
  52. package/dist/interactiveCLI.d.ts +8 -17
  53. package/dist/interactiveCLI.js +209 -1172
  54. package/dist/main.js +364 -21
  55. package/dist/migrations/afterImportActions.js +22 -30
  56. package/dist/migrations/appwriteToX.js +71 -25
  57. package/dist/migrations/dataLoader.js +35 -26
  58. package/dist/migrations/importController.js +29 -30
  59. package/dist/migrations/relationships.js +13 -12
  60. package/dist/migrations/services/ImportOrchestrator.js +16 -19
  61. package/dist/migrations/transfer.js +46 -46
  62. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
  63. package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
  64. package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
  65. package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
  66. package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
  67. package/dist/migrations/yaml/generateImportSchemas.js +736 -7
  68. package/dist/schemas/authUser.d.ts +1 -1
  69. package/dist/setupController.js +3 -2
  70. package/dist/shared/backupMetadataSchema.d.ts +94 -0
  71. package/dist/shared/backupMetadataSchema.js +38 -0
  72. package/dist/shared/backupTracking.d.ts +18 -0
  73. package/dist/shared/backupTracking.js +176 -0
  74. package/dist/shared/confirmationDialogs.js +15 -15
  75. package/dist/shared/errorUtils.d.ts +54 -0
  76. package/dist/shared/errorUtils.js +95 -0
  77. package/dist/shared/functionManager.js +20 -19
  78. package/dist/shared/indexManager.js +12 -11
  79. package/dist/shared/jsonSchemaGenerator.js +10 -26
  80. package/dist/shared/logging.d.ts +51 -0
  81. package/dist/shared/logging.js +70 -0
  82. package/dist/shared/messageFormatter.d.ts +2 -0
  83. package/dist/shared/messageFormatter.js +10 -0
  84. package/dist/shared/migrationHelpers.d.ts +6 -16
  85. package/dist/shared/migrationHelpers.js +24 -21
  86. package/dist/shared/operationLogger.d.ts +8 -1
  87. package/dist/shared/operationLogger.js +11 -24
  88. package/dist/shared/operationQueue.d.ts +28 -1
  89. package/dist/shared/operationQueue.js +268 -66
  90. package/dist/shared/operationsTable.d.ts +26 -0
  91. package/dist/shared/operationsTable.js +286 -0
  92. package/dist/shared/operationsTableSchema.d.ts +48 -0
  93. package/dist/shared/operationsTableSchema.js +35 -0
  94. package/dist/shared/relationshipExtractor.d.ts +56 -0
  95. package/dist/shared/relationshipExtractor.js +138 -0
  96. package/dist/shared/schemaGenerator.d.ts +19 -1
  97. package/dist/shared/schemaGenerator.js +56 -75
  98. package/dist/storage/backupCompression.d.ts +20 -0
  99. package/dist/storage/backupCompression.js +67 -0
  100. package/dist/storage/methods.d.ts +16 -2
  101. package/dist/storage/methods.js +98 -14
  102. package/dist/users/methods.js +9 -8
  103. package/dist/utils/configDiscovery.d.ts +78 -0
  104. package/dist/utils/configDiscovery.js +430 -0
  105. package/dist/utils/directoryUtils.d.ts +22 -0
  106. package/dist/utils/directoryUtils.js +59 -0
  107. package/dist/utils/getClientFromConfig.d.ts +17 -8
  108. package/dist/utils/getClientFromConfig.js +162 -17
  109. package/dist/utils/helperFunctions.d.ts +16 -2
  110. package/dist/utils/helperFunctions.js +19 -5
  111. package/dist/utils/loadConfigs.d.ts +34 -9
  112. package/dist/utils/loadConfigs.js +236 -316
  113. package/dist/utils/pathResolvers.d.ts +53 -0
  114. package/dist/utils/pathResolvers.js +72 -0
  115. package/dist/utils/projectConfig.d.ts +119 -0
  116. package/dist/utils/projectConfig.js +171 -0
  117. package/dist/utils/retryFailedPromises.js +4 -2
  118. package/dist/utils/sessionAuth.d.ts +48 -0
  119. package/dist/utils/sessionAuth.js +164 -0
  120. package/dist/utils/sessionPreservationExample.d.ts +1666 -0
  121. package/dist/utils/sessionPreservationExample.js +101 -0
  122. package/dist/utils/setupFiles.js +301 -41
  123. package/dist/utils/typeGuards.d.ts +35 -0
  124. package/dist/utils/typeGuards.js +57 -0
  125. package/dist/utils/versionDetection.js +145 -9
  126. package/dist/utils/yamlConverter.d.ts +53 -3
  127. package/dist/utils/yamlConverter.js +232 -13
  128. package/dist/utils/yamlLoader.d.ts +70 -0
  129. package/dist/utils/yamlLoader.js +263 -0
  130. package/dist/utilsController.d.ts +36 -3
  131. package/dist/utilsController.js +186 -56
  132. package/package.json +12 -2
  133. package/src/adapters/AdapterFactory.ts +263 -35
  134. package/src/adapters/TablesDBAdapter.ts +225 -36
  135. package/src/backups/operations/bucketBackup.ts +277 -0
  136. package/src/backups/operations/collectionBackup.ts +310 -0
  137. package/src/backups/operations/comprehensiveBackup.ts +342 -0
  138. package/src/backups/schemas/bucketManifest.ts +78 -0
  139. package/src/backups/schemas/comprehensiveManifest.ts +76 -0
  140. package/src/backups/tracking/centralizedTracking.ts +352 -0
  141. package/src/cli/commands/configCommands.ts +194 -0
  142. package/src/cli/commands/databaseCommands.ts +635 -0
  143. package/src/cli/commands/functionCommands.ts +379 -0
  144. package/src/cli/commands/schemaCommands.ts +163 -0
  145. package/src/cli/commands/transferCommands.ts +457 -0
  146. package/src/collections/attributes.ts +900 -621
  147. package/src/collections/attributes.ts.backup +1555 -0
  148. package/src/collections/indexes.ts +116 -114
  149. package/src/collections/methods.ts +295 -968
  150. package/src/collections/transferOperations.ts +516 -0
  151. package/src/collections/wipeOperations.ts +501 -0
  152. package/src/config/README.md +274 -0
  153. package/src/config/configMigration.ts +575 -0
  154. package/src/config/configValidation.ts +445 -0
  155. package/src/config/yamlConfig.ts +168 -55
  156. package/src/databases/methods.ts +3 -2
  157. package/src/databases/setup.ts +11 -138
  158. package/src/examples/yamlTerminologyExample.ts +341 -0
  159. package/src/functions/deployments.ts +14 -12
  160. package/src/functions/methods.ts +11 -11
  161. package/src/functions/templates/hono-typescript/README.md +286 -0
  162. package/src/functions/templates/hono-typescript/package.json +26 -0
  163. package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  164. package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  165. package/src/functions/templates/hono-typescript/src/app.ts +180 -0
  166. package/src/functions/templates/hono-typescript/src/context.ts +103 -0
  167. package/src/functions/templates/hono-typescript/src/index.ts +54 -0
  168. package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  169. package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
  170. package/src/functions/templates/typescript-node/package.json +2 -1
  171. package/src/functions/templates/typescript-node/src/context.ts +103 -0
  172. package/src/functions/templates/typescript-node/src/index.ts +18 -12
  173. package/src/functions/templates/uv/pyproject.toml +1 -0
  174. package/src/functions/templates/uv/src/context.py +125 -0
  175. package/src/functions/templates/uv/src/index.py +35 -5
  176. package/src/init.ts +9 -11
  177. package/src/interactiveCLI.ts +274 -1563
  178. package/src/main.ts +418 -24
  179. package/src/migrations/afterImportActions.ts +71 -44
  180. package/src/migrations/appwriteToX.ts +100 -34
  181. package/src/migrations/dataLoader.ts +48 -34
  182. package/src/migrations/importController.ts +44 -39
  183. package/src/migrations/relationships.ts +28 -18
  184. package/src/migrations/services/ImportOrchestrator.ts +24 -27
  185. package/src/migrations/transfer.ts +159 -121
  186. package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
  187. package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
  188. package/src/migrations/yaml/generateImportSchemas.ts +751 -12
  189. package/src/setupController.ts +3 -2
  190. package/src/shared/backupMetadataSchema.ts +93 -0
  191. package/src/shared/backupTracking.ts +211 -0
  192. package/src/shared/confirmationDialogs.ts +19 -19
  193. package/src/shared/errorUtils.ts +110 -0
  194. package/src/shared/functionManager.ts +21 -20
  195. package/src/shared/indexManager.ts +12 -11
  196. package/src/shared/jsonSchemaGenerator.ts +38 -52
  197. package/src/shared/logging.ts +75 -0
  198. package/src/shared/messageFormatter.ts +14 -1
  199. package/src/shared/migrationHelpers.ts +45 -38
  200. package/src/shared/operationLogger.ts +11 -36
  201. package/src/shared/operationQueue.ts +322 -93
  202. package/src/shared/operationsTable.ts +338 -0
  203. package/src/shared/operationsTableSchema.ts +60 -0
  204. package/src/shared/relationshipExtractor.ts +214 -0
  205. package/src/shared/schemaGenerator.ts +179 -219
  206. package/src/storage/backupCompression.ts +88 -0
  207. package/src/storage/methods.ts +131 -34
  208. package/src/users/methods.ts +11 -9
  209. package/src/utils/configDiscovery.ts +502 -0
  210. package/src/utils/directoryUtils.ts +61 -0
  211. package/src/utils/getClientFromConfig.ts +205 -22
  212. package/src/utils/helperFunctions.ts +23 -5
  213. package/src/utils/loadConfigs.ts +313 -345
  214. package/src/utils/pathResolvers.ts +81 -0
  215. package/src/utils/projectConfig.ts +299 -0
  216. package/src/utils/retryFailedPromises.ts +4 -2
  217. package/src/utils/sessionAuth.ts +230 -0
  218. package/src/utils/setupFiles.ts +322 -54
  219. package/src/utils/typeGuards.ts +65 -0
  220. package/src/utils/versionDetection.ts +218 -64
  221. package/src/utils/yamlConverter.ts +296 -13
  222. package/src/utils/yamlLoader.ts +364 -0
  223. package/src/utilsController.ts +314 -110
  224. package/tests/README.md +497 -0
  225. package/tests/adapters/AdapterFactory.test.ts +277 -0
  226. package/tests/integration/syncOperations.test.ts +463 -0
  227. package/tests/jest.config.js +25 -0
  228. package/tests/migration/configMigration.test.ts +546 -0
  229. package/tests/setup.ts +62 -0
  230. package/tests/testUtils.ts +340 -0
  231. package/tests/utils/loadConfigs.test.ts +350 -0
  232. package/tests/validation/configValidation.test.ts +412 -0
  233. package/src/utils/schemaStrings.ts +0 -517
@@ -22,23 +22,21 @@ import { AppwriteToX } from "./migrations/appwriteToX.js";
22
22
  import { ImportController } from "./migrations/importController.js";
23
23
  import { ImportDataActions } from "./migrations/importDataActions.js";
24
24
  import {
25
- setupMigrationDatabase,
26
25
  ensureDatabasesExist,
27
26
  wipeOtherDatabases,
28
27
  ensureCollectionsExist,
29
28
  } from "./databases/setup.js";
30
- import {
31
- createOrUpdateCollections,
32
- wipeDatabase,
33
- generateSchemas,
34
- fetchAllCollections,
35
- wipeCollection,
36
- } from "./collections/methods.js";
37
- import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
29
+ import {
30
+ createOrUpdateCollections,
31
+ wipeDatabase,
32
+ generateSchemas,
33
+ fetchAllCollections,
34
+ wipeCollection,
35
+ } from "./collections/methods.js";
36
+ import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
38
37
  import {
39
38
  backupDatabase,
40
39
  ensureDatabaseConfigBucketsExist,
41
- initOrGetBackupStorage,
42
40
  wipeDocumentStorage,
43
41
  } from "./storage/methods.js";
44
42
  import path from "path";
@@ -58,8 +56,11 @@ import {
58
56
  transferUsersLocalToRemote,
59
57
  type TransferOptions,
60
58
  } from "./migrations/transfer.js";
61
- import { getClient } from "./utils/getClientFromConfig.js";
62
- import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
59
+ import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
60
+ import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
61
+ import type { DatabaseAdapter } from './adapters/DatabaseAdapter.js';
62
+ import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie, type SessionAuthInfo } from "./utils/sessionAuth.js";
63
+ import { createSessionPreservation, type SessionPreservationOptions } from "./utils/loadConfigs.js";
63
64
  import { fetchAllDatabases } from "./databases/methods.js";
64
65
  import {
65
66
  listFunctions,
@@ -72,6 +73,12 @@ import { configureLogging, updateLogger } from "./shared/logging.js";
72
73
  import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
73
74
  import { SchemaGenerator } from "./shared/schemaGenerator.js";
74
75
  import { findYamlConfig } from "./config/yamlConfig.js";
76
+ import {
77
+ validateCollectionsTablesConfig,
78
+ reportValidationResults,
79
+ validateWithStrictMode,
80
+ type ValidationResult
81
+ } from "./config/configValidation.js";
75
82
 
76
83
  export interface SetupOptions {
77
84
  databases?: Models.Database[];
@@ -95,10 +102,19 @@ export class UtilsController {
95
102
  public appwriteServer?: Client;
96
103
  public database?: Databases;
97
104
  public storage?: Storage;
105
+ public adapter?: DatabaseAdapter;
98
106
  public converterDefinitions: ConverterFunctions = converterFunctions;
99
107
  public validityRuleDefinitions: ValidationRules = validationRules;
100
108
  public afterImportActionsDefinitions: AfterImportActions = afterImportActions;
101
109
 
110
+ // Session preservation fields
111
+ private sessionCookie?: string;
112
+ private authMethod?: "session" | "apikey" | "auto";
113
+ private sessionMetadata?: {
114
+ email?: string;
115
+ expiresAt?: string;
116
+ };
117
+
102
118
  constructor(
103
119
  currentUserDir: string,
104
120
  directConfig?: {
@@ -119,9 +135,21 @@ export class UtilsController {
119
135
  MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
120
136
  hasErrors = true;
121
137
  }
122
- if (!directConfig.appwriteKey) {
123
- MessageFormatter.error("Appwrite key is required", undefined, { prefix: "Config" });
138
+ // Check authentication: either API key or session auth is required
139
+ const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
140
+ hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
141
+
142
+ if (!directConfig.appwriteKey && !hasValidSession) {
143
+ MessageFormatter.error(
144
+ "Authentication required: provide an API key or login with 'appwrite login'",
145
+ undefined,
146
+ { prefix: "Config" }
147
+ );
124
148
  hasErrors = true;
149
+ } else if (!directConfig.appwriteKey && hasValidSession) {
150
+ MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
151
+ } else if (directConfig.appwriteKey && hasValidSession) {
152
+ MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
125
153
  }
126
154
  if (!hasErrors) {
127
155
  // Only set config if we have all required fields
@@ -129,9 +157,10 @@ export class UtilsController {
129
157
  this.config = {
130
158
  appwriteEndpoint: directConfig.appwriteEndpoint!,
131
159
  appwriteProject: directConfig.appwriteProject!,
132
- appwriteKey: directConfig.appwriteKey!,
160
+ appwriteKey: directConfig.appwriteKey || "",
133
161
  appwriteClient: null,
134
162
  apiMode: "auto", // Default to auto-detect for dual API support
163
+ authMethod: "auto", // Default to auto-detect authentication method
135
164
  enableBackups: false,
136
165
  backupInterval: 0,
137
166
  backupRetention: 0,
@@ -139,7 +168,6 @@ export class UtilsController {
139
168
  enableMockData: false,
140
169
  documentBucketId: "",
141
170
  usersCollectionName: "",
142
- useMigrations: true,
143
171
  databases: [],
144
172
  buckets: [],
145
173
  functions: [],
@@ -165,16 +193,31 @@ export class UtilsController {
165
193
  }
166
194
  }
167
195
 
168
- async init() {
196
+ async init(options: { validate?: boolean; strictMode?: boolean; useSession?: boolean; sessionCookie?: string } = {}) {
197
+ const { validate = false, strictMode = false, useSession = false, sessionCookie } = options;
198
+
169
199
  if (!this.config) {
170
200
  if (this.appwriteFolderPath && this.appwriteConfigPath) {
171
201
  MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
172
202
  try {
173
- const { config, actualConfigPath } = await loadConfigWithPath(this.appwriteFolderPath);
203
+ const { config, actualConfigPath, validation } = await loadConfigWithPath(
204
+ this.appwriteFolderPath,
205
+ { validate, strictMode, reportValidation: false }
206
+ );
174
207
  this.config = config;
175
208
  MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
209
+
210
+ // Report validation results if validation was requested
211
+ if (validation && validate) {
212
+ reportValidationResults(validation, { verbose: false });
213
+
214
+ // In strict mode, throw if validation fails
215
+ if (strictMode && !validation.isValid) {
216
+ throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
217
+ }
218
+ }
176
219
  } catch (error) {
177
- MessageFormatter.error("Failed to load config from file", undefined, { prefix: "Config" });
220
+ MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
178
221
  return;
179
222
  }
180
223
  } else {
@@ -189,15 +232,41 @@ export class UtilsController {
189
232
  updateLogger();
190
233
  }
191
234
 
192
- this.appwriteServer = new Client();
193
- this.appwriteServer
194
- .setEndpoint(this.config.appwriteEndpoint)
195
- .setProject(this.config.appwriteProject)
196
- .setKey(this.config.appwriteKey);
235
+ // Use enhanced client with session authentication support
236
+ // Pass session cookie from options if provided
237
+ const clientSessionCookie = sessionCookie || this.sessionCookie;
238
+ this.appwriteServer = getClientWithAuth(
239
+ this.config.appwriteEndpoint,
240
+ this.config.appwriteProject,
241
+ this.config.appwriteKey || undefined,
242
+ clientSessionCookie
243
+ );
197
244
 
198
245
  this.database = new Databases(this.appwriteServer);
199
246
  this.storage = new Storage(this.appwriteServer);
200
247
  this.config.appwriteClient = this.appwriteServer;
248
+
249
+ // Initialize adapter with version detection
250
+ try {
251
+ const { adapter, apiMode } = await getAdapterFromConfig(
252
+ this.config,
253
+ false,
254
+ clientSessionCookie
255
+ );
256
+ this.adapter = adapter;
257
+
258
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, {
259
+ prefix: "Adapter"
260
+ });
261
+ } catch (error) {
262
+ MessageFormatter.warning(
263
+ 'Database adapter initialization failed - some features may not work',
264
+ { prefix: "Adapter" }
265
+ );
266
+ }
267
+
268
+ // Extract and store session information after successful authentication
269
+ this.extractSessionInfo();
201
270
  }
202
271
 
203
272
  async reloadConfig() {
@@ -205,9 +274,15 @@ export class UtilsController {
205
274
  MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
206
275
  return;
207
276
  }
208
- this.config = await loadConfig(this.appwriteFolderPath);
277
+
278
+ // Preserve session authentication during config reload
279
+ const preserveAuth = this.createSessionPreservationOptions();
280
+
281
+ this.config = await loadConfig(this.appwriteFolderPath, {
282
+ preserveAuth
283
+ });
209
284
  if (!this.config) {
210
- console.log(chalk.red("Failed to load config"));
285
+ MessageFormatter.error("Failed to load config", undefined, { prefix: "Controller" });
211
286
  return;
212
287
  }
213
288
 
@@ -217,25 +292,41 @@ export class UtilsController {
217
292
  updateLogger();
218
293
  }
219
294
 
220
- this.appwriteServer = new Client();
221
- this.appwriteServer
222
- .setEndpoint(this.config.appwriteEndpoint)
223
- .setProject(this.config.appwriteProject)
224
- .setKey(this.config.appwriteKey);
295
+ // Use enhanced client with session authentication support, passing preserved session
296
+ this.appwriteServer = getClientWithAuth(
297
+ this.config.appwriteEndpoint,
298
+ this.config.appwriteProject,
299
+ this.config.appwriteKey || undefined,
300
+ this.sessionCookie
301
+ );
225
302
  this.database = new Databases(this.appwriteServer);
226
303
  this.storage = new Storage(this.appwriteServer);
227
304
  this.config.appwriteClient = this.appwriteServer;
228
- }
229
305
 
230
- async setupMigrationDatabase() {
231
- await this.init();
232
- if (!this.config) {
233
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
234
- return;
306
+ // Re-initialize adapter with version detection after config reload
307
+ try {
308
+ const { adapter, apiMode } = await getAdapterFromConfig(
309
+ this.config,
310
+ false,
311
+ this.sessionCookie
312
+ );
313
+ this.adapter = adapter;
314
+
315
+ MessageFormatter.info(`Database adapter re-initialized (apiMode: ${apiMode})`, {
316
+ prefix: "Adapter"
317
+ });
318
+ } catch (error) {
319
+ MessageFormatter.warning(
320
+ 'Database adapter re-initialization failed - some features may not work',
321
+ { prefix: "Adapter" }
322
+ );
235
323
  }
236
- await setupMigrationDatabase(this.config);
324
+
325
+ // Re-extract session information after reload
326
+ this.extractSessionInfo();
237
327
  }
238
328
 
329
+
239
330
  async ensureDatabaseConfigBucketsExist(databases: Models.Database[] = []) {
240
331
  await this.init();
241
332
  if (!this.storage) {
@@ -259,7 +350,6 @@ export class UtilsController {
259
350
  MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
260
351
  return;
261
352
  }
262
- await this.setupMigrationDatabase();
263
353
  await this.ensureDatabaseConfigBucketsExist(databases);
264
354
  await ensureDatabasesExist(this.config, databases);
265
355
  }
@@ -296,37 +386,38 @@ export class UtilsController {
296
386
  MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
297
387
  return;
298
388
  }
299
- await wipeOtherDatabases(this.database, databasesToKeep, this.config?.useMigrations ?? true);
389
+ await wipeOtherDatabases(this.database, databasesToKeep);
300
390
  }
301
391
 
302
392
  async wipeUsers() {
303
393
  await this.init();
304
394
  if (!this.config || !this.database) {
305
- console.log(chalk.red("Config or database not initialized"));
395
+ MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
306
396
  return;
307
397
  }
308
398
  const usersController = new UsersController(this.config, this.database);
309
399
  await usersController.wipeUsers();
310
400
  }
311
401
 
312
- async backupDatabase(database: Models.Database) {
402
+ async backupDatabase(database: Models.Database, format: 'json' | 'zip' = 'json') {
313
403
  await this.init();
314
404
  if (!this.database || !this.storage || !this.config) {
315
- console.log(chalk.red("Database, storage, or config not initialized"));
405
+ MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
316
406
  return;
317
407
  }
318
408
  await backupDatabase(
319
409
  this.config,
320
410
  this.database,
321
411
  database.$id,
322
- this.storage
412
+ this.storage,
413
+ format
323
414
  );
324
415
  }
325
416
 
326
417
  async listAllFunctions() {
327
418
  await this.init();
328
419
  if (!this.appwriteServer) {
329
- console.log(chalk.red("Appwrite server not initialized"));
420
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
330
421
  return [];
331
422
  }
332
423
  const { functions } = await listFunctions(this.appwriteServer, [
@@ -342,7 +433,7 @@ export class UtilsController {
342
433
  }
343
434
  const functionsDir = findFunctionsDir(this.appwriteFolderPath);
344
435
  if (!functionsDir) {
345
- console.log(chalk.red("Failed to find functions directory"));
436
+ MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
346
437
  return new Map();
347
438
  }
348
439
 
@@ -373,7 +464,7 @@ export class UtilsController {
373
464
  ) {
374
465
  await this.init();
375
466
  if (!this.appwriteServer) {
376
- console.log(chalk.red("Appwrite server not initialized"));
467
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
377
468
  return;
378
469
  }
379
470
 
@@ -383,7 +474,7 @@ export class UtilsController {
383
474
  );
384
475
  }
385
476
  if (!functionConfig) {
386
- console.log(chalk.red(`Function ${functionName} not found in config`));
477
+ MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
387
478
  return;
388
479
  }
389
480
 
@@ -398,7 +489,7 @@ export class UtilsController {
398
489
  async syncFunctions() {
399
490
  await this.init();
400
491
  if (!this.appwriteServer) {
401
- console.log(chalk.red("Appwrite server not initialized"));
492
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
402
493
  return;
403
494
  }
404
495
 
@@ -415,23 +506,23 @@ export class UtilsController {
415
506
  MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
416
507
  }
417
508
 
418
- async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
419
- await this.init();
420
- if (!this.database || !this.config) throw new Error("Database not initialized");
421
- try {
422
- const { adapter, apiMode } = await getAdapterFromConfig(this.config);
423
- if (apiMode === 'tablesdb') {
424
- await wipeAllTables(adapter, database.$id);
425
- } else {
426
- await wipeDatabase(this.database, database.$id);
427
- }
428
- } catch {
429
- await wipeDatabase(this.database, database.$id);
430
- }
431
- if (wipeBucket) {
432
- await this.wipeBucketFromDatabase(database);
433
- }
434
- }
509
+ async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
510
+ await this.init();
511
+ if (!this.database || !this.config) throw new Error("Database not initialized");
512
+ try {
513
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
514
+ if (apiMode === 'tablesdb') {
515
+ await wipeAllTables(adapter, database.$id);
516
+ } else {
517
+ await wipeDatabase(this.database, database.$id);
518
+ }
519
+ } catch {
520
+ await wipeDatabase(this.database, database.$id);
521
+ }
522
+ if (wipeBucket) {
523
+ await this.wipeBucketFromDatabase(database);
524
+ }
525
+ }
435
526
 
436
527
  async wipeBucketFromDatabase(database: Models.Database) {
437
528
  // Check configured bucket in database config
@@ -459,23 +550,23 @@ export class UtilsController {
459
550
  }
460
551
  }
461
552
 
462
- async wipeCollection(
463
- database: Models.Database,
464
- collection: Models.Collection
465
- ) {
466
- await this.init();
467
- if (!this.database || !this.config) throw new Error("Database not initialized");
468
- try {
469
- const { adapter, apiMode } = await getAdapterFromConfig(this.config);
470
- if (apiMode === 'tablesdb') {
471
- await wipeTableRows(adapter, database.$id, collection.$id);
472
- } else {
473
- await wipeCollection(this.database, database.$id, collection.$id);
474
- }
475
- } catch {
476
- await wipeCollection(this.database, database.$id, collection.$id);
477
- }
478
- }
553
+ async wipeCollection(
554
+ database: Models.Database,
555
+ collection: Models.Collection
556
+ ) {
557
+ await this.init();
558
+ if (!this.database || !this.config) throw new Error("Database not initialized");
559
+ try {
560
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
561
+ if (apiMode === 'tablesdb') {
562
+ await wipeTableRows(adapter, database.$id, collection.$id);
563
+ } else {
564
+ await wipeCollection(this.database, database.$id, collection.$id);
565
+ }
566
+ } catch {
567
+ await wipeCollection(this.database, database.$id, collection.$id);
568
+ }
569
+ }
479
570
 
480
571
  async wipeDocumentStorage(bucketId: string) {
481
572
  await this.init();
@@ -491,7 +582,6 @@ export class UtilsController {
491
582
  if (!this.database || !this.config)
492
583
  throw new Error("Database or config not initialized");
493
584
  for (const database of databases) {
494
- if (!this.config.useMigrations && database.$id === "migrations") continue;
495
585
  await this.createOrUpdateCollections(database, undefined, collections);
496
586
  }
497
587
  }
@@ -514,10 +604,25 @@ export class UtilsController {
514
604
  }
515
605
 
516
606
  async generateSchemas() {
517
- await this.init();
607
+ // Schema generation doesn't need Appwrite connection, just config
518
608
  if (!this.config) {
519
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
520
- return;
609
+ if (this.appwriteFolderPath && this.appwriteConfigPath) {
610
+ MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
611
+ try {
612
+ const { config, actualConfigPath } = await loadConfigWithPath(
613
+ this.appwriteFolderPath,
614
+ { validate: false, strictMode: false, reportValidation: false }
615
+ );
616
+ this.config = config;
617
+ MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
618
+ } catch (error) {
619
+ MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
620
+ return;
621
+ }
622
+ } else {
623
+ MessageFormatter.error("No configuration available", undefined, { prefix: "Controller" });
624
+ return;
625
+ }
521
626
  }
522
627
  if (!this.appwriteFolderPath) {
523
628
  MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
@@ -601,10 +706,10 @@ export class UtilsController {
601
706
  await generator.updateConfig(this.config, isYamlProject);
602
707
  }
603
708
 
604
- async syncDb(
605
- databases: Models.Database[] = [],
606
- collections: Models.Collection[] = []
607
- ) {
709
+ async syncDb(
710
+ databases: Models.Database[] = [],
711
+ collections: Models.Collection[] = []
712
+ ) {
608
713
  await this.init();
609
714
  if (!this.database) {
610
715
  MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
@@ -614,17 +719,12 @@ export class UtilsController {
614
719
  const allDatabases = await fetchAllDatabases(this.database);
615
720
  databases = allDatabases;
616
721
  }
617
- // Ensure DBs exist (this may internally ensure migrations exists)
618
- await this.ensureDatabasesExist(databases);
619
- await this.ensureDatabaseConfigBucketsExist(databases);
620
-
621
- // Do not push collections to the migrations database (prevents duplicate runs)
622
- const dbsForCollections = databases.filter(
623
- (db) => (this.config?.useMigrations ?? true) ? db.name.toLowerCase() !== "migrations" : true
624
- );
625
-
626
- await this.createOrUpdateCollectionsForDatabases(dbsForCollections, collections);
627
- }
722
+ // Ensure DBs exist
723
+ await this.ensureDatabasesExist(databases);
724
+ await this.ensureDatabaseConfigBucketsExist(databases);
725
+
726
+ await this.createOrUpdateCollectionsForDatabases(databases, collections);
727
+ }
628
728
 
629
729
  getAppwriteFolderPath() {
630
730
  return this.appwriteFolderPath;
@@ -637,7 +737,7 @@ export class UtilsController {
637
737
  let targetDatabases: Models.Database[] = [];
638
738
 
639
739
  if (!sourceClient) {
640
- console.log(chalk.red("Source database not initialized"));
740
+ MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
641
741
  return;
642
742
  }
643
743
 
@@ -647,7 +747,7 @@ export class UtilsController {
647
747
  !options.transferProject ||
648
748
  !options.transferKey
649
749
  ) {
650
- console.log(chalk.red("Remote transfer options are missing"));
750
+ MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
651
751
  return;
652
752
  }
653
753
 
@@ -675,7 +775,7 @@ export class UtilsController {
675
775
  );
676
776
 
677
777
  if (!fromDb || !targetDb) {
678
- console.log(chalk.red("Source or target database not found"));
778
+ MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
679
779
  return;
680
780
  }
681
781
 
@@ -699,13 +799,12 @@ export class UtilsController {
699
799
 
700
800
  if (options.transferUsers) {
701
801
  if (!options.isRemote) {
702
- console.log(
703
- chalk.yellow(
704
- "User transfer is only supported for remote transfers. Skipping..."
705
- )
802
+ MessageFormatter.warning(
803
+ "User transfer is only supported for remote transfers. Skipping...",
804
+ { prefix: "Controller" }
706
805
  );
707
806
  } else if (!this.appwriteServer) {
708
- console.log(chalk.red("Appwrite server not initialized"));
807
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
709
808
  return;
710
809
  } else {
711
810
  MessageFormatter.progress("Starting user transfer...", { prefix: "Transfer" });
@@ -789,4 +888,109 @@ export class UtilsController {
789
888
  { prefix: "Functions" }
790
889
  );
791
890
  }
891
+
892
+ /**
893
+ * Validates the current configuration for collections/tables conflicts
894
+ */
895
+ async validateConfiguration(strictMode: boolean = false): Promise<ValidationResult> {
896
+ await this.init();
897
+ if (!this.config) {
898
+ throw new Error("Configuration not loaded");
899
+ }
900
+
901
+ MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
902
+
903
+ const validation = strictMode
904
+ ? validateWithStrictMode(this.config, strictMode)
905
+ : validateCollectionsTablesConfig(this.config);
906
+
907
+ reportValidationResults(validation, { verbose: true });
908
+
909
+ if (validation.isValid) {
910
+ MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
911
+ } else {
912
+ MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
913
+ }
914
+
915
+ return validation;
916
+ }
917
+
918
+ /**
919
+ * Extract session information from the current authenticated client
920
+ * This preserves session context for use across config reloads and adapter operations
921
+ */
922
+ private extractSessionInfo(): void {
923
+ if (!this.config) {
924
+ return;
925
+ }
926
+
927
+ // Try to extract session from current config first
928
+ if (this.config.sessionCookie && isValidSessionCookie(this.config.sessionCookie)) {
929
+ this.sessionCookie = this.config.sessionCookie;
930
+ this.authMethod = "session";
931
+ this.sessionMetadata = this.config.sessionMetadata;
932
+ MessageFormatter.debug("Extracted session from config", { prefix: "Session" });
933
+ return;
934
+ }
935
+
936
+ // Fall back to finding session from Appwrite CLI preferences
937
+ const sessionAuth = findSessionByEndpointAndProject(
938
+ this.config.appwriteEndpoint,
939
+ this.config.appwriteProject
940
+ );
941
+
942
+ if (sessionAuth && isValidSessionCookie(sessionAuth.sessionCookie)) {
943
+ this.sessionCookie = sessionAuth.sessionCookie;
944
+ this.authMethod = "session";
945
+ this.sessionMetadata = {
946
+ email: sessionAuth.email
947
+ };
948
+ MessageFormatter.debug(
949
+ `Extracted session from CLI preferences for ${sessionAuth.email || 'unknown user'}`,
950
+ { prefix: "Session" }
951
+ );
952
+ return;
953
+ }
954
+
955
+ // No session found, using API key authentication
956
+ if (this.config.appwriteKey) {
957
+ this.authMethod = "apikey";
958
+ this.sessionCookie = undefined;
959
+ this.sessionMetadata = undefined;
960
+ MessageFormatter.debug("Using API key authentication", { prefix: "Session" });
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Create session preservation options for passing to loadConfig
966
+ * Returns current session state to maintain authentication across config reloads
967
+ */
968
+ private createSessionPreservationOptions(): SessionPreservationOptions | undefined {
969
+ if (!this.sessionCookie || !isValidSessionCookie(this.sessionCookie)) {
970
+ return undefined;
971
+ }
972
+
973
+ return createSessionPreservation(
974
+ this.sessionCookie,
975
+ this.sessionMetadata?.email,
976
+ this.sessionMetadata?.expiresAt
977
+ );
978
+ }
979
+
980
+ /**
981
+ * Get current session information for debugging/logging purposes
982
+ */
983
+ public getSessionInfo(): {
984
+ hasSession: boolean;
985
+ authMethod?: string;
986
+ email?: string;
987
+ expiresAt?: string;
988
+ } {
989
+ return {
990
+ hasSession: !!this.sessionCookie && isValidSessionCookie(this.sessionCookie),
991
+ authMethod: this.authMethod,
992
+ email: this.sessionMetadata?.email,
993
+ expiresAt: this.sessionMetadata?.expiresAt
994
+ };
995
+ }
792
996
  }