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,502 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { type CollectionCreate, type Collection } from "appwrite-utils";
4
+ import { register } from "tsx/esm/api";
5
+ import { pathToFileURL } from "node:url";
6
+ import yaml from "js-yaml";
7
+ import { z } from "zod";
8
+ import { MessageFormatter } from "../shared/messageFormatter.js";
9
+ import { shouldIgnoreDirectory } from "./directoryUtils.js";
10
+ import { resolveCollectionsDir, resolveTablesDir } from "./pathResolvers.js";
11
+ import { findYamlConfig } from "../config/yamlConfig.js";
12
+
13
+ /**
14
+ * Recursively searches for TypeScript configuration files (appwriteConfig.ts)
15
+ * @param dir The directory to start the search from
16
+ * @param depth Current search depth for recursion limiting
17
+ * @returns Path to the config file or null if not found
18
+ */
19
+ export const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null => {
20
+ // Limit search depth to prevent infinite recursion
21
+ if (depth > 10) {
22
+ return null;
23
+ }
24
+
25
+ if (shouldIgnoreDirectory(path.basename(dir))) {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
31
+
32
+ // First check current directory for appwriteConfig.ts
33
+ for (const entry of entries) {
34
+ if (entry.isFile() && entry.name === "appwriteConfig.ts") {
35
+ return path.join(dir, entry.name);
36
+ }
37
+ }
38
+
39
+ // Then search subdirectories
40
+ for (const entry of entries) {
41
+ if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
42
+ const result = findAppwriteConfigTS(path.join(dir, entry.name), depth + 1);
43
+ if (result) return result;
44
+ }
45
+ }
46
+ } catch (error) {
47
+ // Ignore directory access errors
48
+ }
49
+
50
+ return null;
51
+ };
52
+
53
+ /**
54
+ * Recursively searches for configuration files starting from the given directory.
55
+ * Priority: 1) YAML configs in .appwrite directories, 2) appwriteConfig.ts files in subdirectories
56
+ * @param dir The directory to start the search from
57
+ * @returns The directory path where the config was found, suitable for passing to loadConfig()
58
+ */
59
+ export const findAppwriteConfig = (dir: string): string | null => {
60
+ // First try to find YAML config (already searches recursively for .appwrite dirs)
61
+ const yamlConfig = findYamlConfig(dir);
62
+ if (yamlConfig) {
63
+ // Return the directory containing the config file
64
+ return path.dirname(yamlConfig);
65
+ }
66
+
67
+ // Fall back to TypeScript config search
68
+ const tsConfigPath = findAppwriteConfigTS(dir);
69
+ if (tsConfigPath) {
70
+ return path.dirname(tsConfigPath);
71
+ }
72
+
73
+ return null;
74
+ };
75
+
76
+ /**
77
+ * Recursively searches for the functions directory
78
+ * @param dir The directory to start searching from
79
+ * @param depth Current search depth for recursion limiting
80
+ * @returns Path to the functions directory or null if not found
81
+ */
82
+ export const findFunctionsDir = (dir: string, depth: number = 0): string | null => {
83
+ // Limit search depth to prevent infinite recursion
84
+ if (depth > 5) {
85
+ return null;
86
+ }
87
+
88
+ if (shouldIgnoreDirectory(path.basename(dir))) {
89
+ return null;
90
+ }
91
+
92
+ try {
93
+ const files = fs.readdirSync(dir, { withFileTypes: true });
94
+
95
+ for (const entry of files) {
96
+ if (!entry.isDirectory() || shouldIgnoreDirectory(entry.name)) {
97
+ continue;
98
+ }
99
+
100
+ if (entry.name === "functions") {
101
+ return path.join(dir, entry.name);
102
+ }
103
+
104
+ const result = findFunctionsDir(path.join(dir, entry.name), depth + 1);
105
+ if (result) return result;
106
+ }
107
+ } catch (error) {
108
+ // Ignore directory access errors
109
+ }
110
+
111
+ return null;
112
+ };
113
+
114
+ // YAML Collection Schema
115
+ const YamlCollectionSchema = z.object({
116
+ name: z.string(),
117
+ id: z.string().optional(),
118
+ documentSecurity: z.boolean().default(false),
119
+ enabled: z.boolean().default(true),
120
+ permissions: z.array(
121
+ z.object({
122
+ permission: z.string(),
123
+ target: z.string()
124
+ })
125
+ ).optional().default([]),
126
+ attributes: z.array(
127
+ z.object({
128
+ key: z.string(),
129
+ type: z.string(),
130
+ size: z.number().optional(),
131
+ required: z.boolean().default(false),
132
+ array: z.boolean().optional(),
133
+ default: z.any().optional(),
134
+ min: z.number().optional(),
135
+ max: z.number().optional(),
136
+ elements: z.array(z.string()).optional(),
137
+ relatedCollection: z.string().optional(),
138
+ relationType: z.string().optional(),
139
+ twoWay: z.boolean().optional(),
140
+ twoWayKey: z.string().optional(),
141
+ onDelete: z.string().optional(),
142
+ side: z.string().optional()
143
+ })
144
+ ).optional().default([]),
145
+ indexes: z.array(
146
+ z.object({
147
+ key: z.string(),
148
+ type: z.string(),
149
+ attributes: z.array(z.string()),
150
+ orders: z.array(z.string()).optional()
151
+ })
152
+ ).optional().default([]),
153
+ importDefs: z.array(z.any()).optional().default([])
154
+ });
155
+
156
+ type YamlCollection = z.infer<typeof YamlCollectionSchema>;
157
+
158
+ /**
159
+ * Loads a YAML collection file and converts it to CollectionCreate format
160
+ * @param filePath Path to the YAML collection file
161
+ * @returns CollectionCreate object or null if loading fails
162
+ */
163
+ export const loadYamlCollection = (filePath: string): CollectionCreate | null => {
164
+ try {
165
+ const fileContent = fs.readFileSync(filePath, "utf8");
166
+ const yamlData = yaml.load(fileContent) as unknown;
167
+ const parsedCollection = YamlCollectionSchema.parse(yamlData);
168
+
169
+ // Convert YAML collection to CollectionCreate format
170
+ const collection: CollectionCreate = {
171
+ name: parsedCollection.name,
172
+ $id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
173
+ documentSecurity: parsedCollection.documentSecurity,
174
+ enabled: parsedCollection.enabled,
175
+ $permissions: parsedCollection.permissions.map(p => ({
176
+ permission: p.permission as any,
177
+ target: p.target
178
+ })),
179
+ attributes: parsedCollection.attributes.map(attr => ({
180
+ key: attr.key,
181
+ type: attr.type as any,
182
+ size: attr.size,
183
+ required: attr.required,
184
+ array: attr.array,
185
+ xdefault: attr.default,
186
+ min: attr.min,
187
+ max: attr.max,
188
+ elements: attr.elements,
189
+ relatedCollection: attr.relatedCollection,
190
+ relationType: attr.relationType as any,
191
+ twoWay: attr.twoWay,
192
+ twoWayKey: attr.twoWayKey,
193
+ onDelete: attr.onDelete as any,
194
+ side: attr.side as any
195
+ })),
196
+ indexes: parsedCollection.indexes.map(idx => ({
197
+ key: idx.key,
198
+ type: idx.type as any,
199
+ attributes: idx.attributes,
200
+ orders: idx.orders as any
201
+ })),
202
+ importDefs: parsedCollection.importDefs && Array.isArray(parsedCollection.importDefs) && parsedCollection.importDefs.length > 0 ? parsedCollection.importDefs : []
203
+ };
204
+
205
+ return collection;
206
+ } catch (error) {
207
+ MessageFormatter.error(`Error loading YAML collection from ${filePath}`, error as Error, { prefix: "Config" });
208
+ return null;
209
+ }
210
+ };
211
+
212
+ /**
213
+ * Loads a YAML table file and converts it to table format
214
+ * @param filePath Path to the YAML table file
215
+ * @returns Table object or null if loading fails
216
+ */
217
+ export const loadYamlTable = (filePath: string): any | null => {
218
+ try {
219
+ const fileContent = fs.readFileSync(filePath, "utf8");
220
+ const yamlData = yaml.load(fileContent) as unknown;
221
+
222
+ // For now, use the collection schema as base and adapt for tables
223
+ const parsedTable = YamlCollectionSchema.parse(yamlData);
224
+
225
+ // Convert YAML table to TableCreate format
226
+ const table: any = {
227
+ name: parsedTable.name,
228
+ tableId: (yamlData as any).tableId || parsedTable.id || parsedTable.name.toLowerCase().replace(/\s+/g, '_'),
229
+ documentSecurity: parsedTable.documentSecurity,
230
+ enabled: parsedTable.enabled,
231
+ $permissions: parsedTable.permissions.map(p => ({
232
+ permission: p.permission as any,
233
+ target: p.target
234
+ })),
235
+ attributes: parsedTable.attributes.map(attr => ({
236
+ key: attr.key,
237
+ type: attr.type as any,
238
+ size: attr.size,
239
+ required: attr.required,
240
+ array: attr.array,
241
+ xdefault: attr.default,
242
+ min: attr.min,
243
+ max: attr.max,
244
+ elements: attr.elements,
245
+ relatedCollection: attr.relatedCollection,
246
+ relationType: attr.relationType as any,
247
+ twoWay: attr.twoWay,
248
+ twoWayKey: attr.twoWayKey,
249
+ onDelete: attr.onDelete as any,
250
+ side: attr.side as any
251
+ })),
252
+ indexes: parsedTable.indexes.map(idx => ({
253
+ key: idx.key,
254
+ type: idx.type as any,
255
+ attributes: idx.attributes,
256
+ orders: idx.orders as any
257
+ })),
258
+ importDefs: parsedTable.importDefs,
259
+ databaseId: (yamlData as any).databaseId,
260
+ // Add backward compatibility field
261
+ $id: (yamlData as any).$id || parsedTable.id
262
+ };
263
+
264
+ return table;
265
+ } catch (error) {
266
+ MessageFormatter.error(`Error loading YAML table from ${filePath}`, error as Error, { prefix: "Config" });
267
+ return null;
268
+ }
269
+ };
270
+
271
+ /**
272
+ * Result of discovering collections from a directory
273
+ */
274
+ export interface CollectionDiscoveryResult {
275
+ collections: CollectionCreate[];
276
+ loadedNames: Set<string>;
277
+ conflicts: Array<{ name: string; source1: string; source2: string }>;
278
+ }
279
+
280
+ /**
281
+ * Discovers and loads collections from a collections/ directory
282
+ * @param collectionsDir Path to the collections directory
283
+ * @returns Discovery result with loaded collections and metadata
284
+ */
285
+ export const discoverCollections = async (collectionsDir: string): Promise<CollectionDiscoveryResult> => {
286
+ const collections: CollectionCreate[] = [];
287
+ const loadedNames = new Set<string>();
288
+ const conflicts: Array<{ name: string; source1: string; source2: string }> = [];
289
+
290
+ if (!fs.existsSync(collectionsDir)) {
291
+ return { collections, loadedNames, conflicts };
292
+ }
293
+
294
+ const unregister = register(); // Register tsx for collections
295
+
296
+ try {
297
+ const collectionFiles = fs.readdirSync(collectionsDir);
298
+ MessageFormatter.success(`Loading from collections/ directory: ${collectionFiles.length} files found`, { prefix: "Config" });
299
+
300
+ for (const file of collectionFiles) {
301
+ if (file === "index.ts") {
302
+ continue;
303
+ }
304
+ const filePath = path.join(collectionsDir, file);
305
+ let collection: CollectionCreate | null = null;
306
+
307
+ // Handle YAML collections
308
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
309
+ collection = loadYamlCollection(filePath);
310
+ }
311
+
312
+ // Handle TypeScript collections
313
+ else if (file.endsWith('.ts')) {
314
+ const fileUrl = pathToFileURL(filePath).href;
315
+ const collectionModule = (await import(fileUrl));
316
+ const importedCollection: Collection | undefined = collectionModule.default?.default || collectionModule.default || collectionModule;
317
+ if (importedCollection) {
318
+ collection = importedCollection as CollectionCreate;
319
+ // Ensure importDefs are properly loaded
320
+ if (collectionModule.importDefs || collection.importDefs) {
321
+ collection.importDefs = collectionModule.importDefs || collection.importDefs;
322
+ }
323
+ }
324
+ }
325
+
326
+ if (collection) {
327
+ const collectionName = collection.name || collection.$id || file;
328
+ loadedNames.add(collectionName);
329
+ collections.push(collection);
330
+ }
331
+ }
332
+ } finally {
333
+ unregister(); // Unregister tsx when done
334
+ }
335
+
336
+ return { collections, loadedNames, conflicts };
337
+ };
338
+
339
+ /**
340
+ * Result of discovering tables from a directory
341
+ */
342
+ export interface TableDiscoveryResult {
343
+ tables: any[];
344
+ loadedNames: Set<string>;
345
+ conflicts: Array<{ name: string; source1: string; source2: string }>;
346
+ }
347
+
348
+ /**
349
+ * Discovers and loads tables from a tables/ directory
350
+ * @param tablesDir Path to the tables directory
351
+ * @param existingNames Set of already-loaded collection names to check for conflicts
352
+ * @returns Discovery result with loaded tables and metadata
353
+ */
354
+ export const discoverTables = async (
355
+ tablesDir: string,
356
+ existingNames: Set<string> = new Set()
357
+ ): Promise<TableDiscoveryResult> => {
358
+ const tables: any[] = [];
359
+ const loadedNames = new Set<string>();
360
+ const conflicts: Array<{ name: string; source1: string; source2: string }> = [];
361
+
362
+ if (!fs.existsSync(tablesDir)) {
363
+ return { tables, loadedNames, conflicts };
364
+ }
365
+
366
+ const unregister = register(); // Register tsx for tables
367
+
368
+ try {
369
+ const tableFiles = fs.readdirSync(tablesDir);
370
+ MessageFormatter.success(`Loading from tables/ directory: ${tableFiles.length} files found`, { prefix: "Config" });
371
+
372
+ for (const file of tableFiles) {
373
+ if (file === "index.ts") {
374
+ continue;
375
+ }
376
+ const filePath = path.join(tablesDir, file);
377
+ let table: any | null = null;
378
+
379
+ // Handle YAML tables
380
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
381
+ table = loadYamlTable(filePath);
382
+ }
383
+
384
+ // Handle TypeScript tables
385
+ else if (file.endsWith('.ts')) {
386
+ const fileUrl = pathToFileURL(filePath).href;
387
+ const tableModule = (await import(fileUrl));
388
+ const importedTable: any = tableModule.default?.default || tableModule.default || tableModule;
389
+ if (importedTable) {
390
+ table = importedTable;
391
+ // Ensure importDefs are properly loaded
392
+ if (tableModule.importDefs || table.importDefs) {
393
+ table.importDefs = tableModule.importDefs || table.importDefs;
394
+ }
395
+ }
396
+ }
397
+
398
+ if (table) {
399
+ const tableName = table.name || table.tableId || table.$id || file;
400
+
401
+ // Check for naming conflicts with existing collections
402
+ if (existingNames.has(tableName)) {
403
+ conflicts.push({
404
+ name: tableName,
405
+ source1: "collections/",
406
+ source2: "tables/"
407
+ });
408
+ MessageFormatter.warning(`Skipping duplicate '${tableName}' from tables/ (collections/ takes priority)`, { prefix: "Config" });
409
+ } else {
410
+ loadedNames.add(tableName);
411
+ // Mark as coming from tables directory
412
+ table._isFromTablesDir = true;
413
+ tables.push(table);
414
+ }
415
+ }
416
+ }
417
+ } finally {
418
+ unregister(); // Unregister tsx when done
419
+ }
420
+
421
+ return { tables, loadedNames, conflicts };
422
+ };
423
+
424
+ /**
425
+ * Discovers and loads collections/tables from legacy single directory structure
426
+ * @param configFileDir Directory containing the config file
427
+ * @param dirName Directory name to search for ('collections' or 'tables')
428
+ * @returns Array of discovered collections/tables
429
+ */
430
+ export const discoverLegacyDirectory = async (
431
+ configFileDir: string,
432
+ dirName: 'collections' | 'tables'
433
+ ): Promise<CollectionCreate[]> => {
434
+ const legacyDir = path.join(configFileDir, dirName);
435
+ const items: CollectionCreate[] = [];
436
+
437
+ if (!fs.existsSync(legacyDir)) {
438
+ return items;
439
+ }
440
+
441
+ MessageFormatter.info(`Using legacy single directory: ${dirName}/`, { prefix: "Config" });
442
+
443
+ const unregister = register(); // Register tsx for legacy collections
444
+
445
+ try {
446
+ const collectionFiles = fs.readdirSync(legacyDir);
447
+
448
+ for (const file of collectionFiles) {
449
+ if (file === "index.ts") {
450
+ continue;
451
+ }
452
+ const filePath = path.join(legacyDir, file);
453
+
454
+ // Handle YAML collections
455
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
456
+ const collection = loadYamlCollection(filePath);
457
+ if (collection) {
458
+ if (dirName === 'tables') {
459
+ // Mark as coming from tables directory
460
+ const table = {
461
+ ...collection,
462
+ _isFromTablesDir: true,
463
+ tableId: collection.$id || collection.name.toLowerCase().replace(/\s+/g, '_')
464
+ };
465
+ items.push(table);
466
+ } else {
467
+ items.push(collection);
468
+ }
469
+ }
470
+ continue;
471
+ }
472
+
473
+ // Handle TypeScript collections
474
+ if (file.endsWith('.ts')) {
475
+ const fileUrl = pathToFileURL(filePath).href;
476
+ const collectionModule = (await import(fileUrl));
477
+ const collection: Collection | undefined = collectionModule.default?.default || collectionModule.default || collectionModule;
478
+ if (collection) {
479
+ // Ensure importDefs are properly loaded
480
+ if (collectionModule.importDefs || collection.importDefs) {
481
+ collection.importDefs = collectionModule.importDefs || collection.importDefs;
482
+ }
483
+ if (dirName === 'tables') {
484
+ // Mark as coming from tables directory
485
+ const table = {
486
+ ...collection,
487
+ _isFromTablesDir: true,
488
+ tableId: collection.$id || collection.name.toLowerCase().replace(/\s+/g, '_')
489
+ };
490
+ items.push(table as CollectionCreate);
491
+ } else {
492
+ items.push(collection as CollectionCreate);
493
+ }
494
+ }
495
+ }
496
+ }
497
+ } finally {
498
+ unregister(); // Unregister tsx when done
499
+ }
500
+
501
+ return items;
502
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Directory utility functions for filtering and managing directory traversal
3
+ */
4
+
5
+ /**
6
+ * List of directories that should be ignored during recursive searches
7
+ * Includes common build outputs, dependencies, caches, and system folders
8
+ */
9
+ export const IGNORED_DIRECTORIES = [
10
+ 'node_modules',
11
+ 'dist',
12
+ 'build',
13
+ 'coverage',
14
+ '.next',
15
+ '.nuxt',
16
+ '.cache',
17
+ '.git',
18
+ '.svn',
19
+ '.hg',
20
+ '__pycache__',
21
+ '.pytest_cache',
22
+ '.mypy_cache',
23
+ 'venv',
24
+ '.venv',
25
+ 'env',
26
+ '.env',
27
+ 'target',
28
+ 'out',
29
+ 'bin',
30
+ 'obj',
31
+ '.vs',
32
+ '.vscode',
33
+ '.idea',
34
+ 'temp',
35
+ 'tmp',
36
+ '.tmp',
37
+ 'logs',
38
+ 'log',
39
+ '.DS_Store',
40
+ 'Thumbs.db'
41
+ ];
42
+
43
+ /**
44
+ * Determines if a directory should be ignored during recursive file operations
45
+ *
46
+ * @param dirName - The name of the directory to check
47
+ * @returns true if the directory should be ignored, false otherwise
48
+ *
49
+ * @remarks
50
+ * A directory is ignored if:
51
+ * - It matches one of the entries in IGNORED_DIRECTORIES
52
+ * - It starts with '.git' prefix
53
+ * - It starts with 'node_modules' prefix
54
+ * - It starts with '.' (hidden directory) except for '.appwrite'
55
+ */
56
+ export const shouldIgnoreDirectory = (dirName: string): boolean => {
57
+ return IGNORED_DIRECTORIES.includes(dirName) ||
58
+ dirName.startsWith('.git') ||
59
+ dirName.startsWith('node_modules') ||
60
+ (dirName.startsWith('.') && dirName !== '.appwrite');
61
+ };