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
package/src/main.ts CHANGED
@@ -16,6 +16,10 @@ import { listSpecifications } from "./functions/methods.js";
16
16
  import { MessageFormatter } from "./shared/messageFormatter.js";
17
17
  import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
18
18
  import path from "path";
19
+ import fs from "fs";
20
+ import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig } from "./utils/projectConfig.js";
21
+ import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus } from "./utils/sessionAuth.js";
22
+ import { findYamlConfig, loadYamlConfigWithSession } from "./config/yamlConfig.js";
19
23
 
20
24
  interface CliOptions {
21
25
  config?: string;
@@ -28,6 +32,10 @@ interface CliOptions {
28
32
  generate?: boolean;
29
33
  import?: boolean;
30
34
  backup?: boolean;
35
+ backupFormat?: 'json' | 'zip';
36
+ comprehensiveBackup?: boolean;
37
+ trackingDatabaseId?: string;
38
+ parallelDownloads?: number;
31
39
  writeData?: boolean;
32
40
  push?: boolean;
33
41
  sync?: boolean;
@@ -53,10 +61,62 @@ interface CliOptions {
53
61
  generateConstants?: boolean;
54
62
  constantsLanguages?: string;
55
63
  constantsOutput?: string;
64
+ migrateCollectionsToTables?: boolean;
65
+ useSession?: boolean;
66
+ session?: string;
67
+ listBackups?: boolean;
56
68
  }
57
69
 
58
70
  type ParsedArgv = ArgumentsCamelCase<CliOptions>;
59
71
 
72
+ /**
73
+ * Checks if the migration from collections to tables should be allowed
74
+ * Returns an object with:
75
+ * - allowed: boolean indicating if migration should proceed
76
+ * - reason: string explaining why migration was blocked (if not allowed)
77
+ */
78
+ function checkMigrationConditions(configPath: string): { allowed: boolean; reason?: string } {
79
+ const collectionsPath = path.join(configPath, "collections");
80
+ const tablesPath = path.join(configPath, "tables");
81
+
82
+ // Check if collections/ folder exists
83
+ if (!fs.existsSync(collectionsPath)) {
84
+ return {
85
+ allowed: false,
86
+ reason: "No collections/ folder found. Migration requires existing collections to migrate."
87
+ };
88
+ }
89
+
90
+ // Check if collections/ folder has YAML files
91
+ const collectionFiles = fs.readdirSync(collectionsPath).filter(file =>
92
+ file.endsWith(".yaml") || file.endsWith(".yml")
93
+ );
94
+
95
+ if (collectionFiles.length === 0) {
96
+ return {
97
+ allowed: false,
98
+ reason: "No YAML files found in collections/ folder. Migration requires existing collection YAML files."
99
+ };
100
+ }
101
+
102
+ // Check if tables/ folder exists and has YAML files
103
+ if (fs.existsSync(tablesPath)) {
104
+ const tableFiles = fs.readdirSync(tablesPath).filter(file =>
105
+ file.endsWith(".yaml") || file.endsWith(".yml")
106
+ );
107
+
108
+ if (tableFiles.length > 0) {
109
+ return {
110
+ allowed: false,
111
+ reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`
112
+ };
113
+ }
114
+ }
115
+
116
+ // All conditions met
117
+ return { allowed: true };
118
+ }
119
+
60
120
  const argv = yargs(hideBin(process.argv))
61
121
  .option("config", {
62
122
  type: "string",
@@ -72,9 +132,9 @@ const argv = yargs(hideBin(process.argv))
72
132
  description: "Comma-separated list of database IDs to target (e.g., 'db1,db2,db3')",
73
133
  })
74
134
  .option("collectionIds", {
75
- alias: ["collIds"],
135
+ alias: ["collIds", "tableIds", "tables"],
76
136
  type: "string",
77
- description: "Comma-separated list of collection IDs to target (e.g., 'users,posts')",
137
+ description: "Comma-separated list of collection/table IDs to target (e.g., 'users,posts')",
78
138
  })
79
139
  .option("bucketIds", {
80
140
  type: "string",
@@ -88,7 +148,7 @@ const argv = yargs(hideBin(process.argv))
88
148
  .option("wipeCollections", {
89
149
  type: "boolean",
90
150
  description:
91
- "⚠️ DESTRUCTIVE: Wipe specific collections (requires --collectionIds)",
151
+ "⚠️ DESTRUCTIVE: Wipe specific collections/tables (requires --collectionIds or --tableIds)",
92
152
  })
93
153
  .option("transferUsers", {
94
154
  type: "boolean",
@@ -106,6 +166,31 @@ const argv = yargs(hideBin(process.argv))
106
166
  type: "boolean",
107
167
  description: "Create a complete backup of your databases and collections",
108
168
  })
169
+ .option("backupFormat", {
170
+ type: "string",
171
+ choices: ["json", "zip"] as const,
172
+ default: "json",
173
+ description: "Backup file format (json or zip)",
174
+ })
175
+ .option("listBackups", {
176
+ type: "boolean",
177
+ description: "List all backups for databases",
178
+ })
179
+ .option("comprehensiveBackup", {
180
+ alias: ["comprehensive", "backup-all"],
181
+ type: "boolean",
182
+ description: "🚀 Create comprehensive backup of ALL databases and ALL storage buckets",
183
+ })
184
+ .option("trackingDatabaseId", {
185
+ alias: ["tracking-db"],
186
+ type: "string",
187
+ description: "Database ID to use for centralized backup tracking (interactive prompt if not specified)",
188
+ })
189
+ .option("parallelDownloads", {
190
+ type: "number",
191
+ default: 10,
192
+ description: "Number of parallel file downloads for bucket backups (default: 10)",
193
+ })
109
194
  .option("writeData", {
110
195
  type: "boolean",
111
196
  description: "Output converted import data to files for validation before importing",
@@ -222,26 +307,136 @@ const argv = yargs(hideBin(process.argv))
222
307
  description: "Output directory for generated constants files (default: config-folder/constants)",
223
308
  default: "auto",
224
309
  })
310
+ .option("migrateCollectionsToTables", {
311
+ alias: ["migrate-collections"],
312
+ type: "boolean",
313
+ description: "Migrate collections to tables format for TablesDB API compatibility",
314
+ })
315
+ .option("useSession", {
316
+ alias: ["session"],
317
+ type: "boolean",
318
+ description: "Use Appwrite CLI session authentication instead of API key",
319
+ })
320
+ .option("sessionCookie", {
321
+ type: "string",
322
+ description: "Explicit session cookie to use for authentication",
323
+ })
225
324
  .parse() as ParsedArgv;
226
325
 
227
326
  async function main() {
228
327
  const startTime = Date.now();
229
328
  const operationStats: Record<string, number> = {};
230
329
 
330
+ // Early session detection for better user guidance
331
+ const availableSessions = getAvailableSessions();
332
+ let hasAnyValidSessions = availableSessions.length > 0;
333
+
231
334
  if (argv.it) {
232
335
  const cli = new InteractiveCLI(process.cwd());
233
336
  await cli.run();
234
337
  } else {
235
- const directConfig =
236
- argv.endpoint || argv.projectId || argv.apiKey
237
- ? {
238
- appwriteEndpoint: argv.endpoint,
239
- appwriteProject: argv.projectId,
240
- appwriteKey: argv.apiKey,
241
- }
242
- : undefined;
243
- const controller = new UtilsController(process.cwd(), directConfig);
244
- await controller.init();
338
+ // Enhanced config creation with session and project file support
339
+ let directConfig: any = undefined;
340
+
341
+ // Show authentication status on startup if no config provided
342
+ if (!argv.config && !argv.endpoint && !argv.projectId && !argv.apiKey && !argv.useSession && !argv.sessionCookie) {
343
+ if (hasAnyValidSessions) {
344
+ MessageFormatter.info(`Found ${availableSessions.length} available session(s)`, { prefix: "Auth" });
345
+ availableSessions.forEach(session => {
346
+ MessageFormatter.info(` \u2022 ${session.projectId} (${session.email || 'unknown'}) at ${session.endpoint}`, { prefix: "Auth" });
347
+ });
348
+ MessageFormatter.info("Use --session to enable session authentication", { prefix: "Auth" });
349
+ } else {
350
+ MessageFormatter.info("No active Appwrite sessions found", { prefix: "Auth" });
351
+ MessageFormatter.info("\u2022 Run 'appwrite login' to authenticate with session", { prefix: "Auth" });
352
+ MessageFormatter.info("\u2022 Or provide --apiKey for API key authentication", { prefix: "Auth" });
353
+ }
354
+ }
355
+
356
+ // Priority 1: Check for appwrite.json project configuration
357
+ const projectConfigPath = findAppwriteProjectConfig(process.cwd());
358
+ if (projectConfigPath) {
359
+ const projectConfig = loadAppwriteProjectConfig(projectConfigPath);
360
+ if (projectConfig) {
361
+ directConfig = projectConfigToAppwriteConfig(projectConfig);
362
+ MessageFormatter.info(`Loaded project configuration from ${projectConfigPath}`, { prefix: "CLI" });
363
+ }
364
+ }
365
+
366
+ // Priority 2: CLI arguments override project config
367
+ if (argv.endpoint || argv.projectId || argv.apiKey || argv.useSession || argv.sessionCookie) {
368
+ directConfig = {
369
+ ...directConfig,
370
+ appwriteEndpoint: argv.endpoint || directConfig?.appwriteEndpoint,
371
+ appwriteProject: argv.projectId || directConfig?.appwriteProject,
372
+ appwriteKey: argv.apiKey || directConfig?.appwriteKey,
373
+ };
374
+ }
375
+
376
+ // Priority 3: Session authentication support with improved detection
377
+ let sessionAuthAvailable = false;
378
+
379
+ if (directConfig?.appwriteEndpoint && directConfig?.appwriteProject) {
380
+ sessionAuthAvailable = hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
381
+ }
382
+
383
+ if (argv.useSession || argv.sessionCookie) {
384
+ if (argv.sessionCookie) {
385
+ // Explicit session cookie provided
386
+ MessageFormatter.info("Using explicit session cookie for authentication", { prefix: "Auth" });
387
+ } else if (sessionAuthAvailable) {
388
+ MessageFormatter.info("Session authentication detected and will be used", { prefix: "Auth" });
389
+ } else {
390
+ MessageFormatter.warning("Session authentication requested but no valid session found", { prefix: "Auth" });
391
+ const availableSessions = getAvailableSessions();
392
+ if (availableSessions.length > 0) {
393
+ MessageFormatter.info(`Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
394
+ MessageFormatter.info("Use --session flag to enable session authentication", { prefix: "Auth" });
395
+ } else {
396
+ MessageFormatter.warning("No Appwrite CLI sessions found. Please run 'appwrite login' first.", { prefix: "Auth" });
397
+ }
398
+ MessageFormatter.error("Session authentication requested but not available", undefined, { prefix: "Auth" });
399
+ return; // Exit early if session auth was requested but not available
400
+ }
401
+ } else if (sessionAuthAvailable && !argv.apiKey) {
402
+ // Auto-detect session authentication when no API key is provided
403
+ MessageFormatter.info("Session authentication detected - no API key required", { prefix: "Auth" });
404
+ MessageFormatter.info("Use --session flag to explicitly enable session authentication", { prefix: "Auth" });
405
+ }
406
+
407
+ // Enhanced session authentication support:
408
+ // 1. If session auth is explicitly requested via flags, use it
409
+ // 2. If no API key is provided but sessions are available, offer to use session auth
410
+ // 3. Auto-detect session authentication when possible
411
+ let finalDirectConfig = directConfig;
412
+
413
+ if ((argv.useSession || argv.sessionCookie) &&
414
+ (!directConfig || !directConfig.appwriteEndpoint || !directConfig.appwriteProject)) {
415
+ // Don't pass incomplete directConfig - let UtilsController load YAML config normally
416
+ finalDirectConfig = null;
417
+ } else if (finalDirectConfig && !finalDirectConfig.appwriteKey && !argv.useSession && !argv.sessionCookie) {
418
+ // Auto-detect session authentication when no API key provided
419
+ if (sessionAuthAvailable) {
420
+ MessageFormatter.info("No API key provided, but session authentication is available", { prefix: "Auth" });
421
+ MessageFormatter.info("Automatically using session authentication (add --session to suppress this message)", { prefix: "Auth" });
422
+ // Implicitly enable session authentication
423
+ argv.useSession = true;
424
+ }
425
+ }
426
+
427
+ // Create controller with session authentication support
428
+ const controller = new UtilsController(process.cwd(), finalDirectConfig);
429
+
430
+ // Pass session authentication options to the controller
431
+ const initOptions: any = {};
432
+ if (argv.useSession || argv.sessionCookie) {
433
+ initOptions.useSession = true;
434
+ if (argv.sessionCookie) {
435
+ initOptions.sessionCookie = argv.sessionCookie;
436
+ }
437
+ }
438
+
439
+ await controller.init(initOptions);
245
440
 
246
441
  if (argv.setup) {
247
442
  await setupDirsFiles(false, process.cwd());
@@ -288,13 +483,130 @@ async function main() {
288
483
  return;
289
484
  }
290
485
 
486
+ if (argv.migrateCollectionsToTables) {
487
+ try {
488
+ if (!controller.config) {
489
+ MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Migration" });
490
+ return;
491
+ }
492
+
493
+ // Get the config path from the controller or use .appwrite in current directory
494
+ let configPath = controller.getAppwriteFolderPath();
495
+ if (!configPath) {
496
+ // Try .appwrite in current directory
497
+ const defaultPath = path.join(process.cwd(), ".appwrite");
498
+ if (fs.existsSync(defaultPath)) {
499
+ configPath = defaultPath;
500
+ } else {
501
+ MessageFormatter.error("Could not determine configuration folder path", undefined, { prefix: "Migration" });
502
+ MessageFormatter.info("Make sure you have a .appwrite/ folder in your current directory", { prefix: "Migration" });
503
+ return;
504
+ }
505
+ }
506
+
507
+ // Check if migration conditions are met
508
+ const migrationCheck = checkMigrationConditions(configPath);
509
+ if (!migrationCheck.allowed) {
510
+ MessageFormatter.error(`Migration not allowed: ${migrationCheck.reason}`, undefined, { prefix: "Migration" });
511
+ MessageFormatter.info("Migration requirements:", { prefix: "Migration" });
512
+ MessageFormatter.info(" • Configuration must be loaded (use --config or have .appwrite/ folder)", { prefix: "Migration" });
513
+ MessageFormatter.info(" • collections/ folder must exist with YAML files", { prefix: "Migration" });
514
+ MessageFormatter.info(" • tables/ folder must not exist or be empty", { prefix: "Migration" });
515
+ return;
516
+ }
517
+
518
+ const { migrateCollectionsToTables } = await import("./config/configMigration.js");
519
+
520
+ MessageFormatter.info("Starting collections to tables migration...", { prefix: "Migration" });
521
+ const result = migrateCollectionsToTables(controller.config, {
522
+ strategy: "full_migration",
523
+ validateResult: true,
524
+ dryRun: false
525
+ });
526
+
527
+ if (result.success) {
528
+ operationStats.migratedCollections = result.changes.length;
529
+ MessageFormatter.success("Collections migration completed successfully", { prefix: "Migration" });
530
+ } else {
531
+ MessageFormatter.error(`Migration failed: ${result.errors.join(", ")}`, undefined, { prefix: "Migration" });
532
+ process.exit(1);
533
+ }
534
+ } catch (error) {
535
+ MessageFormatter.error("Migration failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
536
+ process.exit(1);
537
+ }
538
+ return;
539
+ }
540
+
291
541
  if (!controller.config) {
292
- MessageFormatter.error("No Appwrite connection found", undefined, { prefix: "CLI" });
542
+ // Provide better guidance based on available authentication methods
543
+ const availableSessions = getAvailableSessions();
544
+
545
+ if (availableSessions.length > 0) {
546
+ MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
547
+ MessageFormatter.info("Available authentication options:", { prefix: "Auth" });
548
+ MessageFormatter.info("• Session authentication: Add --session flag", { prefix: "Auth" });
549
+ MessageFormatter.info("• API key authentication: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
550
+ MessageFormatter.info(`• Available sessions: ${availableSessions.map(s => `${s.projectId} (${s.email || 'unknown'})`).join(", ")}`, { prefix: "Auth" });
551
+ } else {
552
+ MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "CLI" });
553
+ MessageFormatter.info("Authentication options:", { prefix: "Auth" });
554
+ MessageFormatter.info("• Login with Appwrite CLI: Run 'appwrite login' then use --session flag", { prefix: "Auth" });
555
+ MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
556
+ MessageFormatter.info("• Create config file: Run with --setup to initialize project configuration", { prefix: "Auth" });
557
+ }
293
558
  return;
294
559
  }
295
560
 
296
561
  const parsedArgv = argv;
297
562
 
563
+ // List backups if requested
564
+ if (parsedArgv.listBackups) {
565
+ const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
566
+ const { listBackups } = await import("./shared/backupTracking.js");
567
+
568
+ if (!controller.config) {
569
+ MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Backups" });
570
+ return;
571
+ }
572
+
573
+ const { adapter } = await AdapterFactory.create({
574
+ appwriteEndpoint: controller.config.appwriteEndpoint,
575
+ appwriteProject: controller.config.appwriteProject,
576
+ appwriteKey: controller.config.appwriteKey
577
+ });
578
+
579
+ const databases = parsedArgv.dbIds
580
+ ? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
581
+ : await fetchAllDatabases(controller.database!);
582
+
583
+ if (!databases || databases.length === 0) {
584
+ MessageFormatter.info("No databases found", { prefix: "Backups" });
585
+ return;
586
+ }
587
+
588
+ for (const db of databases!) {
589
+ const backups = await listBackups(adapter, db.$id);
590
+
591
+ MessageFormatter.info(`\nBackups for database: ${db.name} (${db.$id})`, { prefix: "Backups" });
592
+
593
+ if (backups.length === 0) {
594
+ MessageFormatter.info(" No backups found", { prefix: "Backups" });
595
+ } else {
596
+ backups.forEach((backup, index) => {
597
+ const date = new Date(backup.$createdAt).toLocaleString();
598
+ const size = MessageFormatter.formatBytes(backup.sizeBytes);
599
+ MessageFormatter.info(
600
+ ` ${index + 1}. ${date} - ${backup.format.toUpperCase()} - ${size} - ${backup.collections} collections, ${backup.documents} documents`,
601
+ { prefix: "Backups" }
602
+ );
603
+ });
604
+ }
605
+ }
606
+
607
+ return;
608
+ }
609
+
298
610
  const options: SetupOptions = {
299
611
  databases: parsedArgv.dbIds
300
612
  ? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
@@ -343,15 +655,15 @@ async function main() {
343
655
  );
344
656
  }
345
657
 
346
- // Add default databases if not specified
347
- if (!options.databases || options.databases.length === 0) {
658
+ // Add default databases if not specified (only if we need them for operations)
659
+ const needsDatabases = options.doBackup || options.wipeDatabase ||
660
+ options.wipeDocumentStorage || options.wipeUsers ||
661
+ options.wipeCollections || options.importData ||
662
+ parsedArgv.sync || parsedArgv.transfer;
663
+
664
+ if (needsDatabases && (!options.databases || options.databases.length === 0)) {
348
665
  const allDatabases = await fetchAllDatabases(controller.database!);
349
- options.databases = allDatabases.filter(
350
- (db) => {
351
- const useMigrations = controller.config?.useMigrations ?? true;
352
- return useMigrations || db.name.toLowerCase() !== "migrations";
353
- }
354
- );
666
+ options.databases = allDatabases;
355
667
  }
356
668
 
357
669
  // Add default collections if not specified
@@ -363,10 +675,92 @@ async function main() {
363
675
  }
364
676
  }
365
677
 
678
+ // Comprehensive backup (all databases + all buckets)
679
+ if (parsedArgv.comprehensiveBackup) {
680
+ const { comprehensiveBackup } = await import("./backups/operations/comprehensiveBackup.js");
681
+ const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
682
+
683
+ // Get tracking database ID (interactive prompt if not specified)
684
+ let trackingDatabaseId = parsedArgv.trackingDatabaseId;
685
+
686
+ if (!trackingDatabaseId) {
687
+ // Fetch all databases for selection
688
+ const allDatabases = await fetchAllDatabases(controller.database!);
689
+
690
+ if (allDatabases.length === 0) {
691
+ MessageFormatter.error("No databases found. Cannot create comprehensive backup without a tracking database.", undefined, { prefix: "Backup" });
692
+ return;
693
+ }
694
+
695
+ if (allDatabases.length === 1) {
696
+ trackingDatabaseId = allDatabases[0].$id;
697
+ MessageFormatter.info(`Using only available database for tracking: ${allDatabases[0].name} (${trackingDatabaseId})`, { prefix: "Backup" });
698
+ } else {
699
+ // Interactive selection
700
+ const inquirer = (await import("inquirer")).default;
701
+ const answer = await inquirer.prompt([{
702
+ type: 'list',
703
+ name: 'trackingDb',
704
+ message: 'Select database to store backup tracking metadata:',
705
+ choices: allDatabases.map(db => ({
706
+ name: `${db.name} (${db.$id})`,
707
+ value: db.$id
708
+ }))
709
+ }]);
710
+ trackingDatabaseId = answer.trackingDb;
711
+ }
712
+ }
713
+
714
+ // Ensure trackingDatabaseId is defined before proceeding
715
+ if (!trackingDatabaseId) {
716
+ throw new Error('Tracking database ID is required for comprehensive backup');
717
+ }
718
+
719
+ MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, { prefix: "Backup" });
720
+
721
+ // Create adapter for backup tracking
722
+ const { adapter } = await AdapterFactory.create({
723
+ appwriteEndpoint: controller.config!.appwriteEndpoint,
724
+ appwriteProject: controller.config!.appwriteProject,
725
+ appwriteKey: controller.config!.appwriteKey,
726
+ sessionCookie: controller.config!.sessionCookie
727
+ });
728
+
729
+ const result = await comprehensiveBackup(
730
+ controller.config!,
731
+ controller.database!,
732
+ controller.storage!,
733
+ adapter,
734
+ {
735
+ trackingDatabaseId,
736
+ backupFormat: parsedArgv.backupFormat || 'zip',
737
+ parallelDownloads: parsedArgv.parallelDownloads || 10,
738
+ onProgress: (message) => {
739
+ MessageFormatter.info(message, { prefix: "Backup" });
740
+ }
741
+ }
742
+ );
743
+
744
+ operationStats.comprehensiveBackup = 1;
745
+ operationStats.databasesBackedUp = result.databaseBackups.length;
746
+ operationStats.bucketsBackedUp = result.bucketBackups.length;
747
+ operationStats.totalBackupSize = result.totalSizeBytes;
748
+
749
+ if (result.status === 'completed') {
750
+ MessageFormatter.success(`Comprehensive backup completed successfully (ID: ${result.backupId})`, { prefix: "Backup" });
751
+ } else if (result.status === 'partial') {
752
+ MessageFormatter.warning(`Comprehensive backup completed with errors (ID: ${result.backupId})`, { prefix: "Backup" });
753
+ result.errors.forEach(err => MessageFormatter.warning(err, { prefix: "Backup" }));
754
+ } else {
755
+ MessageFormatter.error(`Comprehensive backup failed (ID: ${result.backupId})`, undefined, { prefix: "Backup" });
756
+ result.errors.forEach(err => MessageFormatter.error(err, undefined, { prefix: "Backup" }));
757
+ }
758
+ }
759
+
366
760
  if (options.doBackup && options.databases) {
367
- MessageFormatter.info(`Creating backups for ${options.databases.length} database(s)`, { prefix: "Backup" });
761
+ MessageFormatter.info(`Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`, { prefix: "Backup" });
368
762
  for (const db of options.databases) {
369
- await controller.backupDatabase(db);
763
+ await controller.backupDatabase(db, parsedArgv.backupFormat || 'json');
370
764
  }
371
765
  operationStats.backups = options.databases.length;
372
766
  MessageFormatter.success(`Backup completed for ${options.databases.length} database(s)`, { prefix: "Backup" });