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
@@ -5,16 +5,18 @@ import { UsersController } from "./users/methods.js";
5
5
  import { AppwriteToX } from "./migrations/appwriteToX.js";
6
6
  import { ImportController } from "./migrations/importController.js";
7
7
  import { ImportDataActions } from "./migrations/importDataActions.js";
8
- import { setupMigrationDatabase, ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
8
+ import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
9
9
  import { createOrUpdateCollections, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
10
10
  import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
11
- import { backupDatabase, ensureDatabaseConfigBucketsExist, initOrGetBackupStorage, wipeDocumentStorage, } from "./storage/methods.js";
11
+ import { backupDatabase, ensureDatabaseConfigBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
12
12
  import path from "path";
13
13
  import { converterFunctions, validationRules, } from "appwrite-utils";
14
14
  import { afterImportActions } from "./migrations/afterImportActions.js";
15
15
  import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, transferUsersLocalToRemote, } from "./migrations/transfer.js";
16
- import { getClient } from "./utils/getClientFromConfig.js";
16
+ import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
17
17
  import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
18
+ import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie } from "./utils/sessionAuth.js";
19
+ import { createSessionPreservation } from "./utils/loadConfigs.js";
18
20
  import { fetchAllDatabases } from "./databases/methods.js";
19
21
  import { listFunctions, updateFunctionSpecifications, } from "./functions/methods.js";
20
22
  import chalk from "chalk";
@@ -24,6 +26,7 @@ import { configureLogging, updateLogger } from "./shared/logging.js";
24
26
  import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
25
27
  import { SchemaGenerator } from "./shared/schemaGenerator.js";
26
28
  import { findYamlConfig } from "./config/yamlConfig.js";
29
+ import { validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode } from "./config/configValidation.js";
27
30
  export class UtilsController {
28
31
  appwriteFolderPath;
29
32
  appwriteConfigPath;
@@ -31,9 +34,14 @@ export class UtilsController {
31
34
  appwriteServer;
32
35
  database;
33
36
  storage;
37
+ adapter;
34
38
  converterDefinitions = converterFunctions;
35
39
  validityRuleDefinitions = validationRules;
36
40
  afterImportActionsDefinitions = afterImportActions;
41
+ // Session preservation fields
42
+ sessionCookie;
43
+ authMethod;
44
+ sessionMetadata;
37
45
  constructor(currentUserDir, directConfig) {
38
46
  const basePath = currentUserDir;
39
47
  if (directConfig) {
@@ -46,19 +54,29 @@ export class UtilsController {
46
54
  MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
47
55
  hasErrors = true;
48
56
  }
49
- if (!directConfig.appwriteKey) {
50
- MessageFormatter.error("Appwrite key is required", undefined, { prefix: "Config" });
57
+ // Check authentication: either API key or session auth is required
58
+ const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
59
+ hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
60
+ if (!directConfig.appwriteKey && !hasValidSession) {
61
+ MessageFormatter.error("Authentication required: provide an API key or login with 'appwrite login'", undefined, { prefix: "Config" });
51
62
  hasErrors = true;
52
63
  }
64
+ else if (!directConfig.appwriteKey && hasValidSession) {
65
+ MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
66
+ }
67
+ else if (directConfig.appwriteKey && hasValidSession) {
68
+ MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
69
+ }
53
70
  if (!hasErrors) {
54
71
  // Only set config if we have all required fields
55
72
  this.appwriteFolderPath = basePath;
56
73
  this.config = {
57
74
  appwriteEndpoint: directConfig.appwriteEndpoint,
58
75
  appwriteProject: directConfig.appwriteProject,
59
- appwriteKey: directConfig.appwriteKey,
76
+ appwriteKey: directConfig.appwriteKey || "",
60
77
  appwriteClient: null,
61
78
  apiMode: "auto", // Default to auto-detect for dual API support
79
+ authMethod: "auto", // Default to auto-detect authentication method
62
80
  enableBackups: false,
63
81
  backupInterval: 0,
64
82
  backupRetention: 0,
@@ -66,7 +84,6 @@ export class UtilsController {
66
84
  enableMockData: false,
67
85
  documentBucketId: "",
68
86
  usersCollectionName: "",
69
- useMigrations: true,
70
87
  databases: [],
71
88
  buckets: [],
72
89
  functions: [],
@@ -89,17 +106,26 @@ export class UtilsController {
89
106
  this.appwriteFolderPath = appwriteConfigFound; // For YAML configs, findAppwriteConfig already returns the correct directory
90
107
  }
91
108
  }
92
- async init() {
109
+ async init(options = {}) {
110
+ const { validate = false, strictMode = false, useSession = false, sessionCookie } = options;
93
111
  if (!this.config) {
94
112
  if (this.appwriteFolderPath && this.appwriteConfigPath) {
95
113
  MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
96
114
  try {
97
- const { config, actualConfigPath } = await loadConfigWithPath(this.appwriteFolderPath);
115
+ const { config, actualConfigPath, validation } = await loadConfigWithPath(this.appwriteFolderPath, { validate, strictMode, reportValidation: false });
98
116
  this.config = config;
99
117
  MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
118
+ // Report validation results if validation was requested
119
+ if (validation && validate) {
120
+ reportValidationResults(validation, { verbose: false });
121
+ // In strict mode, throw if validation fails
122
+ if (strictMode && !validation.isValid) {
123
+ throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
124
+ }
125
+ }
100
126
  }
101
127
  catch (error) {
102
- MessageFormatter.error("Failed to load config from file", undefined, { prefix: "Config" });
128
+ MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
103
129
  return;
104
130
  }
105
131
  }
@@ -113,23 +139,39 @@ export class UtilsController {
113
139
  configureLogging(this.config.logging);
114
140
  updateLogger();
115
141
  }
116
- this.appwriteServer = new Client();
117
- this.appwriteServer
118
- .setEndpoint(this.config.appwriteEndpoint)
119
- .setProject(this.config.appwriteProject)
120
- .setKey(this.config.appwriteKey);
142
+ // Use enhanced client with session authentication support
143
+ // Pass session cookie from options if provided
144
+ const clientSessionCookie = sessionCookie || this.sessionCookie;
145
+ this.appwriteServer = getClientWithAuth(this.config.appwriteEndpoint, this.config.appwriteProject, this.config.appwriteKey || undefined, clientSessionCookie);
121
146
  this.database = new Databases(this.appwriteServer);
122
147
  this.storage = new Storage(this.appwriteServer);
123
148
  this.config.appwriteClient = this.appwriteServer;
149
+ // Initialize adapter with version detection
150
+ try {
151
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, clientSessionCookie);
152
+ this.adapter = adapter;
153
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, {
154
+ prefix: "Adapter"
155
+ });
156
+ }
157
+ catch (error) {
158
+ MessageFormatter.warning('Database adapter initialization failed - some features may not work', { prefix: "Adapter" });
159
+ }
160
+ // Extract and store session information after successful authentication
161
+ this.extractSessionInfo();
124
162
  }
125
163
  async reloadConfig() {
126
164
  if (!this.appwriteFolderPath) {
127
165
  MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
128
166
  return;
129
167
  }
130
- this.config = await loadConfig(this.appwriteFolderPath);
168
+ // Preserve session authentication during config reload
169
+ const preserveAuth = this.createSessionPreservationOptions();
170
+ this.config = await loadConfig(this.appwriteFolderPath, {
171
+ preserveAuth
172
+ });
131
173
  if (!this.config) {
132
- console.log(chalk.red("Failed to load config"));
174
+ MessageFormatter.error("Failed to load config", undefined, { prefix: "Controller" });
133
175
  return;
134
176
  }
135
177
  // Configure logging based on updated config
@@ -137,22 +179,24 @@ export class UtilsController {
137
179
  configureLogging(this.config.logging);
138
180
  updateLogger();
139
181
  }
140
- this.appwriteServer = new Client();
141
- this.appwriteServer
142
- .setEndpoint(this.config.appwriteEndpoint)
143
- .setProject(this.config.appwriteProject)
144
- .setKey(this.config.appwriteKey);
182
+ // Use enhanced client with session authentication support, passing preserved session
183
+ this.appwriteServer = getClientWithAuth(this.config.appwriteEndpoint, this.config.appwriteProject, this.config.appwriteKey || undefined, this.sessionCookie);
145
184
  this.database = new Databases(this.appwriteServer);
146
185
  this.storage = new Storage(this.appwriteServer);
147
186
  this.config.appwriteClient = this.appwriteServer;
148
- }
149
- async setupMigrationDatabase() {
150
- await this.init();
151
- if (!this.config) {
152
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
153
- return;
187
+ // Re-initialize adapter with version detection after config reload
188
+ try {
189
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
190
+ this.adapter = adapter;
191
+ MessageFormatter.info(`Database adapter re-initialized (apiMode: ${apiMode})`, {
192
+ prefix: "Adapter"
193
+ });
154
194
  }
155
- await setupMigrationDatabase(this.config);
195
+ catch (error) {
196
+ MessageFormatter.warning('Database adapter re-initialization failed - some features may not work', { prefix: "Adapter" });
197
+ }
198
+ // Re-extract session information after reload
199
+ this.extractSessionInfo();
156
200
  }
157
201
  async ensureDatabaseConfigBucketsExist(databases = []) {
158
202
  await this.init();
@@ -172,7 +216,6 @@ export class UtilsController {
172
216
  MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
173
217
  return;
174
218
  }
175
- await this.setupMigrationDatabase();
176
219
  await this.ensureDatabaseConfigBucketsExist(databases);
177
220
  await ensureDatabasesExist(this.config, databases);
178
221
  }
@@ -204,29 +247,29 @@ export class UtilsController {
204
247
  MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
205
248
  return;
206
249
  }
207
- await wipeOtherDatabases(this.database, databasesToKeep, this.config?.useMigrations ?? true);
250
+ await wipeOtherDatabases(this.database, databasesToKeep);
208
251
  }
209
252
  async wipeUsers() {
210
253
  await this.init();
211
254
  if (!this.config || !this.database) {
212
- console.log(chalk.red("Config or database not initialized"));
255
+ MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
213
256
  return;
214
257
  }
215
258
  const usersController = new UsersController(this.config, this.database);
216
259
  await usersController.wipeUsers();
217
260
  }
218
- async backupDatabase(database) {
261
+ async backupDatabase(database, format = 'json') {
219
262
  await this.init();
220
263
  if (!this.database || !this.storage || !this.config) {
221
- console.log(chalk.red("Database, storage, or config not initialized"));
264
+ MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
222
265
  return;
223
266
  }
224
- await backupDatabase(this.config, this.database, database.$id, this.storage);
267
+ await backupDatabase(this.config, this.database, database.$id, this.storage, format);
225
268
  }
226
269
  async listAllFunctions() {
227
270
  await this.init();
228
271
  if (!this.appwriteServer) {
229
- console.log(chalk.red("Appwrite server not initialized"));
272
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
230
273
  return [];
231
274
  }
232
275
  const { functions } = await listFunctions(this.appwriteServer, [
@@ -241,7 +284,7 @@ export class UtilsController {
241
284
  }
242
285
  const functionsDir = findFunctionsDir(this.appwriteFolderPath);
243
286
  if (!functionsDir) {
244
- console.log(chalk.red("Failed to find functions directory"));
287
+ MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
245
288
  return new Map();
246
289
  }
247
290
  const functionDirMap = new Map();
@@ -263,14 +306,14 @@ export class UtilsController {
263
306
  async deployFunction(functionName, functionPath, functionConfig) {
264
307
  await this.init();
265
308
  if (!this.appwriteServer) {
266
- console.log(chalk.red("Appwrite server not initialized"));
309
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
267
310
  return;
268
311
  }
269
312
  if (!functionConfig) {
270
313
  functionConfig = this.config?.functions?.find((f) => f.name === functionName);
271
314
  }
272
315
  if (!functionConfig) {
273
- console.log(chalk.red(`Function ${functionName} not found in config`));
316
+ MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
274
317
  return;
275
318
  }
276
319
  await deployLocalFunction(this.appwriteServer, functionName, functionConfig, functionPath);
@@ -278,7 +321,7 @@ export class UtilsController {
278
321
  async syncFunctions() {
279
322
  await this.init();
280
323
  if (!this.appwriteServer) {
281
- console.log(chalk.red("Appwrite server not initialized"));
324
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
282
325
  return;
283
326
  }
284
327
  const localFunctions = this.config?.functions || [];
@@ -296,7 +339,7 @@ export class UtilsController {
296
339
  if (!this.database || !this.config)
297
340
  throw new Error("Database not initialized");
298
341
  try {
299
- const { adapter, apiMode } = await getAdapterFromConfig(this.config);
342
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
300
343
  if (apiMode === 'tablesdb') {
301
344
  await wipeAllTables(adapter, database.$id);
302
345
  }
@@ -339,7 +382,7 @@ export class UtilsController {
339
382
  if (!this.database || !this.config)
340
383
  throw new Error("Database not initialized");
341
384
  try {
342
- const { adapter, apiMode } = await getAdapterFromConfig(this.config);
385
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
343
386
  if (apiMode === 'tablesdb') {
344
387
  await wipeTableRows(adapter, database.$id, collection.$id);
345
388
  }
@@ -362,8 +405,6 @@ export class UtilsController {
362
405
  if (!this.database || !this.config)
363
406
  throw new Error("Database or config not initialized");
364
407
  for (const database of databases) {
365
- if (!this.config.useMigrations && database.$id === "migrations")
366
- continue;
367
408
  await this.createOrUpdateCollections(database, undefined, collections);
368
409
  }
369
410
  }
@@ -374,10 +415,24 @@ export class UtilsController {
374
415
  await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
375
416
  }
376
417
  async generateSchemas() {
377
- await this.init();
418
+ // Schema generation doesn't need Appwrite connection, just config
378
419
  if (!this.config) {
379
- MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
380
- return;
420
+ if (this.appwriteFolderPath && this.appwriteConfigPath) {
421
+ MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
422
+ try {
423
+ const { config, actualConfigPath } = await loadConfigWithPath(this.appwriteFolderPath, { validate: false, strictMode: false, reportValidation: false });
424
+ this.config = config;
425
+ MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
426
+ }
427
+ catch (error) {
428
+ MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
429
+ return;
430
+ }
431
+ }
432
+ else {
433
+ MessageFormatter.error("No configuration available", undefined, { prefix: "Controller" });
434
+ return;
435
+ }
381
436
  }
382
437
  if (!this.appwriteFolderPath) {
383
438
  MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
@@ -442,12 +497,10 @@ export class UtilsController {
442
497
  const allDatabases = await fetchAllDatabases(this.database);
443
498
  databases = allDatabases;
444
499
  }
445
- // Ensure DBs exist (this may internally ensure migrations exists)
500
+ // Ensure DBs exist
446
501
  await this.ensureDatabasesExist(databases);
447
502
  await this.ensureDatabaseConfigBucketsExist(databases);
448
- // Do not push collections to the migrations database (prevents duplicate runs)
449
- const dbsForCollections = databases.filter((db) => (this.config?.useMigrations ?? true) ? db.name.toLowerCase() !== "migrations" : true);
450
- await this.createOrUpdateCollectionsForDatabases(dbsForCollections, collections);
503
+ await this.createOrUpdateCollectionsForDatabases(databases, collections);
451
504
  }
452
505
  getAppwriteFolderPath() {
453
506
  return this.appwriteFolderPath;
@@ -458,14 +511,14 @@ export class UtilsController {
458
511
  let sourceDatabases = [];
459
512
  let targetDatabases = [];
460
513
  if (!sourceClient) {
461
- console.log(chalk.red("Source database not initialized"));
514
+ MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
462
515
  return;
463
516
  }
464
517
  if (options.isRemote) {
465
518
  if (!options.transferEndpoint ||
466
519
  !options.transferProject ||
467
520
  !options.transferKey) {
468
- console.log(chalk.red("Remote transfer options are missing"));
521
+ MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
469
522
  return;
470
523
  }
471
524
  const remoteClient = getClient(options.transferEndpoint, options.transferProject, options.transferKey);
@@ -482,7 +535,7 @@ export class UtilsController {
482
535
  const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb.$id);
483
536
  const targetDb = targetDatabases.find((db) => db.$id === options.targetDb.$id);
484
537
  if (!fromDb || !targetDb) {
485
- console.log(chalk.red("Source or target database not found"));
538
+ MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
486
539
  return;
487
540
  }
488
541
  if (options.isRemote && targetClient) {
@@ -494,10 +547,10 @@ export class UtilsController {
494
547
  }
495
548
  if (options.transferUsers) {
496
549
  if (!options.isRemote) {
497
- console.log(chalk.yellow("User transfer is only supported for remote transfers. Skipping..."));
550
+ MessageFormatter.warning("User transfer is only supported for remote transfers. Skipping...", { prefix: "Controller" });
498
551
  }
499
552
  else if (!this.appwriteServer) {
500
- console.log(chalk.red("Appwrite server not initialized"));
553
+ MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
501
554
  return;
502
555
  }
503
556
  else {
@@ -543,4 +596,81 @@ export class UtilsController {
543
596
  await updateFunctionSpecifications(this.appwriteServer, functionId, specification);
544
597
  MessageFormatter.success(`Successfully updated function specifications for ${functionId} to ${specification}`, { prefix: "Functions" });
545
598
  }
599
+ /**
600
+ * Validates the current configuration for collections/tables conflicts
601
+ */
602
+ async validateConfiguration(strictMode = false) {
603
+ await this.init();
604
+ if (!this.config) {
605
+ throw new Error("Configuration not loaded");
606
+ }
607
+ MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
608
+ const validation = strictMode
609
+ ? validateWithStrictMode(this.config, strictMode)
610
+ : validateCollectionsTablesConfig(this.config);
611
+ reportValidationResults(validation, { verbose: true });
612
+ if (validation.isValid) {
613
+ MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
614
+ }
615
+ else {
616
+ MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
617
+ }
618
+ return validation;
619
+ }
620
+ /**
621
+ * Extract session information from the current authenticated client
622
+ * This preserves session context for use across config reloads and adapter operations
623
+ */
624
+ extractSessionInfo() {
625
+ if (!this.config) {
626
+ return;
627
+ }
628
+ // Try to extract session from current config first
629
+ if (this.config.sessionCookie && isValidSessionCookie(this.config.sessionCookie)) {
630
+ this.sessionCookie = this.config.sessionCookie;
631
+ this.authMethod = "session";
632
+ this.sessionMetadata = this.config.sessionMetadata;
633
+ MessageFormatter.debug("Extracted session from config", { prefix: "Session" });
634
+ return;
635
+ }
636
+ // Fall back to finding session from Appwrite CLI preferences
637
+ const sessionAuth = findSessionByEndpointAndProject(this.config.appwriteEndpoint, this.config.appwriteProject);
638
+ if (sessionAuth && isValidSessionCookie(sessionAuth.sessionCookie)) {
639
+ this.sessionCookie = sessionAuth.sessionCookie;
640
+ this.authMethod = "session";
641
+ this.sessionMetadata = {
642
+ email: sessionAuth.email
643
+ };
644
+ MessageFormatter.debug(`Extracted session from CLI preferences for ${sessionAuth.email || 'unknown user'}`, { prefix: "Session" });
645
+ return;
646
+ }
647
+ // No session found, using API key authentication
648
+ if (this.config.appwriteKey) {
649
+ this.authMethod = "apikey";
650
+ this.sessionCookie = undefined;
651
+ this.sessionMetadata = undefined;
652
+ MessageFormatter.debug("Using API key authentication", { prefix: "Session" });
653
+ }
654
+ }
655
+ /**
656
+ * Create session preservation options for passing to loadConfig
657
+ * Returns current session state to maintain authentication across config reloads
658
+ */
659
+ createSessionPreservationOptions() {
660
+ if (!this.sessionCookie || !isValidSessionCookie(this.sessionCookie)) {
661
+ return undefined;
662
+ }
663
+ return createSessionPreservation(this.sessionCookie, this.sessionMetadata?.email, this.sessionMetadata?.expiresAt);
664
+ }
665
+ /**
666
+ * Get current session information for debugging/logging purposes
667
+ */
668
+ getSessionInfo() {
669
+ return {
670
+ hasSession: !!this.sessionCookie && isValidSessionCookie(this.sessionCookie),
671
+ authMethod: this.authMethod,
672
+ email: this.sessionMetadata?.email,
673
+ expiresAt: this.sessionMetadata?.expiresAt
674
+ };
675
+ }
546
676
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.5.2",
4
+ "version": "1.6.1",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -27,13 +27,17 @@
27
27
  "build": "bun run tsc",
28
28
  "start": "tsx --no-cache src/main.ts",
29
29
  "deploy": "bun run build && npm publish --access public",
30
+ "test": "jest",
31
+ "test:watch": "jest --watch",
32
+ "test:coverage": "jest --coverage",
33
+ "test:ci": "jest --ci --coverage --watchAll=false",
30
34
  "postinstall": "echo 'This package is intended for CLI use only and should not be added as a dependency in other projects.'"
31
35
  },
32
36
  "dependencies": {
33
37
  "@types/inquirer": "^9.0.8",
34
38
  "@types/json-schema": "^7.0.15",
35
39
  "@types/yargs": "^17.0.33",
36
- "appwrite-utils": "^1.4.1",
40
+ "appwrite-utils": "^1.6.1",
37
41
  "chalk": "^5.4.1",
38
42
  "cli-progress": "^3.12.0",
39
43
  "commander": "^12.1.0",
@@ -41,6 +45,7 @@
41
45
  "ignore": "^6.0.2",
42
46
  "inquirer": "^9.3.7",
43
47
  "js-yaml": "^4.1.0",
48
+ "jszip": "^3.10.1",
44
49
  "luxon": "^3.6.1",
45
50
  "nanostores": "^0.10.3",
46
51
  "node-appwrite": "^17",
@@ -54,10 +59,15 @@
54
59
  "zod": "^4.0.0"
55
60
  },
56
61
  "devDependencies": {
62
+ "@jest/globals": "^29.7.0",
57
63
  "@types/cli-progress": "^3.11.6",
64
+ "@types/jest": "^29.5.12",
58
65
  "@types/js-yaml": "^4.0.9",
66
+ "@types/jszip": "^3.4.1",
59
67
  "@types/lodash": "^4.17.18",
60
68
  "@types/luxon": "^3.6.2",
69
+ "jest": "^29.7.0",
70
+ "ts-jest": "^29.1.2",
61
71
  "typescript": "^5.8.3"
62
72
  }
63
73
  }