appwrite-utils-cli 1.5.1 → 1.6.0

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 +478 -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 +186 -1171
  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 +276 -1591
  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,463 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { jest } from '@jest/globals';
4
+ import { TestUtils } from '../testUtils';
5
+ import { AdapterFactory } from '../../src/adapters/AdapterFactory';
6
+ import { MessageFormatter } from '../../src/shared/messageFormatter';
7
+
8
+ // Mock dependencies
9
+ jest.mock('../../src/shared/messageFormatter');
10
+ jest.mock('../../src/adapters/AdapterFactory');
11
+ jest.mock('../../src/utils/versionDetection');
12
+
13
+ // Import the actual modules we're testing
14
+ import { loadConfig } from '../../src/utils/loadConfigs';
15
+
16
+ describe('Sync Operations Integration Tests', () => {
17
+ let testDir: string;
18
+ let mockAdapter: any;
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+
23
+ // Create mock adapter with all required methods
24
+ mockAdapter = {
25
+ syncFromAppwrite: jest.fn(),
26
+ syncToAppwrite: jest.fn(),
27
+ validateConfiguration: jest.fn(),
28
+ generateYamlConfig: jest.fn(),
29
+ getDatabases: jest.fn(),
30
+ getCollections: jest.fn(),
31
+ createCollection: jest.fn(),
32
+ updateCollection: jest.fn(),
33
+ deleteCollection: jest.fn(),
34
+ client: TestUtils.createMockAppwriteResponses(),
35
+ };
36
+
37
+ (AdapterFactory.createAdapter as jest.Mock).mockResolvedValue(mockAdapter);
38
+ });
39
+
40
+ afterEach(() => {
41
+ TestUtils.cleanup();
42
+ });
43
+
44
+ describe('Sync from Appwrite to Local Configuration', () => {
45
+ it('should sync collections to collections/ directory for database API', async () => {
46
+ testDir = TestUtils.createTestProject({ hasCollections: true });
47
+
48
+ // Mock Appwrite response for collections
49
+ mockAdapter.getDatabases.mockResolvedValue([
50
+ {
51
+ $id: 'test-db-id',
52
+ name: 'test-database',
53
+ enabled: true,
54
+ }
55
+ ]);
56
+
57
+ mockAdapter.getCollections.mockResolvedValue([
58
+ {
59
+ $id: 'synced-collection',
60
+ name: 'SyncedCollection',
61
+ documentSecurity: false,
62
+ enabled: true,
63
+ $permissions: [],
64
+ attributes: [
65
+ {
66
+ key: 'title',
67
+ type: 'string',
68
+ size: 255,
69
+ required: true,
70
+ array: false,
71
+ }
72
+ ],
73
+ indexes: [],
74
+ $createdAt: new Date().toISOString(),
75
+ $updatedAt: new Date().toISOString(),
76
+ }
77
+ ]);
78
+
79
+ mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
80
+ const collectionsDir = path.join(outputDir, 'collections');
81
+ fs.mkdirSync(collectionsDir, { recursive: true });
82
+
83
+ collections.forEach((collection: any) => {
84
+ const yamlContent = `
85
+ name: ${collection.name}
86
+ id: ${collection.$id}
87
+ documentSecurity: ${collection.documentSecurity}
88
+ enabled: ${collection.enabled}
89
+ permissions: []
90
+ attributes:
91
+ - key: ${collection.attributes[0].key}
92
+ type: ${collection.attributes[0].type}
93
+ size: ${collection.attributes[0].size}
94
+ required: ${collection.attributes[0].required}
95
+ indexes: []
96
+ `;
97
+ fs.writeFileSync(
98
+ path.join(collectionsDir, `${collection.name}.yaml`),
99
+ yamlContent
100
+ );
101
+ });
102
+ });
103
+
104
+ await mockAdapter.syncFromAppwrite(testDir);
105
+
106
+ // Verify collections were written to collections/ directory
107
+ const collectionsDir = path.join(testDir, 'collections');
108
+ expect(fs.existsSync(collectionsDir)).toBe(true);
109
+
110
+ const collectionFiles = fs.readdirSync(collectionsDir);
111
+ expect(collectionFiles).toContain('SyncedCollection.yaml');
112
+
113
+ const collectionContent = fs.readFileSync(
114
+ path.join(collectionsDir, 'SyncedCollection.yaml'),
115
+ 'utf8'
116
+ );
117
+ expect(collectionContent).toContain('name: SyncedCollection');
118
+ });
119
+
120
+ it('should sync tables to tables/ directory for tablesdb API', async () => {
121
+ testDir = TestUtils.createTestProject({ hasTables: true });
122
+
123
+ // Mock TablesDB adapter
124
+ mockAdapter.getDatabases.mockResolvedValue([
125
+ {
126
+ $id: 'test-db-id',
127
+ name: 'test-database',
128
+ enabled: true,
129
+ }
130
+ ]);
131
+
132
+ mockAdapter.getCollections.mockResolvedValue([
133
+ {
134
+ tableId: 'synced-table',
135
+ name: 'SyncedTable',
136
+ databaseId: 'test-db-id',
137
+ documentSecurity: false,
138
+ enabled: true,
139
+ $permissions: [],
140
+ attributes: [
141
+ {
142
+ key: 'content',
143
+ type: 'string',
144
+ size: 1000,
145
+ required: true,
146
+ array: false,
147
+ }
148
+ ],
149
+ indexes: [],
150
+ $createdAt: new Date().toISOString(),
151
+ $updatedAt: new Date().toISOString(),
152
+ }
153
+ ]);
154
+
155
+ mockAdapter.generateYamlConfig.mockImplementation((tables, outputDir) => {
156
+ const tablesDir = path.join(outputDir, 'tables');
157
+ fs.mkdirSync(tablesDir, { recursive: true });
158
+
159
+ tables.forEach((table: any) => {
160
+ const yamlContent = `
161
+ name: ${table.name}
162
+ tableId: ${table.tableId}
163
+ databaseId: ${table.databaseId}
164
+ documentSecurity: ${table.documentSecurity}
165
+ enabled: ${table.enabled}
166
+ permissions: []
167
+ attributes:
168
+ - key: ${table.attributes[0].key}
169
+ type: ${table.attributes[0].type}
170
+ size: ${table.attributes[0].size}
171
+ required: ${table.attributes[0].required}
172
+ indexes: []
173
+ `;
174
+ fs.writeFileSync(
175
+ path.join(tablesDir, `${table.name}.yaml`),
176
+ yamlContent
177
+ );
178
+ });
179
+ });
180
+
181
+ await mockAdapter.syncFromAppwrite(testDir);
182
+
183
+ // Verify tables were written to tables/ directory
184
+ const tablesDir = path.join(testDir, 'tables');
185
+ expect(fs.existsSync(tablesDir)).toBe(true);
186
+
187
+ const tableFiles = fs.readdirSync(tablesDir);
188
+ expect(tableFiles).toContain('SyncedTable.yaml');
189
+
190
+ const tableContent = fs.readFileSync(
191
+ path.join(tablesDir, 'SyncedTable.yaml'),
192
+ 'utf8'
193
+ );
194
+ expect(tableContent).toContain('name: SyncedTable');
195
+ expect(tableContent).toContain('tableId: synced-table');
196
+ expect(tableContent).toContain('databaseId: test-db-id');
197
+ });
198
+
199
+ it('should preserve existing configurations during sync', async () => {
200
+ testDir = TestUtils.createTestProject({
201
+ hasCollections: true,
202
+ hasTables: true,
203
+ });
204
+
205
+ // Add existing configurations
206
+ const existingCollectionContent = fs.readFileSync(
207
+ path.join(testDir, 'collections', 'TestCollection.ts'),
208
+ 'utf8'
209
+ );
210
+
211
+ mockAdapter.getDatabases.mockResolvedValue([]);
212
+ mockAdapter.getCollections.mockResolvedValue([]);
213
+ mockAdapter.generateYamlConfig.mockImplementation(() => {
214
+ // Don't overwrite existing files
215
+ });
216
+
217
+ await mockAdapter.syncFromAppwrite(testDir);
218
+
219
+ // Verify existing files weren't overwritten
220
+ const currentContent = fs.readFileSync(
221
+ path.join(testDir, 'collections', 'TestCollection.ts'),
222
+ 'utf8'
223
+ );
224
+ expect(currentContent).toBe(existingCollectionContent);
225
+ });
226
+ });
227
+
228
+ describe('Database Configuration Sync', () => {
229
+ it('should update main config.yaml with database information', async () => {
230
+ testDir = TestUtils.createTestProject({ useYaml: true });
231
+
232
+ mockAdapter.getDatabases.mockResolvedValue([
233
+ {
234
+ $id: 'db1',
235
+ name: 'Database One',
236
+ enabled: true,
237
+ },
238
+ {
239
+ $id: 'db2',
240
+ name: 'Database Two',
241
+ enabled: false,
242
+ }
243
+ ]);
244
+
245
+ mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
246
+ // Update the main config file
247
+ const configPath = path.join(outputDir, '.appwrite', 'config.yaml');
248
+ const currentConfig = fs.readFileSync(configPath, 'utf8');
249
+ const updatedConfig = currentConfig + `
250
+ # Synced databases
251
+ syncedDatabases:
252
+ - $id: db1
253
+ name: Database One
254
+ enabled: true
255
+ - $id: db2
256
+ name: Database Two
257
+ enabled: false
258
+ `;
259
+ fs.writeFileSync(configPath, updatedConfig);
260
+ });
261
+
262
+ await mockAdapter.syncFromAppwrite(testDir);
263
+
264
+ const configContent = fs.readFileSync(
265
+ path.join(testDir, '.appwrite', 'config.yaml'),
266
+ 'utf8'
267
+ );
268
+ expect(configContent).toContain('syncedDatabases:');
269
+ expect(configContent).toContain('Database One');
270
+ expect(configContent).toContain('Database Two');
271
+ });
272
+ });
273
+
274
+ describe('Version-Aware Sync Operations', () => {
275
+ it('should use appropriate directory based on detected API version', async () => {
276
+ testDir = TestUtils.createTestProject();
277
+
278
+ // Mock version detection for old version (should use collections/)
279
+ const oldVersionAdapter = { ...mockAdapter };
280
+ oldVersionAdapter.getCollections.mockResolvedValue([
281
+ TestUtils.createTestCollection({ name: 'OldVersionCollection' })
282
+ ]);
283
+
284
+ await oldVersionAdapter.syncFromAppwrite(testDir);
285
+
286
+ // For newer versions, should use tables/
287
+ const newVersionAdapter = { ...mockAdapter };
288
+ newVersionAdapter.getCollections.mockResolvedValue([
289
+ TestUtils.createTestTable({ name: 'NewVersionTable' })
290
+ ]);
291
+
292
+ await newVersionAdapter.syncFromAppwrite(testDir);
293
+
294
+ // Verify appropriate handling based on version
295
+ expect(mockAdapter.syncFromAppwrite).toHaveBeenCalledTimes(2);
296
+ });
297
+
298
+ it('should handle mixed API environments gracefully', async () => {
299
+ testDir = TestUtils.createTestProject({
300
+ hasCollections: true,
301
+ hasTables: true,
302
+ });
303
+
304
+ // Mock mixed response
305
+ mockAdapter.getCollections.mockResolvedValue([
306
+ TestUtils.createTestCollection({ name: 'MixedCollection' }),
307
+ TestUtils.createTestTable({ name: 'MixedTable' }),
308
+ ]);
309
+
310
+ mockAdapter.generateYamlConfig.mockImplementation((items, outputDir) => {
311
+ items.forEach((item: any) => {
312
+ const isTable = item._isFromTablesDir || item.tableId;
313
+ const dir = isTable ? 'tables' : 'collections';
314
+ const dirPath = path.join(outputDir, dir);
315
+ fs.mkdirSync(dirPath, { recursive: true });
316
+
317
+ const yamlContent = `name: ${item.name}\n`;
318
+ fs.writeFileSync(
319
+ path.join(dirPath, `${item.name}.yaml`),
320
+ yamlContent
321
+ );
322
+ });
323
+ });
324
+
325
+ await mockAdapter.syncFromAppwrite(testDir);
326
+
327
+ // Should handle both types appropriately
328
+ expect(mockAdapter.generateYamlConfig).toHaveBeenCalled();
329
+ });
330
+ });
331
+
332
+ describe('Error Handling and Recovery', () => {
333
+ it('should handle network errors during sync gracefully', async () => {
334
+ testDir = TestUtils.createTestProject();
335
+
336
+ mockAdapter.getDatabases.mockRejectedValue(new Error('Network timeout'));
337
+
338
+ await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow('Network timeout');
339
+
340
+ // Verify that partial configurations are not left in inconsistent state
341
+ const collectionsDir = path.join(testDir, 'collections');
342
+ const tablesDir = path.join(testDir, 'tables');
343
+
344
+ // Should not create partial directories on failure
345
+ if (fs.existsSync(collectionsDir)) {
346
+ const files = fs.readdirSync(collectionsDir);
347
+ expect(files.length).toBe(0); // No partial files
348
+ }
349
+ });
350
+
351
+ it('should validate synced configurations before writing', async () => {
352
+ testDir = TestUtils.createTestProject();
353
+
354
+ // Mock invalid configuration from Appwrite
355
+ mockAdapter.getCollections.mockResolvedValue([
356
+ {
357
+ // Missing required fields
358
+ name: '',
359
+ $id: '',
360
+ attributes: [],
361
+ }
362
+ ]);
363
+
364
+ mockAdapter.validateConfiguration.mockReturnValue({
365
+ isValid: false,
366
+ errors: ['Collection name is required', 'Collection ID is required'],
367
+ warnings: [],
368
+ });
369
+
370
+ await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow();
371
+
372
+ expect(mockAdapter.validateConfiguration).toHaveBeenCalled();
373
+ });
374
+
375
+ it('should provide rollback capability on sync failure', async () => {
376
+ testDir = TestUtils.createTestProject({ hasCollections: true });
377
+
378
+ // Backup existing state
379
+ const originalFiles = fs.readdirSync(path.join(testDir, 'collections'));
380
+
381
+ mockAdapter.getCollections.mockResolvedValue([
382
+ TestUtils.createTestCollection({ name: 'NewCollection' })
383
+ ]);
384
+
385
+ // Simulate failure during write
386
+ mockAdapter.generateYamlConfig.mockImplementation(() => {
387
+ throw new Error('Write permission denied');
388
+ });
389
+
390
+ await expect(mockAdapter.syncFromAppwrite(testDir)).rejects.toThrow();
391
+
392
+ // Verify original files are preserved
393
+ const currentFiles = fs.readdirSync(path.join(testDir, 'collections'));
394
+ expect(currentFiles).toEqual(originalFiles);
395
+ });
396
+ });
397
+
398
+ describe('Performance Optimization', () => {
399
+ it('should handle large numbers of collections efficiently', async () => {
400
+ testDir = TestUtils.createTestProject();
401
+
402
+ // Create large number of collections
403
+ const largeCollectionSet = Array.from({ length: 1000 }, (_, i) =>
404
+ TestUtils.createTestCollection({
405
+ name: `Collection${i}`,
406
+ $id: `collection-${i}`,
407
+ })
408
+ );
409
+
410
+ mockAdapter.getCollections.mockResolvedValue(largeCollectionSet);
411
+ mockAdapter.generateYamlConfig.mockImplementation((collections, outputDir) => {
412
+ // Simulate efficient batch writing
413
+ const collectionsDir = path.join(outputDir, 'collections');
414
+ fs.mkdirSync(collectionsDir, { recursive: true });
415
+
416
+ // Write files in batches to avoid memory issues
417
+ const batchSize = 100;
418
+ for (let i = 0; i < collections.length; i += batchSize) {
419
+ const batch = collections.slice(i, i + batchSize);
420
+ batch.forEach((collection: any) => {
421
+ fs.writeFileSync(
422
+ path.join(collectionsDir, `${collection.name}.yaml`),
423
+ `name: ${collection.name}\n`
424
+ );
425
+ });
426
+ }
427
+ });
428
+
429
+ const startTime = Date.now();
430
+ await mockAdapter.syncFromAppwrite(testDir);
431
+ const syncTime = Date.now() - startTime;
432
+
433
+ expect(syncTime).toBeLessThan(10000); // Should complete within 10 seconds
434
+
435
+ const collectionFiles = fs.readdirSync(path.join(testDir, 'collections'));
436
+ expect(collectionFiles.length).toBe(1000);
437
+ });
438
+
439
+ it('should use incremental sync when possible', async () => {
440
+ testDir = TestUtils.createTestProject({ hasCollections: true });
441
+
442
+ // Mock last sync timestamp
443
+ const lastSyncFile = path.join(testDir, '.appwrite', '.last-sync');
444
+ fs.writeFileSync(lastSyncFile, new Date().toISOString());
445
+
446
+ mockAdapter.getCollections.mockResolvedValue([
447
+ TestUtils.createTestCollection({
448
+ name: 'UpdatedCollection',
449
+ $updatedAt: new Date().toISOString(),
450
+ })
451
+ ]);
452
+
453
+ await mockAdapter.syncFromAppwrite(testDir);
454
+
455
+ // Should only sync updated items
456
+ expect(mockAdapter.getCollections).toHaveBeenCalledWith(
457
+ expect.objectContaining({
458
+ modifiedSince: expect.any(String),
459
+ })
460
+ );
461
+ });
462
+ });
463
+ });
@@ -0,0 +1,25 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ roots: ['<rootDir>/src', '<rootDir>/tests'],
6
+ testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
7
+ transform: {
8
+ '^.+\\.ts$': 'ts-jest',
9
+ },
10
+ collectCoverageFrom: [
11
+ 'src/**/*.ts',
12
+ '!src/**/*.d.ts',
13
+ '!src/main.ts', // Exclude entry point
14
+ '!src/functions/templates/**', // Exclude template files
15
+ ],
16
+ coverageDirectory: 'coverage',
17
+ coverageReporters: ['text', 'lcov', 'html'],
18
+ setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
19
+ testTimeout: 30000,
20
+ moduleFileExtensions: ['ts', 'js', 'json'],
21
+ testPathIgnorePatterns: ['/node_modules/', '/dist/'],
22
+ clearMocks: true,
23
+ restoreMocks: true,
24
+ verbose: true,
25
+ };