appwrite-utils-cli 1.5.2 → 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 +181 -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 +278 -1596
  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,546 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { jest } from '@jest/globals';
4
+ import { TestUtils } from '../testUtils';
5
+
6
+ // Mock config migration utilities
7
+ jest.mock('../../src/utils/configMigration', () => ({
8
+ migrateToTablesDir: jest.fn(),
9
+ migrateToCollectionsDir: jest.fn(),
10
+ detectMigrationNeeds: jest.fn(),
11
+ createBackup: jest.fn(),
12
+ validateMigration: jest.fn(),
13
+ convertCollectionToTable: jest.fn(),
14
+ convertTableToCollection: jest.fn(),
15
+ }));
16
+
17
+ import {
18
+ migrateToTablesDir,
19
+ migrateToCollectionsDir,
20
+ detectMigrationNeeds,
21
+ createBackup,
22
+ validateMigration,
23
+ convertCollectionToTable,
24
+ convertTableToCollection,
25
+ } from '../../src/utils/configMigration';
26
+
27
+ describe('Configuration Migration Tests', () => {
28
+ let testDir: string;
29
+
30
+ beforeEach(() => {
31
+ jest.clearAllMocks();
32
+ });
33
+
34
+ afterEach(() => {
35
+ TestUtils.cleanup();
36
+ });
37
+
38
+ describe('Migration Detection', () => {
39
+ it('should detect need to migrate from collections to dual schema', async () => {
40
+ testDir = TestUtils.createTestProject({ hasCollections: true });
41
+
42
+ (detectMigrationNeeds as jest.Mock).mockReturnValue({
43
+ needsMigration: true,
44
+ migrationType: 'to-dual-schema',
45
+ reason: 'Only collections/ directory found, recommend adding tables/ support',
46
+ sourceDir: 'collections',
47
+ targetDirs: ['collections', 'tables'],
48
+ });
49
+
50
+ const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
51
+
52
+ expect(migrationNeeds.needsMigration).toBe(true);
53
+ expect(migrationNeeds.migrationType).toBe('to-dual-schema');
54
+ expect(detectMigrationNeeds).toHaveBeenCalledWith(testDir);
55
+ });
56
+
57
+ it('should detect need to migrate from legacy single directory to version-aware structure', async () => {
58
+ testDir = TestUtils.createTempDir();
59
+
60
+ // Create legacy structure without .appwrite directory
61
+ const config = TestUtils.createTestAppwriteConfig();
62
+ fs.writeFileSync(
63
+ path.join(testDir, 'appwriteConfig.ts'),
64
+ `export default ${JSON.stringify(config, null, 2)};`
65
+ );
66
+
67
+ const collectionsDir = path.join(testDir, 'collections');
68
+ fs.mkdirSync(collectionsDir);
69
+
70
+ (detectMigrationNeeds as jest.Mock).mockReturnValue({
71
+ needsMigration: true,
72
+ migrationType: 'to-version-aware',
73
+ reason: 'Config is outside .appwrite directory',
74
+ sourceDir: testDir,
75
+ targetDir: path.join(testDir, '.appwrite'),
76
+ });
77
+
78
+ const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
79
+
80
+ expect(migrationNeeds.needsMigration).toBe(true);
81
+ expect(migrationNeeds.migrationType).toBe('to-version-aware');
82
+ });
83
+
84
+ it('should detect no migration needed for current dual schema structure', async () => {
85
+ testDir = TestUtils.createTestProject({
86
+ hasCollections: true,
87
+ hasTables: true,
88
+ useYaml: true,
89
+ });
90
+
91
+ (detectMigrationNeeds as jest.Mock).mockReturnValue({
92
+ needsMigration: false,
93
+ migrationType: 'none',
94
+ reason: 'Configuration is already using dual schema structure',
95
+ });
96
+
97
+ const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
98
+
99
+ expect(migrationNeeds.needsMigration).toBe(false);
100
+ expect(migrationNeeds.migrationType).toBe('none');
101
+ });
102
+ });
103
+
104
+ describe('Migration to Tables Directory', () => {
105
+ it('should migrate collections to tables directory for TablesDB API', async () => {
106
+ testDir = TestUtils.createTestProject({ hasCollections: true });
107
+
108
+ const originalCollection = TestUtils.createTestCollection();
109
+
110
+ (migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
111
+ const tablesDir = path.join(sourceDir, 'tables');
112
+ fs.mkdirSync(tablesDir, { recursive: true });
113
+
114
+ // Convert collection to table format
115
+ const table = {
116
+ ...originalCollection,
117
+ tableId: originalCollection.$id,
118
+ databaseId: 'test-db-id',
119
+ _isFromTablesDir: true,
120
+ };
121
+
122
+ fs.writeFileSync(
123
+ path.join(tablesDir, 'TestCollection.yaml'),
124
+ `name: ${table.name}\ntableId: ${table.tableId}\ndatabaseId: ${table.databaseId}\n`
125
+ );
126
+
127
+ return {
128
+ success: true,
129
+ migratedFiles: ['TestCollection.ts -> TestCollection.yaml'],
130
+ targetDir: tablesDir,
131
+ };
132
+ });
133
+
134
+ const result = (migrateToTablesDir as jest.Mock)(testDir, {
135
+ preserveOriginal: true,
136
+ convertToYaml: true,
137
+ });
138
+
139
+ expect(result.success).toBe(true);
140
+ expect(result.migratedFiles).toHaveLength(1);
141
+
142
+ const tablesDir = path.join(testDir, 'tables');
143
+ expect(fs.existsSync(tablesDir)).toBe(true);
144
+ expect(fs.existsSync(path.join(tablesDir, 'TestCollection.yaml'))).toBe(true);
145
+ });
146
+
147
+ it('should preserve original collections when requested', async () => {
148
+ testDir = TestUtils.createTestProject({ hasCollections: true });
149
+
150
+ const originalFile = path.join(testDir, 'collections', 'TestCollection.ts');
151
+ const originalContent = fs.readFileSync(originalFile, 'utf8');
152
+
153
+ (migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
154
+ if (options.preserveOriginal) {
155
+ // Don't remove original files
156
+ return {
157
+ success: true,
158
+ migratedFiles: ['TestCollection.ts'],
159
+ preservedFiles: ['TestCollection.ts'],
160
+ };
161
+ }
162
+ });
163
+
164
+ const result = (migrateToTablesDir as jest.Mock)(testDir, {
165
+ preserveOriginal: true,
166
+ });
167
+
168
+ expect(result.preservedFiles).toContain('TestCollection.ts');
169
+ expect(fs.existsSync(originalFile)).toBe(true);
170
+
171
+ const currentContent = fs.readFileSync(originalFile, 'utf8');
172
+ expect(currentContent).toBe(originalContent);
173
+ });
174
+
175
+ it('should handle collection to table conversion with proper field mapping', async () => {
176
+ const collection = TestUtils.createTestCollection({
177
+ name: 'UserProfiles',
178
+ $id: 'user-profiles',
179
+ });
180
+
181
+ (convertCollectionToTable as jest.Mock).mockReturnValue({
182
+ name: 'UserProfiles',
183
+ tableId: 'user-profiles',
184
+ databaseId: 'main-db',
185
+ documentSecurity: collection.documentSecurity,
186
+ enabled: collection.enabled,
187
+ $permissions: collection.$permissions,
188
+ attributes: collection.attributes,
189
+ indexes: collection.indexes,
190
+ importDefs: collection.importDefs,
191
+ _isFromTablesDir: true,
192
+ });
193
+
194
+ const table = (convertCollectionToTable as jest.Mock)(collection, 'main-db');
195
+
196
+ expect(table.tableId).toBe('user-profiles');
197
+ expect(table.databaseId).toBe('main-db');
198
+ expect(table._isFromTablesDir).toBe(true);
199
+ expect(table.attributes).toEqual(collection.attributes);
200
+ });
201
+ });
202
+
203
+ describe('Migration to Collections Directory', () => {
204
+ it('should migrate tables back to collections directory for Database API', async () => {
205
+ testDir = TestUtils.createTestProject({ hasTables: true });
206
+
207
+ (migrateToCollectionsDir as jest.Mock).mockImplementation((sourceDir, options) => {
208
+ const collectionsDir = path.join(sourceDir, 'collections');
209
+ fs.mkdirSync(collectionsDir, { recursive: true });
210
+
211
+ const collection = TestUtils.createTestCollection({
212
+ name: 'TestTable',
213
+ $id: 'test-table',
214
+ });
215
+
216
+ fs.writeFileSync(
217
+ path.join(collectionsDir, 'TestTable.ts'),
218
+ `export default ${JSON.stringify(collection, null, 2)};`
219
+ );
220
+
221
+ return {
222
+ success: true,
223
+ migratedFiles: ['TestTable.yaml -> TestTable.ts'],
224
+ targetDir: collectionsDir,
225
+ };
226
+ });
227
+
228
+ const result = (migrateToCollectionsDir as jest.Mock)(testDir, {
229
+ convertToTypeScript: true,
230
+ });
231
+
232
+ expect(result.success).toBe(true);
233
+ expect(result.migratedFiles).toHaveLength(1);
234
+
235
+ const collectionsDir = path.join(testDir, 'collections');
236
+ expect(fs.existsSync(collectionsDir)).toBe(true);
237
+ expect(fs.existsSync(path.join(collectionsDir, 'TestTable.ts'))).toBe(true);
238
+ });
239
+
240
+ it('should handle table to collection conversion with field mapping', async () => {
241
+ const table = TestUtils.createTestTable({
242
+ name: 'UserData',
243
+ tableId: 'user-data',
244
+ databaseId: 'main-db',
245
+ });
246
+
247
+ (convertTableToCollection as jest.Mock).mockReturnValue({
248
+ name: 'UserData',
249
+ $id: 'user-data',
250
+ documentSecurity: table.documentSecurity,
251
+ enabled: table.enabled,
252
+ $permissions: table.$permissions,
253
+ attributes: table.attributes,
254
+ indexes: table.indexes,
255
+ importDefs: table.importDefs,
256
+ });
257
+
258
+ const collection = (convertTableToCollection as jest.Mock)(table);
259
+
260
+ expect(collection.$id).toBe('user-data');
261
+ expect(collection.name).toBe('UserData');
262
+ expect(collection._isFromTablesDir).toBeUndefined();
263
+ expect(collection.attributes).toEqual(table.attributes);
264
+ });
265
+ });
266
+
267
+ describe('Migration Validation', () => {
268
+ it('should validate migration before execution', async () => {
269
+ testDir = TestUtils.createTestProject({ hasCollections: true });
270
+
271
+ (validateMigration as jest.Mock).mockReturnValue({
272
+ isValid: true,
273
+ errors: [],
274
+ warnings: [],
275
+ canProceed: true,
276
+ estimatedTime: '2 minutes',
277
+ affectedFiles: ['TestCollection.ts'],
278
+ });
279
+
280
+ const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
281
+
282
+ expect(validation.isValid).toBe(true);
283
+ expect(validation.canProceed).toBe(true);
284
+ expect(validation.affectedFiles).toHaveLength(1);
285
+ });
286
+
287
+ it('should detect validation errors that prevent migration', async () => {
288
+ testDir = TestUtils.createTestProject({ hasCollections: true });
289
+
290
+ (validateMigration as jest.Mock).mockReturnValue({
291
+ isValid: false,
292
+ errors: [
293
+ 'Collection "TestCollection" has invalid attribute types for TablesDB',
294
+ 'Missing required databaseId for target tables'
295
+ ],
296
+ warnings: ['Migration will take significant time'],
297
+ canProceed: false,
298
+ reasons: ['Critical validation errors found'],
299
+ });
300
+
301
+ const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
302
+
303
+ expect(validation.isValid).toBe(false);
304
+ expect(validation.canProceed).toBe(false);
305
+ expect(validation.errors).toHaveLength(2);
306
+ });
307
+
308
+ it('should provide migration warnings for user review', async () => {
309
+ testDir = TestUtils.createTestProject({ hasCollections: true });
310
+
311
+ (validateMigration as jest.Mock).mockReturnValue({
312
+ isValid: true,
313
+ errors: [],
314
+ warnings: [
315
+ 'Some relationships may need manual adjustment',
316
+ 'Index performance may differ between APIs',
317
+ 'Import definitions will be preserved but may need updates'
318
+ ],
319
+ canProceed: true,
320
+ requiresUserConfirmation: true,
321
+ });
322
+
323
+ const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
324
+
325
+ expect(validation.warnings).toHaveLength(3);
326
+ expect(validation.requiresUserConfirmation).toBe(true);
327
+ });
328
+ });
329
+
330
+ describe('Migration Backup and Recovery', () => {
331
+ it('should create backup before migration', async () => {
332
+ testDir = TestUtils.createTestProject({
333
+ hasCollections: true,
334
+ hasTables: true,
335
+ });
336
+
337
+ const backupDir = path.join(testDir, '.migration-backup');
338
+
339
+ (createBackup as jest.Mock).mockImplementation((sourceDir, backupPath) => {
340
+ fs.mkdirSync(backupPath, { recursive: true });
341
+
342
+ // Copy collections
343
+ const collectionsBackup = path.join(backupPath, 'collections');
344
+ fs.mkdirSync(collectionsBackup);
345
+ fs.writeFileSync(
346
+ path.join(collectionsBackup, 'TestCollection.ts'),
347
+ 'backup content'
348
+ );
349
+
350
+ return {
351
+ success: true,
352
+ backupPath,
353
+ backedUpFiles: ['collections/TestCollection.ts'],
354
+ timestamp: new Date().toISOString(),
355
+ };
356
+ });
357
+
358
+ const backup = (createBackup as jest.Mock)(testDir, backupDir);
359
+
360
+ expect(backup.success).toBe(true);
361
+ expect(backup.backedUpFiles).toContain('collections/TestCollection.ts');
362
+ expect(fs.existsSync(backupDir)).toBe(true);
363
+ });
364
+
365
+ it('should provide rollback capability after failed migration', async () => {
366
+ testDir = TestUtils.createTestProject({ hasCollections: true });
367
+
368
+ const backupDir = path.join(testDir, '.migration-backup');
369
+
370
+ // Simulate backup creation
371
+ (createBackup as jest.Mock).mockReturnValue({
372
+ success: true,
373
+ backupPath: backupDir,
374
+ backedUpFiles: ['collections/TestCollection.ts'],
375
+ });
376
+
377
+ // Simulate migration failure
378
+ (migrateToTablesDir as jest.Mock).mockImplementation(() => {
379
+ throw new Error('Migration failed - disk space insufficient');
380
+ });
381
+
382
+ try {
383
+ (migrateToTablesDir as jest.Mock)(testDir);
384
+ } catch (error) {
385
+ // Rollback should restore from backup
386
+ expect(createBackup).toHaveBeenCalled();
387
+ }
388
+ });
389
+ });
390
+
391
+ describe('Complex Migration Scenarios', () => {
392
+ it('should handle mixed YAML and TypeScript migration', async () => {
393
+ testDir = TestUtils.createTestProject({ hasCollections: true });
394
+
395
+ // Add YAML files
396
+ const collectionsDir = path.join(testDir, 'collections');
397
+ fs.writeFileSync(
398
+ path.join(collectionsDir, 'YamlCollection.yaml'),
399
+ 'name: YamlCollection\nid: yaml-collection\n'
400
+ );
401
+
402
+ (migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
403
+ return {
404
+ success: true,
405
+ migratedFiles: [
406
+ 'TestCollection.ts -> TestCollection.yaml',
407
+ 'YamlCollection.yaml -> YamlCollection.yaml'
408
+ ],
409
+ skippedFiles: [],
410
+ };
411
+ });
412
+
413
+ const result = (migrateToTablesDir as jest.Mock)(testDir, {
414
+ preserveFormat: false, // Convert all to YAML
415
+ });
416
+
417
+ expect(result.migratedFiles).toHaveLength(2);
418
+ expect(result.migratedFiles).toContain('TestCollection.ts -> TestCollection.yaml');
419
+ });
420
+
421
+ it('should handle large-scale migration with progress tracking', async () => {
422
+ testDir = TestUtils.createTempDir();
423
+
424
+ // Create many collections
425
+ const collectionsDir = path.join(testDir, 'collections');
426
+ fs.mkdirSync(collectionsDir);
427
+
428
+ for (let i = 0; i < 100; i++) {
429
+ const collection = TestUtils.createTestCollection({
430
+ name: `Collection${i}`,
431
+ $id: `collection-${i}`,
432
+ });
433
+ fs.writeFileSync(
434
+ path.join(collectionsDir, `Collection${i}.ts`),
435
+ `export default ${JSON.stringify(collection, null, 2)};`
436
+ );
437
+ }
438
+
439
+ (migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
440
+ return {
441
+ success: true,
442
+ migratedFiles: Array.from({ length: 100 }, (_, i) =>
443
+ `Collection${i}.ts -> Collection${i}.yaml`
444
+ ),
445
+ totalFiles: 100,
446
+ completedFiles: 100,
447
+ estimatedTimeRemaining: '0 minutes',
448
+ };
449
+ });
450
+
451
+ const result = (migrateToTablesDir as jest.Mock)(testDir, {
452
+ batchSize: 10,
453
+ progressCallback: jest.fn(),
454
+ });
455
+
456
+ expect(result.migratedFiles).toHaveLength(100);
457
+ expect(result.completedFiles).toBe(100);
458
+ });
459
+
460
+ it('should migrate import definitions and maintain relationships', async () => {
461
+ testDir = TestUtils.createTestProject({ hasCollections: true });
462
+
463
+ const collectionWithImports = TestUtils.createTestCollection({
464
+ name: 'Users',
465
+ importDefs: [
466
+ {
467
+ sourceFile: 'users.json',
468
+ mapping: {
469
+ 'name': 'fullName',
470
+ 'email': 'emailAddress',
471
+ },
472
+ relationships: [
473
+ {
474
+ field: 'profileId',
475
+ targetCollection: 'Profiles',
476
+ type: 'oneToOne',
477
+ }
478
+ ],
479
+ }
480
+ ],
481
+ });
482
+
483
+ (migrateToTablesDir as jest.Mock).mockImplementation(() => {
484
+ return {
485
+ success: true,
486
+ migratedFiles: ['Users.ts -> Users.yaml'],
487
+ preservedImportDefs: true,
488
+ updatedRelationships: ['Users.profileId -> Profiles'],
489
+ };
490
+ });
491
+
492
+ const result = (migrateToTablesDir as jest.Mock)(testDir);
493
+
494
+ expect(result.preservedImportDefs).toBe(true);
495
+ expect(result.updatedRelationships).toHaveLength(1);
496
+ });
497
+ });
498
+
499
+ describe('Error Recovery and Edge Cases', () => {
500
+ it('should handle partial migration failures gracefully', async () => {
501
+ testDir = TestUtils.createTestProject({ hasCollections: true });
502
+
503
+ (migrateToTablesDir as jest.Mock).mockReturnValue({
504
+ success: false,
505
+ errors: [
506
+ 'Failed to convert TestCollection.ts: invalid attribute type',
507
+ ],
508
+ partiallyMigrated: [],
509
+ rollbackRequired: true,
510
+ });
511
+
512
+ const result = (migrateToTablesDir as jest.Mock)(testDir);
513
+
514
+ expect(result.success).toBe(false);
515
+ expect(result.rollbackRequired).toBe(true);
516
+ expect(result.errors).toHaveLength(1);
517
+ });
518
+
519
+ it('should handle file permission issues during migration', async () => {
520
+ testDir = TestUtils.createTestProject({ hasCollections: true });
521
+
522
+ (migrateToTablesDir as jest.Mock).mockImplementation(() => {
523
+ throw new Error('EACCES: permission denied, mkdir \'/readonly/tables\'');
524
+ });
525
+
526
+ expect(() => (migrateToTablesDir as jest.Mock)(testDir)).toThrow('permission denied');
527
+ });
528
+
529
+ it('should validate disk space before migration', async () => {
530
+ testDir = TestUtils.createTestProject({ hasCollections: true });
531
+
532
+ (validateMigration as jest.Mock).mockReturnValue({
533
+ isValid: false,
534
+ errors: ['Insufficient disk space for migration'],
535
+ requiredSpace: '500MB',
536
+ availableSpace: '100MB',
537
+ canProceed: false,
538
+ });
539
+
540
+ const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
541
+
542
+ expect(validation.canProceed).toBe(false);
543
+ expect(validation.errors).toContain('Insufficient disk space for migration');
544
+ });
545
+ });
546
+ });
package/tests/setup.ts ADDED
@@ -0,0 +1,62 @@
1
+ import { jest } from '@jest/globals';
2
+
3
+ // Mock winston logger to avoid log output during tests
4
+ jest.mock('winston', () => ({
5
+ createLogger: jest.fn(() => ({
6
+ info: jest.fn(),
7
+ warn: jest.fn(),
8
+ error: jest.fn(),
9
+ debug: jest.fn(),
10
+ })),
11
+ format: {
12
+ combine: jest.fn(),
13
+ timestamp: jest.fn(),
14
+ errors: jest.fn(),
15
+ json: jest.fn(),
16
+ printf: jest.fn(),
17
+ },
18
+ transports: {
19
+ Console: jest.fn(),
20
+ File: jest.fn(),
21
+ },
22
+ }));
23
+
24
+ // Mock chalk to avoid ANSI codes in tests
25
+ jest.mock('chalk', () => ({
26
+ red: jest.fn((text) => text),
27
+ green: jest.fn((text) => text),
28
+ yellow: jest.fn((text) => text),
29
+ blue: jest.fn((text) => text),
30
+ cyan: jest.fn((text) => text),
31
+ magenta: jest.fn((text) => text),
32
+ white: jest.fn((text) => text),
33
+ gray: jest.fn((text) => text),
34
+ bold: jest.fn((text) => text),
35
+ dim: jest.fn((text) => text),
36
+ italic: jest.fn((text) => text),
37
+ underline: jest.fn((text) => text),
38
+ strikethrough: jest.fn((text) => text),
39
+ bgRed: jest.fn((text) => text),
40
+ bgGreen: jest.fn((text) => text),
41
+ bgYellow: jest.fn((text) => text),
42
+ bgBlue: jest.fn((text) => text),
43
+ bgCyan: jest.fn((text) => text),
44
+ bgMagenta: jest.fn((text) => text),
45
+ bgWhite: jest.fn((text) => text),
46
+ }));
47
+
48
+ // Mock inquirer to avoid interactive prompts during tests
49
+ jest.mock('inquirer', () => ({
50
+ prompt: jest.fn(),
51
+ }));
52
+
53
+ // Set up global test timeout
54
+ jest.setTimeout(30000);
55
+
56
+ // Clean up environment variables before each test
57
+ beforeEach(() => {
58
+ // Reset any environment variables that might affect tests
59
+ delete process.env.APPWRITE_ENDPOINT;
60
+ delete process.env.APPWRITE_PROJECT;
61
+ delete process.env.APPWRITE_KEY;
62
+ });