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
@@ -3,6 +3,7 @@ import { loadConfig } from "./utils/loadConfigs.js";
3
3
  import path from "path";
4
4
  import fs from "fs";
5
5
  import type { AppwriteConfig } from "appwrite-utils";
6
+ import { MessageFormatter } from "./shared/messageFormatter.js";
6
7
 
7
8
  export class SetupController {
8
9
  private currentDir: string;
@@ -14,7 +15,7 @@ export class SetupController {
14
15
 
15
16
  async runSetup(withExampleData: boolean = false): Promise<void> {
16
17
  await setupDirsFiles(withExampleData, this.currentDir);
17
- console.log("Setup completed successfully.");
18
+ MessageFormatter.success("Setup completed successfully", { prefix: "Setup" });
18
19
  }
19
20
 
20
21
  async loadConfig(): Promise<AppwriteConfig | null> {
@@ -24,7 +25,7 @@ export class SetupController {
24
25
  this.config = await loadConfig(appwriteDir);
25
26
  return this.config;
26
27
  } catch (error) {
27
- console.error("Error loading config:", error);
28
+ MessageFormatter.error("Error loading config", error as Error, { prefix: "Setup" });
28
29
  return null;
29
30
  }
30
31
  }
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for centralized backup tracking table (_appwrite_backups)
5
+ *
6
+ * Tracks all backups created for databases, buckets, and comprehensive backups
7
+ */
8
+
9
+ export type BackupType = 'database' | 'bucket' | 'comprehensive';
10
+ export type BackupFormat = 'json' | 'zip';
11
+ export type BackupStatus = 'completed' | 'partial' | 'failed';
12
+ export type RestorationStatus = 'completed' | 'partial' | 'failed' | 'not_restored';
13
+
14
+ export const BACKUP_TYPES = ['database', 'bucket', 'comprehensive'] as const;
15
+ export const BACKUP_FORMATS = ['json', 'zip'] as const;
16
+ export const BACKUP_STATUSES = ['completed', 'partial', 'failed'] as const;
17
+ export const RESTORATION_STATUSES = ['completed', 'partial', 'failed', 'not_restored'] as const;
18
+
19
+ export const BackupTypeSchema = z.enum(['database', 'bucket', 'comprehensive']);
20
+ export const BackupFormatSchema = z.enum(['json', 'zip']);
21
+ export const BackupStatusSchema = z.enum(['completed', 'partial', 'failed']);
22
+ export const RestorationStatusSchema = z.enum(['completed', 'partial', 'failed', 'not_restored']);
23
+
24
+ export interface BackupMetadata {
25
+ $id: string;
26
+ $createdAt: string;
27
+ $updatedAt: string;
28
+
29
+ // Core backup info
30
+ backupType: BackupType; // Type of backup: database, bucket, or comprehensive
31
+ backupId: string; // File ID in storage bucket for the backup file
32
+ manifestFileId?: string; // File ID for the manifest JSON file
33
+ format: BackupFormat; // 'json' or 'zip'
34
+ sizeBytes: number; // Total backup file size
35
+
36
+ // Resource identification (at least one must be present)
37
+ databaseId?: string; // Database ID (for database backups)
38
+ bucketId?: string; // Bucket ID (for bucket backups)
39
+ comprehensiveBackupId?: string; // Parent comprehensive backup ID
40
+
41
+ // Database-specific metrics
42
+ collections?: number; // Number of collections backed up
43
+ documents?: number; // Number of documents backed up
44
+
45
+ // Bucket-specific metrics
46
+ fileCount?: number; // Number of files backed up (for bucket backups)
47
+
48
+ // Status tracking
49
+ status: BackupStatus; // 'completed', 'partial', or 'failed'
50
+ error?: string; // Error message if failed or partial
51
+
52
+ // Restoration tracking
53
+ restoredAt?: string; // ISO timestamp of restoration
54
+ restorationStatus: RestorationStatus; // Restoration status
55
+ restorationError?: string; // Error message if restoration failed
56
+ }
57
+
58
+ export const BackupMetadataSchema = z.object({
59
+ $id: z.string(),
60
+ $createdAt: z.string(),
61
+ $updatedAt: z.string(),
62
+
63
+ // Core backup info
64
+ backupType: BackupTypeSchema,
65
+ backupId: z.string(),
66
+ manifestFileId: z.string().optional(),
67
+ format: BackupFormatSchema,
68
+ sizeBytes: z.number(),
69
+
70
+ // Resource identification
71
+ databaseId: z.string().optional(),
72
+ bucketId: z.string().optional(),
73
+ comprehensiveBackupId: z.string().optional(),
74
+
75
+ // Database-specific metrics
76
+ collections: z.number().optional(),
77
+ documents: z.number().optional(),
78
+
79
+ // Bucket-specific metrics
80
+ fileCount: z.number().optional(),
81
+
82
+ // Status tracking
83
+ status: BackupStatusSchema,
84
+ error: z.string().optional(),
85
+
86
+ // Restoration tracking
87
+ restoredAt: z.string().optional(),
88
+ restorationStatus: RestorationStatusSchema.default('not_restored'),
89
+ restorationError: z.string().optional()
90
+ });
91
+
92
+ export const BACKUP_TABLE_ID = "appwrite_backups";
93
+ export const BACKUP_TABLE_NAME = "Backup Tracking";
@@ -0,0 +1,211 @@
1
+ import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
2
+ import { logger } from "./logging.js";
3
+ import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
4
+ import { Query, ID } from "node-appwrite";
5
+ import {
6
+ BACKUP_TABLE_ID,
7
+ BACKUP_TABLE_NAME,
8
+ type BackupMetadata,
9
+ BackupMetadataSchema
10
+ } from "./backupMetadataSchema.js";
11
+
12
+ /**
13
+ * Checks if backup tracking table exists in database
14
+ */
15
+ async function tableExists(
16
+ db: DatabaseAdapter,
17
+ databaseId: string
18
+ ): Promise<boolean> {
19
+ try {
20
+ await db.getTable({ databaseId, tableId: BACKUP_TABLE_ID });
21
+ return true;
22
+ } catch (error) {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Creates the backup tracking table in the specified database
29
+ */
30
+ export async function createBackupTrackingTable(
31
+ db: DatabaseAdapter,
32
+ databaseId: string
33
+ ): Promise<void> {
34
+ // Check if table already exists
35
+ const exists = await tableExists(db, databaseId);
36
+ if (exists) {
37
+ logger.debug("Backup tracking table already exists", {
38
+ databaseId,
39
+ tableId: BACKUP_TABLE_ID
40
+ });
41
+ return;
42
+ }
43
+
44
+ logger.info("Creating backup tracking table", {
45
+ databaseId,
46
+ tableId: BACKUP_TABLE_ID
47
+ });
48
+
49
+ // Create table
50
+ await tryAwaitWithRetry(async () => {
51
+ await db.createTable({
52
+ databaseId,
53
+ id: BACKUP_TABLE_ID,
54
+ name: BACKUP_TABLE_NAME
55
+ });
56
+ });
57
+
58
+ // Create attributes
59
+ const attributes = [
60
+ {
61
+ key: "backupId",
62
+ type: "string" as const,
63
+ size: 50,
64
+ required: true
65
+ },
66
+ {
67
+ key: "databaseId",
68
+ type: "string" as const,
69
+ size: 50,
70
+ required: true
71
+ },
72
+ {
73
+ key: "sizeBytes",
74
+ type: "integer" as const,
75
+ required: true
76
+ },
77
+ {
78
+ key: "collections",
79
+ type: "integer" as const,
80
+ required: true
81
+ },
82
+ {
83
+ key: "documents",
84
+ type: "integer" as const,
85
+ required: true
86
+ },
87
+ {
88
+ key: "format",
89
+ type: "enum" as const,
90
+ elements: ["json", "zip"],
91
+ required: true
92
+ },
93
+ {
94
+ key: "status",
95
+ type: "enum" as const,
96
+ elements: ["completed", "failed"],
97
+ required: true
98
+ },
99
+ {
100
+ key: "error",
101
+ type: "string" as const,
102
+ size: 10000,
103
+ required: false
104
+ }
105
+ ];
106
+
107
+ for (const attr of attributes) {
108
+ await tryAwaitWithRetry(async () => {
109
+ await db.createAttribute({
110
+ databaseId,
111
+ tableId: BACKUP_TABLE_ID,
112
+ ...attr
113
+ });
114
+ });
115
+ }
116
+
117
+ logger.info("Backup tracking table created successfully", {
118
+ databaseId,
119
+ tableId: BACKUP_TABLE_ID
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Records backup metadata in the tracking table
125
+ */
126
+ export async function recordBackup(
127
+ db: DatabaseAdapter,
128
+ databaseId: string,
129
+ metadata: Omit<BackupMetadata, '$id' | '$createdAt' | '$updatedAt'>
130
+ ): Promise<BackupMetadata> {
131
+ // Ensure tracking table exists
132
+ await createBackupTrackingTable(db, databaseId);
133
+
134
+ // Create backup record
135
+ const result = await db.createRow({
136
+ databaseId,
137
+ tableId: BACKUP_TABLE_ID,
138
+ id: ID.unique(),
139
+ data: {
140
+ backupId: metadata.backupId,
141
+ databaseId: metadata.databaseId,
142
+ sizeBytes: metadata.sizeBytes,
143
+ collections: metadata.collections,
144
+ documents: metadata.documents,
145
+ format: metadata.format,
146
+ status: metadata.status,
147
+ error: metadata.error
148
+ }
149
+ });
150
+
151
+ logger.info("Recorded backup metadata", {
152
+ backupId: metadata.backupId,
153
+ databaseId: metadata.databaseId,
154
+ format: metadata.format
155
+ });
156
+
157
+ return result.data as BackupMetadata;
158
+ }
159
+
160
+ /**
161
+ * Lists all backups for a database, sorted by creation date (newest first)
162
+ */
163
+ export async function listBackups(
164
+ db: DatabaseAdapter,
165
+ databaseId: string
166
+ ): Promise<BackupMetadata[]> {
167
+ try {
168
+ const result = await db.listRows({
169
+ databaseId,
170
+ tableId: BACKUP_TABLE_ID,
171
+ queries: [
172
+ Query.orderDesc("$createdAt"),
173
+ Query.limit(100) // Limit to last 100 backups
174
+ ]
175
+ });
176
+
177
+ return (result.rows || []) as BackupMetadata[];
178
+ } catch (error) {
179
+ // Table might not exist yet
180
+ logger.debug("No backup tracking table found", { databaseId });
181
+ return [];
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Gets the most recent backup for a database
187
+ */
188
+ export async function getLastBackup(
189
+ db: DatabaseAdapter,
190
+ databaseId: string
191
+ ): Promise<BackupMetadata | null> {
192
+ try {
193
+ const result = await db.listRows({
194
+ databaseId,
195
+ tableId: BACKUP_TABLE_ID,
196
+ queries: [
197
+ Query.orderDesc("$createdAt"),
198
+ Query.limit(1)
199
+ ]
200
+ });
201
+
202
+ if (result.rows && result.rows.length > 0) {
203
+ return result.rows[0] as BackupMetadata;
204
+ }
205
+
206
+ return null;
207
+ } catch (error) {
208
+ logger.debug("No backup found or table doesn't exist", { databaseId });
209
+ return null;
210
+ }
211
+ }
@@ -27,18 +27,18 @@ export class ConfirmationDialogs {
27
27
  return true;
28
28
  }
29
29
 
30
- MessageFormatter.warning(`You are about to perform a destructive operation:`);
31
- console.log(chalk.red.bold(` Operation: ${options.operation}`));
32
- console.log(chalk.yellow(` Targets: ${options.targets.join(", ")}`));
33
-
30
+ MessageFormatter.warning(`You are about to perform a destructive operation:`, { skipLogging: true });
31
+ MessageFormatter.error(`Operation: ${options.operation}`, undefined, { skipLogging: true });
32
+ MessageFormatter.warning(`Targets: ${options.targets.join(", ")}`, { skipLogging: true });
33
+
34
34
  if (options.consequences && options.consequences.length > 0) {
35
- console.log(chalk.red("\n This will:"));
35
+ MessageFormatter.error("This will:", undefined, { skipLogging: true });
36
36
  options.consequences.forEach(consequence => {
37
- console.log(chalk.red(` • ${consequence}`));
37
+ MessageFormatter.error(` • ${consequence}`, undefined, { skipLogging: true });
38
38
  });
39
39
  }
40
40
 
41
- console.log(chalk.red("\n ⚠️ THIS ACTION CANNOT BE UNDONE!"));
41
+ MessageFormatter.error("⚠️ THIS ACTION CANNOT BE UNDONE!", undefined, { skipLogging: true });
42
42
 
43
43
  if (options.requireExplicitConfirmation && options.confirmationText) {
44
44
  const { confirmation } = await inquirer.prompt([{
@@ -68,12 +68,12 @@ export class ConfirmationDialogs {
68
68
  * Prompts user about creating a backup before a destructive operation
69
69
  */
70
70
  static async promptForBackup(options: BackupPromptOptions): Promise<'yes' | 'no' | 'skip'> {
71
- const message = options.backupMessage ||
71
+ const message = options.backupMessage ||
72
72
  `Create a backup before performing ${options.operation} on: ${options.targets.join(", ")}?`;
73
73
 
74
- console.log(chalk.blue("\n🛡️ Backup Recommendation"));
74
+ MessageFormatter.info("🛡️ Backup Recommendation", { skipLogging: true });
75
75
  if (options.recommendBackup !== false) {
76
- console.log(chalk.yellow(" It's strongly recommended to create a backup before proceeding."));
76
+ MessageFormatter.warning("It's strongly recommended to create a backup before proceeding.", { skipLogging: true });
77
77
  }
78
78
 
79
79
  const { choice } = await inquirer.prompt([{
@@ -95,12 +95,12 @@ export class ConfirmationDialogs {
95
95
  * Shows a final confirmation before proceeding with an operation
96
96
  */
97
97
  static async finalConfirmation(operation: string, details?: string[]): Promise<boolean> {
98
- console.log(chalk.green(`\n✅ Ready to perform: ${chalk.bold(operation)}`));
99
-
98
+ MessageFormatter.success(`Ready to perform: ${operation}`, { skipLogging: true });
99
+
100
100
  if (details && details.length > 0) {
101
- console.log(chalk.gray(" Details:"));
101
+ MessageFormatter.debug("Details:", undefined, { skipLogging: true });
102
102
  details.forEach(detail => {
103
- console.log(chalk.gray(` • ${detail}`));
103
+ MessageFormatter.debug(` • ${detail}`, undefined, { skipLogging: true });
104
104
  });
105
105
  }
106
106
 
@@ -215,19 +215,19 @@ export class ConfirmationDialogs {
215
215
 
216
216
  Object.entries(summary).forEach(([key, value]) => {
217
217
  const formattedKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
218
-
218
+
219
219
  if (Array.isArray(value)) {
220
- console.log(`${chalk.gray("●")} ${formattedKey}:`);
220
+ MessageFormatter.info(`● ${formattedKey}:`, { skipLogging: true });
221
221
  value.forEach(item => {
222
- console.log(` ${chalk.gray("")} ${item}`);
222
+ MessageFormatter.debug(` • ${item}`, undefined, { skipLogging: true });
223
223
  });
224
224
  } else {
225
- console.log(`${chalk.gray("●")} ${formattedKey}: ${chalk.cyan(String(value))}`);
225
+ MessageFormatter.info(`● ${formattedKey}: ${String(value)}`, { skipLogging: true });
226
226
  }
227
227
  });
228
228
 
229
229
  if (options.warningMessage) {
230
- console.log(chalk.yellow(`\n⚠️ ${options.warningMessage}`));
230
+ MessageFormatter.warning(`⚠️ ${options.warningMessage}`, { skipLogging: true });
231
231
  }
232
232
 
233
233
  if (options.confirmationRequired !== false) {
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Shared error handling utilities for consistent error processing
3
+ */
4
+
5
+ /**
6
+ * Extracts error message from unknown error type
7
+ * @param error - Error of unknown type
8
+ * @returns String error message
9
+ */
10
+ export function getErrorMessage(error: unknown): string {
11
+ return error instanceof Error ? error.message : String(error);
12
+ }
13
+
14
+ /**
15
+ * Normalizes unknown error to Error instance
16
+ * @param error - Error of unknown type
17
+ * @returns Normalized Error object
18
+ */
19
+ export function normalizeError(error: unknown): Error {
20
+ return error instanceof Error ? error : new Error(String(error));
21
+ }
22
+
23
+ /**
24
+ * Checks if error is a conflict error (409)
25
+ * @param error - Error object to check
26
+ * @returns True if error is a conflict (duplicate resource)
27
+ */
28
+ export function isConflictError(error: any): boolean {
29
+ return error?.code === 409 || (error?.message?.toLowerCase().includes('already exists') ?? false);
30
+ }
31
+
32
+ /**
33
+ * Checks if error is a Cloudflare error (522)
34
+ * @param error - Error object to check
35
+ * @returns True if error is from Cloudflare connection timeout
36
+ */
37
+ export function isCloudflareError(error: any): boolean {
38
+ return error?.code === 522 || error?.code === "522";
39
+ }
40
+
41
+ /**
42
+ * Checks if error is authentication/authorization related
43
+ * @param error - Error object to check
44
+ * @returns True if error is auth-related (401, 403)
45
+ */
46
+ export function isAuthError(error: any): boolean {
47
+ const code = error?.code;
48
+ return code === 401 || code === 403 || code === "401" || code === "403";
49
+ }
50
+
51
+ /**
52
+ * Error categorization helpers for smart fallback logic
53
+ */
54
+
55
+ /**
56
+ * Checks if an error is retryable (network issues, rate limits, etc.)
57
+ * @param errorMessage - Error message to check
58
+ * @returns True if error is retryable
59
+ */
60
+ export function isRetryableError(errorMessage: string): boolean {
61
+ const retryableErrors = [
62
+ "rate limit",
63
+ "timeout",
64
+ "network",
65
+ "temporary",
66
+ "503",
67
+ "502",
68
+ "429"
69
+ ];
70
+ return retryableErrors.some(error =>
71
+ errorMessage.toLowerCase().includes(error)
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Checks if an error indicates bulk operations are not supported
77
+ * @param errorMessage - Error message to check
78
+ * @returns True if bulk operations not supported
79
+ */
80
+ export function isBulkNotSupportedError(errorMessage: string): boolean {
81
+ const notSupportedErrors = [
82
+ "method not found",
83
+ "not implemented",
84
+ "unsupported",
85
+ "not available",
86
+ "404"
87
+ ];
88
+ return notSupportedErrors.some(error =>
89
+ errorMessage.toLowerCase().includes(error)
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Checks if an error is critical (authentication, authorization, permissions)
95
+ * @param errorMessage - Error message to check
96
+ * @returns True if error is critical
97
+ */
98
+ export function isCriticalError(errorMessage: string): boolean {
99
+ const criticalErrors = [
100
+ "authentication",
101
+ "authorization",
102
+ "permission",
103
+ "forbidden",
104
+ "401",
105
+ "403"
106
+ ];
107
+ return criticalErrors.some(error =>
108
+ errorMessage.toLowerCase().includes(error)
109
+ );
110
+ }
@@ -5,6 +5,7 @@ import fs from "node:fs";
5
5
  import chalk from "chalk";
6
6
  import pLimit from "p-limit";
7
7
  import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
8
+ import { MessageFormatter } from "./messageFormatter.js";
8
9
 
9
10
  /**
10
11
  * Validates and filters events array for Appwrite functions
@@ -64,7 +65,7 @@ export class FunctionManager {
64
65
  } = options;
65
66
 
66
67
  if (verbose) {
67
- console.log(chalk.blue(`🔍 Searching for function: ${functionName}`));
68
+ MessageFormatter.info(`Searching for function: ${functionName}`, { prefix: "Functions" });
68
69
  }
69
70
 
70
71
  // Normalize function name for comparison
@@ -76,7 +77,7 @@ export class FunctionManager {
76
77
  for (const path of standardPaths) {
77
78
  if (await this.isValidFunctionDirectory(path)) {
78
79
  if (verbose) {
79
- console.log(chalk.green(`✓ Found function at standard location: ${path}`));
80
+ MessageFormatter.success(`Found function at standard location: ${path}`, { prefix: "Functions" });
80
81
  }
81
82
  return path;
82
83
  }
@@ -92,7 +93,7 @@ export class FunctionManager {
92
93
  );
93
94
  if (foundPath) {
94
95
  if (verbose) {
95
- console.log(chalk.green(`✓ Found function via fuzzy search: ${foundPath}`));
96
+ MessageFormatter.success(`Found function via fuzzy search: ${foundPath}`, { prefix: "Functions" });
96
97
  }
97
98
  return foundPath;
98
99
  }
@@ -100,7 +101,7 @@ export class FunctionManager {
100
101
  }
101
102
 
102
103
  if (verbose) {
103
- console.log(chalk.yellow(`⚠ Function directory not found: ${functionName}`));
104
+ MessageFormatter.warning(`Function directory not found: ${functionName}`, { prefix: "Functions" });
104
105
  }
105
106
  return null;
106
107
  }
@@ -175,7 +176,7 @@ export class FunctionManager {
175
176
  }
176
177
  } catch (error) {
177
178
  if (verbose) {
178
- console.log(chalk.gray(`Skipping inaccessible directory: ${searchPath}`));
179
+ MessageFormatter.debug(`Skipping inaccessible directory: ${searchPath}`, undefined, { prefix: "Functions" });
179
180
  }
180
181
  }
181
182
 
@@ -248,9 +249,9 @@ export class FunctionManager {
248
249
 
249
250
  return await functionLimit(async () => {
250
251
  if (verbose) {
251
- console.log(chalk.blue(`🚀 Deploying function: ${functionConfig.name}`));
252
- console.log(chalk.gray(` Path: ${functionPath}`));
253
- console.log(chalk.gray(` Entrypoint: ${entrypoint}`));
252
+ MessageFormatter.processing(`Deploying function: ${functionConfig.name}`, { prefix: "Functions" });
253
+ MessageFormatter.debug(`Path: ${functionPath}`, undefined, { prefix: "Functions" });
254
+ MessageFormatter.debug(`Entrypoint: ${entrypoint}`, undefined, { prefix: "Functions" });
254
255
  }
255
256
 
256
257
  // Validate function directory
@@ -265,7 +266,7 @@ export class FunctionManager {
265
266
  functionExists = true;
266
267
  } catch (error) {
267
268
  if (verbose) {
268
- console.log(chalk.yellow(`Function ${functionConfig.$id} does not exist, creating...`));
269
+ MessageFormatter.info(`Function ${functionConfig.$id} does not exist, creating...`, { prefix: "Functions" });
269
270
  }
270
271
  }
271
272
 
@@ -289,7 +290,7 @@ export class FunctionManager {
289
290
  );
290
291
 
291
292
  if (verbose) {
292
- console.log(chalk.green(`✅ Function ${functionConfig.name} deployed successfully`));
293
+ MessageFormatter.success(`Function ${functionConfig.name} deployed successfully`, { prefix: "Functions" });
293
294
  }
294
295
 
295
296
  return deployment;
@@ -303,7 +304,7 @@ export class FunctionManager {
303
304
  const { verbose = false } = options;
304
305
 
305
306
  if (verbose) {
306
- console.log(chalk.blue(`Creating function: ${functionConfig.name}`));
307
+ MessageFormatter.processing(`Creating function: ${functionConfig.name}`, { prefix: "Functions" });
307
308
  }
308
309
 
309
310
  return await tryAwaitWithRetry(async () => {
@@ -336,7 +337,7 @@ export class FunctionManager {
336
337
  const { verbose = false } = options;
337
338
 
338
339
  if (verbose) {
339
- console.log(chalk.blue(`Updating function: ${functionConfig.name}`));
340
+ MessageFormatter.processing(`Updating function: ${functionConfig.name}`, { prefix: "Functions" });
340
341
  }
341
342
 
342
343
  return await tryAwaitWithRetry(async () => {
@@ -373,14 +374,14 @@ export class FunctionManager {
373
374
  const { platform } = await import("node:os");
374
375
 
375
376
  if (verbose) {
376
- console.log(chalk.blue("Executing pre-deploy commands..."));
377
+ MessageFormatter.processing("Executing pre-deploy commands...", { prefix: "Functions" });
377
378
  }
378
379
 
379
380
  const isWindows = platform() === "win32";
380
381
 
381
382
  for (const command of commands) {
382
383
  if (verbose) {
383
- console.log(chalk.gray(` $ ${command}`));
384
+ MessageFormatter.debug(`$ ${command}`, undefined, { prefix: "Functions" });
384
385
  }
385
386
 
386
387
  try {
@@ -391,13 +392,13 @@ export class FunctionManager {
391
392
  windowsHide: true,
392
393
  });
393
394
  } catch (error) {
394
- console.error(chalk.red(`Failed to execute command: ${command}`));
395
+ MessageFormatter.error(`Failed to execute command: ${command}`, error as Error, { prefix: "Functions" });
395
396
  throw error;
396
397
  }
397
398
  }
398
399
 
399
400
  if (verbose) {
400
- console.log(chalk.green("Pre-deploy commands completed"));
401
+ MessageFormatter.success("Pre-deploy commands completed", { prefix: "Functions" });
401
402
  }
402
403
  }
403
404
 
@@ -415,7 +416,7 @@ export class FunctionManager {
415
416
 
416
417
  try {
417
418
  if (verbose) {
418
- console.log(chalk.blue("Creating deployment archive..."));
419
+ MessageFormatter.processing("Creating deployment archive...", { prefix: "Functions" });
419
420
  }
420
421
 
421
422
  // Create tarball
@@ -431,9 +432,9 @@ export class FunctionManager {
431
432
  relativePath.includes(`/${pattern.toLowerCase()}`) ||
432
433
  relativePath.includes(`\\${pattern.toLowerCase()}`)
433
434
  );
434
-
435
+
435
436
  if (shouldIgnore && verbose) {
436
- console.log(chalk.gray(` Ignoring: ${path}`));
437
+ MessageFormatter.debug(`Ignoring: ${path}`, undefined, { prefix: "Functions" });
437
438
  }
438
439
 
439
440
  return !shouldIgnore;
@@ -450,7 +451,7 @@ export class FunctionManager {
450
451
  );
451
452
 
452
453
  if (verbose) {
453
- console.log(chalk.blue("Uploading deployment..."));
454
+ MessageFormatter.processing("Uploading deployment...", { prefix: "Functions" });
454
455
  }
455
456
 
456
457
  const deployment = await tryAwaitWithRetry(async () => {