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
@@ -0,0 +1,479 @@
1
+ import inquirer from "inquirer";
2
+ import chalk from "chalk";
3
+ import { join } from "node:path";
4
+ import { MessageFormatter } from "../../shared/messageFormatter.js";
5
+ import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
6
+ import { fetchAllDatabases } from "../../databases/methods.js";
7
+ import { listBuckets } from "../../storage/methods.js";
8
+ import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
9
+ export const databaseCommands = {
10
+ async syncDb(cli) {
11
+ MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
12
+ const databases = await cli.selectDatabases(cli.getLocalDatabases(), chalk.blue("Select local databases to push:"), true);
13
+ if (!databases.length) {
14
+ MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
15
+ return;
16
+ }
17
+ const collections = await cli.selectCollectionsAndTables(databases[0], cli.controller.database, chalk.blue("Select local collections/tables to push:"), true, // multiSelect
18
+ true // prefer local
19
+ // shouldFilterByDatabase removed - user will be prompted interactively
20
+ );
21
+ const { syncFunctions } = await inquirer.prompt([
22
+ {
23
+ type: "confirm",
24
+ name: "syncFunctions",
25
+ message: "Do you want to push local functions to remote?",
26
+ default: false,
27
+ },
28
+ ]);
29
+ try {
30
+ // First sync databases and collections
31
+ await cli.controller.syncDb(databases, collections);
32
+ MessageFormatter.success("Database and collections pushed successfully", { prefix: "Database" });
33
+ // Then handle functions if requested
34
+ if (syncFunctions && cli.controller.config?.functions?.length) {
35
+ const functions = await cli.selectFunctions(chalk.blue("Select local functions to push:"), true, true // prefer local
36
+ );
37
+ for (const func of functions) {
38
+ try {
39
+ await cli.controller.deployFunction(func.name);
40
+ MessageFormatter.success(`Function ${func.name} deployed successfully`, { prefix: "Functions" });
41
+ }
42
+ catch (error) {
43
+ MessageFormatter.error(`Failed to deploy function ${func.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Functions" });
44
+ }
45
+ }
46
+ }
47
+ MessageFormatter.success("Local configuration push completed successfully!", { prefix: "Database" });
48
+ }
49
+ catch (error) {
50
+ MessageFormatter.error("Failed to push local configuration", error instanceof Error ? error : new Error(String(error)), { prefix: "Database" });
51
+ throw error;
52
+ }
53
+ },
54
+ async synchronizeConfigurations(cli) {
55
+ MessageFormatter.progress("Synchronizing configurations...", { prefix: "Config" });
56
+ await cli.controller.init();
57
+ // Sync databases, collections, and buckets
58
+ const { syncDatabases } = await inquirer.prompt([
59
+ {
60
+ type: "confirm",
61
+ name: "syncDatabases",
62
+ message: "Do you want to synchronize databases, collections, and their buckets?",
63
+ default: true,
64
+ },
65
+ ]);
66
+ if (syncDatabases) {
67
+ const remoteDatabases = await fetchAllDatabases(cli.controller.database);
68
+ // Use the controller's synchronizeConfigurations method which handles collections properly
69
+ MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
70
+ await cli.controller.synchronizeConfigurations(remoteDatabases);
71
+ // Also configure buckets for any new databases
72
+ const localDatabases = cli.controller.config?.databases || [];
73
+ const updatedConfig = await cli.configureBuckets({
74
+ ...cli.controller.config,
75
+ databases: [
76
+ ...localDatabases,
77
+ ...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
78
+ ],
79
+ });
80
+ cli.controller.config = updatedConfig;
81
+ }
82
+ // Then sync functions
83
+ const { syncFunctions } = await inquirer.prompt([
84
+ {
85
+ type: "confirm",
86
+ name: "syncFunctions",
87
+ message: "Do you want to synchronize functions?",
88
+ default: true,
89
+ },
90
+ ]);
91
+ if (syncFunctions) {
92
+ const remoteFunctions = await cli.controller.listAllFunctions();
93
+ const localFunctions = cli.controller.config?.functions || [];
94
+ const allFunctions = [
95
+ ...remoteFunctions,
96
+ ...localFunctions.filter((f) => !remoteFunctions.some((rf) => rf.$id === f.$id)),
97
+ ];
98
+ for (const func of allFunctions) {
99
+ const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
100
+ const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
101
+ if (hasLocal && hasRemote) {
102
+ // Function exists in both local and remote
103
+ const { preference } = await inquirer.prompt([
104
+ {
105
+ type: "list",
106
+ name: "preference",
107
+ message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
108
+ choices: [
109
+ { name: "Keep local version (deploy to remote)", value: "local" },
110
+ { name: "Use remote version (download)", value: "remote" },
111
+ { name: "Update config only", value: "config" },
112
+ { name: "Skip this function", value: "skip" },
113
+ ],
114
+ },
115
+ ]);
116
+ if (preference === "local") {
117
+ await cli.controller.deployFunction(func.name);
118
+ }
119
+ else if (preference === "remote") {
120
+ await downloadLatestFunctionDeployment(cli.controller.appwriteServer, func.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
121
+ }
122
+ else if (preference === "config") {
123
+ // Update config with remote function details
124
+ const remoteFunction = await getFunction(cli.controller.appwriteServer, func.$id);
125
+ const newFunction = {
126
+ $id: remoteFunction.$id,
127
+ name: remoteFunction.name,
128
+ runtime: remoteFunction.runtime,
129
+ execute: remoteFunction.execute || [],
130
+ events: remoteFunction.events || [],
131
+ schedule: remoteFunction.schedule || "",
132
+ timeout: remoteFunction.timeout || 15,
133
+ enabled: remoteFunction.enabled !== false,
134
+ logging: remoteFunction.logging !== false,
135
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
136
+ commands: remoteFunction.commands || "npm install",
137
+ scopes: remoteFunction.scopes || [],
138
+ installationId: remoteFunction.installationId,
139
+ providerRepositoryId: remoteFunction.providerRepositoryId,
140
+ providerBranch: remoteFunction.providerBranch,
141
+ providerSilentMode: remoteFunction.providerSilentMode,
142
+ providerRootDirectory: remoteFunction.providerRootDirectory,
143
+ specification: remoteFunction.specification,
144
+ };
145
+ const existingIndex = cli.controller.config.functions.findIndex((f) => f.$id === remoteFunction.$id);
146
+ if (existingIndex >= 0) {
147
+ cli.controller.config.functions[existingIndex] = newFunction;
148
+ }
149
+ else {
150
+ cli.controller.config.functions.push(newFunction);
151
+ }
152
+ MessageFormatter.success(`Updated config for function: ${func.name}`, { prefix: "Functions" });
153
+ }
154
+ }
155
+ else if (hasLocal) {
156
+ // Function exists only locally
157
+ const { action } = await inquirer.prompt([
158
+ {
159
+ type: "list",
160
+ name: "action",
161
+ message: `Function "${func.name}" exists only locally. What would you like to do?`,
162
+ choices: [
163
+ { name: "Deploy to remote", value: "deploy" },
164
+ { name: "Skip this function", value: "skip" },
165
+ ],
166
+ },
167
+ ]);
168
+ if (action === "deploy") {
169
+ await cli.controller.deployFunction(func.name);
170
+ }
171
+ }
172
+ else if (hasRemote) {
173
+ // Function exists only remotely
174
+ const { action } = await inquirer.prompt([
175
+ {
176
+ type: "list",
177
+ name: "action",
178
+ message: `Function "${func.name}" exists only remotely. What would you like to do?`,
179
+ choices: [
180
+ { name: "Update config only", value: "config" },
181
+ { name: "Download locally", value: "download" },
182
+ { name: "Skip this function", value: "skip" },
183
+ ],
184
+ },
185
+ ]);
186
+ if (action === "download") {
187
+ await downloadLatestFunctionDeployment(cli.controller.appwriteServer, func.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
188
+ }
189
+ else if (action === "config") {
190
+ const remoteFunction = await getFunction(cli.controller.appwriteServer, func.$id);
191
+ const newFunction = {
192
+ $id: remoteFunction.$id,
193
+ name: remoteFunction.name,
194
+ runtime: remoteFunction.runtime,
195
+ execute: remoteFunction.execute || [],
196
+ events: remoteFunction.events || [],
197
+ schedule: remoteFunction.schedule || "",
198
+ timeout: remoteFunction.timeout || 15,
199
+ enabled: remoteFunction.enabled !== false,
200
+ logging: remoteFunction.logging !== false,
201
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
202
+ commands: remoteFunction.commands || "npm install",
203
+ scopes: remoteFunction.scopes || [],
204
+ installationId: remoteFunction.installationId,
205
+ providerRepositoryId: remoteFunction.providerRepositoryId,
206
+ providerBranch: remoteFunction.providerBranch,
207
+ providerSilentMode: remoteFunction.providerSilentMode,
208
+ providerRootDirectory: remoteFunction.providerRootDirectory,
209
+ specification: remoteFunction.specification,
210
+ };
211
+ cli.controller.config.functions =
212
+ cli.controller.config.functions || [];
213
+ cli.controller.config.functions.push(newFunction);
214
+ MessageFormatter.success(`Added config for remote function: ${func.name}`, { prefix: "Functions" });
215
+ }
216
+ }
217
+ }
218
+ }
219
+ MessageFormatter.success("✨ Configurations synchronized successfully!", { prefix: "Config" });
220
+ },
221
+ async backupDatabase(cli) {
222
+ if (!cli.controller.database || !cli.controller.storage) {
223
+ throw new Error("Database or Storage is not initialized, is the config file correct & created?");
224
+ }
225
+ try {
226
+ // STEP 1: Select tracking database
227
+ MessageFormatter.info("Step 1/5: Select tracking database", { prefix: "Backup" });
228
+ const trackingDb = await this.selectTrackingDatabase(cli);
229
+ // STEP 2: Ensure backup tracking table exists
230
+ MessageFormatter.info("Step 2/5: Initializing backup tracking", { prefix: "Backup" });
231
+ await this.ensureBackupTrackingTable(cli, trackingDb);
232
+ // STEP 3: Select backup scope
233
+ MessageFormatter.info("Step 3/5: Select backup scope", { prefix: "Backup" });
234
+ const scope = await this.selectBackupScope(cli);
235
+ // STEP 4: Show confirmation
236
+ MessageFormatter.info("Step 4/5: Confirm backup plan", { prefix: "Backup" });
237
+ const confirmed = await this.confirmBackupPlan(scope);
238
+ if (!confirmed) {
239
+ MessageFormatter.info("Backup cancelled by user", { prefix: "Backup" });
240
+ return;
241
+ }
242
+ // STEP 5: Execute unified backup
243
+ MessageFormatter.info("Step 5/5: Executing backup", { prefix: "Backup" });
244
+ await this.executeUnifiedBackup(cli, trackingDb, scope);
245
+ MessageFormatter.success("Backup operation completed successfully", { prefix: "Backup" });
246
+ }
247
+ catch (error) {
248
+ MessageFormatter.error("Backup operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Backup" });
249
+ throw error;
250
+ }
251
+ },
252
+ // Helper method: Select tracking database
253
+ async selectTrackingDatabase(cli) {
254
+ const databases = await fetchAllDatabases(cli.controller.database);
255
+ const { trackingDatabaseId } = await inquirer.prompt([
256
+ {
257
+ type: "list",
258
+ name: "trackingDatabaseId",
259
+ message: "Select database to store backup metadata:",
260
+ choices: databases.map(db => ({
261
+ name: `${db.name} (${db.$id})`,
262
+ value: db.$id
263
+ }))
264
+ }
265
+ ]);
266
+ MessageFormatter.info(`Using ${trackingDatabaseId} for backup tracking`, { prefix: "Backup" });
267
+ return trackingDatabaseId;
268
+ },
269
+ // Helper method: Ensure backup tracking table exists
270
+ async ensureBackupTrackingTable(cli, trackingDatabaseId) {
271
+ const { createCentralizedBackupTrackingTable } = await import("../../backups/tracking/centralizedTracking.js");
272
+ const adapter = cli.controller.adapter;
273
+ await createCentralizedBackupTrackingTable(adapter, trackingDatabaseId);
274
+ MessageFormatter.success("Backup tracking table ready", { prefix: "Backup" });
275
+ },
276
+ // Helper method: Select backup scope
277
+ async selectBackupScope(cli) {
278
+ const { scopeType } = await inquirer.prompt([
279
+ {
280
+ type: "list",
281
+ name: "scopeType",
282
+ message: "What would you like to backup?",
283
+ choices: [
284
+ { name: "Comprehensive (ALL databases + ALL buckets)", value: "comprehensive" },
285
+ { name: "Selective databases (choose specific databases)", value: "selective-databases" },
286
+ { name: "Selective collections (choose specific collections)", value: "selective-collections" }
287
+ ]
288
+ }
289
+ ]);
290
+ if (scopeType === "comprehensive") {
291
+ return { type: "comprehensive" };
292
+ }
293
+ if (scopeType === "selective-databases") {
294
+ const databases = await fetchAllDatabases(cli.controller.database);
295
+ const selectedDatabases = await cli.selectDatabases(databases, "Select databases to backup:");
296
+ const { includeBuckets } = await inquirer.prompt([
297
+ {
298
+ type: "confirm",
299
+ name: "includeBuckets",
300
+ message: "Include storage buckets in backup?",
301
+ default: false
302
+ }
303
+ ]);
304
+ let selectedBuckets = [];
305
+ if (includeBuckets) {
306
+ const buckets = await listBuckets(cli.controller.storage);
307
+ const { bucketIds } = await inquirer.prompt([
308
+ {
309
+ type: "checkbox",
310
+ name: "bucketIds",
311
+ message: "Select buckets to backup:",
312
+ choices: buckets.buckets.map((b) => ({
313
+ name: `${b.name} (${b.$id})`,
314
+ value: b.$id
315
+ }))
316
+ }
317
+ ]);
318
+ selectedBuckets = bucketIds;
319
+ }
320
+ return {
321
+ type: "selective-databases",
322
+ databases: selectedDatabases,
323
+ buckets: selectedBuckets
324
+ };
325
+ }
326
+ if (scopeType === "selective-collections") {
327
+ const databases = await fetchAllDatabases(cli.controller.database);
328
+ const selectedDatabase = await cli.selectDatabases(databases, "Select database containing collections:", false // single selection
329
+ );
330
+ if (!selectedDatabase || selectedDatabase.length === 0) {
331
+ throw new Error("No database selected");
332
+ }
333
+ const db = selectedDatabase[0];
334
+ const collections = await cli.selectCollectionsAndTables(db, cli.controller.database, "Select collections to backup:", true, true, true);
335
+ return {
336
+ type: "selective-collections",
337
+ databaseId: db.$id,
338
+ databaseName: db.name,
339
+ collections: collections
340
+ };
341
+ }
342
+ throw new Error("Invalid backup scope selected");
343
+ },
344
+ // Helper method: Confirm backup plan
345
+ async confirmBackupPlan(scope) {
346
+ let summary = "\n" + chalk.bold("Backup Plan Summary:") + "\n";
347
+ if (scope.type === "comprehensive") {
348
+ summary += " • ALL databases\n";
349
+ summary += " • ALL storage buckets\n";
350
+ }
351
+ else if (scope.type === "selective-databases") {
352
+ summary += ` • ${scope.databases.length} selected databases\n`;
353
+ if (scope.buckets.length > 0) {
354
+ summary += ` • ${scope.buckets.length} selected buckets\n`;
355
+ }
356
+ }
357
+ else if (scope.type === "selective-collections") {
358
+ summary += ` • Database: ${scope.databaseName}\n`;
359
+ summary += ` • ${scope.collections.length} selected collections\n`;
360
+ }
361
+ console.log(summary);
362
+ const { confirmed } = await inquirer.prompt([
363
+ {
364
+ type: "confirm",
365
+ name: "confirmed",
366
+ message: "Proceed with backup?",
367
+ default: true
368
+ }
369
+ ]);
370
+ return confirmed;
371
+ },
372
+ // Helper method: Execute unified backup
373
+ async executeUnifiedBackup(cli, trackingDatabaseId, scope) {
374
+ if (scope.type === "comprehensive") {
375
+ const { comprehensiveBackup } = await import("../../backups/operations/comprehensiveBackup.js");
376
+ await comprehensiveBackup(cli.controller.config, cli.controller.database, cli.controller.storage, cli.controller.adapter, {
377
+ trackingDatabaseId,
378
+ backupFormat: 'zip',
379
+ parallelDownloads: 10,
380
+ onProgress: (message) => {
381
+ MessageFormatter.progress(message, { prefix: "Backup" });
382
+ }
383
+ });
384
+ }
385
+ else if (scope.type === "selective-databases") {
386
+ // Backup each selected database
387
+ for (const db of scope.databases) {
388
+ MessageFormatter.progress(`Backing up database: ${db.name}`, { prefix: "Backup" });
389
+ await cli.controller.backupDatabase(db);
390
+ }
391
+ // Backup selected buckets if any
392
+ for (const bucketId of scope.buckets) {
393
+ MessageFormatter.progress(`Backing up bucket: ${bucketId}`, { prefix: "Backup" });
394
+ const { backupBucket } = await import("../../backups/operations/bucketBackup.js");
395
+ await backupBucket(cli.controller.storage, bucketId, "appwrite-backups", { parallelDownloads: 10 });
396
+ }
397
+ }
398
+ else if (scope.type === "selective-collections") {
399
+ const { backupCollections } = await import("../../backups/operations/collectionBackup.js");
400
+ await backupCollections(cli.controller.config, cli.controller.database, cli.controller.storage, cli.controller.adapter, {
401
+ trackingDatabaseId,
402
+ databaseId: scope.databaseId,
403
+ collectionIds: scope.collections.map((c) => c.$id || c.id),
404
+ backupFormat: 'zip',
405
+ onProgress: (message) => {
406
+ MessageFormatter.progress(message, { prefix: "Backup" });
407
+ }
408
+ });
409
+ }
410
+ },
411
+ async wipeDatabase(cli) {
412
+ if (!cli.controller.database || !cli.controller.storage) {
413
+ throw new Error("Database or Storage is not initialized, is the config file correct & created?");
414
+ }
415
+ const databases = await fetchAllDatabases(cli.controller.database);
416
+ const storage = await listBuckets(cli.controller.storage);
417
+ const selectedDatabases = await cli.selectDatabases(databases, "Select databases to wipe:");
418
+ const { selectedStorage } = await inquirer.prompt([
419
+ {
420
+ type: "checkbox",
421
+ name: "selectedStorage",
422
+ message: "Select storage buckets to wipe:",
423
+ choices: storage.buckets.map((s) => ({ name: s.name, value: s.$id })),
424
+ },
425
+ ]);
426
+ const { wipeUsers } = await inquirer.prompt([
427
+ {
428
+ type: "confirm",
429
+ name: "wipeUsers",
430
+ message: "Do you want to wipe users as well?",
431
+ default: false,
432
+ },
433
+ ]);
434
+ const databaseNames = selectedDatabases.map((db) => db.name);
435
+ const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(databaseNames, {
436
+ includeStorage: selectedStorage.length > 0,
437
+ includeUsers: wipeUsers
438
+ });
439
+ if (confirmed) {
440
+ MessageFormatter.info("Starting wipe operation...", { prefix: "Wipe" });
441
+ for (const db of selectedDatabases) {
442
+ await cli.controller.wipeDatabase(db);
443
+ }
444
+ for (const bucketId of selectedStorage) {
445
+ await cli.controller.wipeDocumentStorage(bucketId);
446
+ }
447
+ if (wipeUsers) {
448
+ await cli.controller.wipeUsers();
449
+ }
450
+ MessageFormatter.success("Wipe operation completed", { prefix: "Wipe" });
451
+ }
452
+ else {
453
+ MessageFormatter.info("Wipe operation cancelled", { prefix: "Wipe" });
454
+ }
455
+ },
456
+ async wipeCollections(cli) {
457
+ if (!cli.controller.database) {
458
+ throw new Error("Database is not initialized, is the config file correct & created?");
459
+ }
460
+ const databases = await fetchAllDatabases(cli.controller.database);
461
+ const selectedDatabases = await cli.selectDatabases(databases, "Select the database(s) containing the collections to wipe:", true);
462
+ for (const database of selectedDatabases) {
463
+ const collections = await cli.selectCollectionsAndTables(database, cli.controller.database, `Select collections/tables to wipe from ${database.name}:`, true, undefined, true);
464
+ const collectionNames = collections.map((c) => c.name);
465
+ const confirmed = await ConfirmationDialogs.confirmCollectionWipe(database.name, collectionNames);
466
+ if (confirmed) {
467
+ MessageFormatter.info(`Wiping selected collections from ${database.name}...`, { prefix: "Wipe" });
468
+ for (const collection of collections) {
469
+ await cli.controller.wipeCollection(database, collection);
470
+ MessageFormatter.success(`Collection ${collection.name} wiped successfully`, { prefix: "Wipe" });
471
+ }
472
+ }
473
+ else {
474
+ MessageFormatter.info(`Wipe operation cancelled for ${database.name}`, { prefix: "Wipe" });
475
+ }
476
+ }
477
+ MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
478
+ }
479
+ };
@@ -0,0 +1,7 @@
1
+ import type { InteractiveCLI } from "../../interactiveCLI.js";
2
+ export declare const functionCommands: {
3
+ createFunction(cli: InteractiveCLI): Promise<void>;
4
+ deployFunction(cli: InteractiveCLI): Promise<void>;
5
+ deleteFunction(cli: InteractiveCLI): Promise<void>;
6
+ updateFunctionSpec(cli: InteractiveCLI): Promise<void>;
7
+ };