appwrite-utils-cli 1.8.9 → 1.9.2

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 (34) hide show
  1. package/dist/adapters/DatabaseAdapter.d.ts +9 -0
  2. package/dist/adapters/LegacyAdapter.js +1 -1
  3. package/dist/adapters/TablesDBAdapter.js +29 -4
  4. package/dist/cli/commands/databaseCommands.d.ts +1 -0
  5. package/dist/cli/commands/databaseCommands.js +90 -0
  6. package/dist/config/ConfigManager.d.ts +5 -0
  7. package/dist/config/ConfigManager.js +1 -1
  8. package/dist/config/services/ConfigDiscoveryService.d.ts +43 -47
  9. package/dist/config/services/ConfigDiscoveryService.js +155 -207
  10. package/dist/config/services/ConfigLoaderService.js +2 -7
  11. package/dist/config/yamlConfig.d.ts +2 -2
  12. package/dist/functions/methods.js +14 -2
  13. package/dist/main.js +9 -1
  14. package/dist/migrations/appwriteToX.d.ts +1 -1
  15. package/dist/migrations/dataLoader.d.ts +3 -3
  16. package/dist/shared/functionManager.js +14 -2
  17. package/dist/storage/schemas.d.ts +4 -4
  18. package/dist/utils/projectConfig.d.ts +4 -1
  19. package/dist/utils/projectConfig.js +41 -6
  20. package/dist/utilsController.d.ts +1 -0
  21. package/dist/utilsController.js +2 -1
  22. package/package.json +2 -1
  23. package/src/adapters/DatabaseAdapter.ts +12 -0
  24. package/src/adapters/LegacyAdapter.ts +28 -28
  25. package/src/adapters/TablesDBAdapter.ts +46 -4
  26. package/src/cli/commands/databaseCommands.ts +141 -11
  27. package/src/config/ConfigManager.ts +10 -1
  28. package/src/config/services/ConfigDiscoveryService.ts +180 -233
  29. package/src/config/services/ConfigLoaderService.ts +2 -10
  30. package/src/functions/methods.ts +15 -2
  31. package/src/main.ts +213 -204
  32. package/src/shared/functionManager.ts +15 -3
  33. package/src/utils/projectConfig.ts +57 -16
  34. package/src/utilsController.ts +73 -72
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import { findUp } from "find-up";
3
4
  import { MessageFormatter } from "../../shared/messageFormatter.js";
4
5
  import { shouldIgnoreDirectory } from "../../utils/directoryUtils.js";
5
6
 
@@ -16,16 +17,15 @@ export interface DiscoveryResult {
16
17
  /**
17
18
  * Service for discovering Appwrite configuration files and collection/table definitions.
18
19
  *
20
+ * Uses find-up for intelligent searching with git repository boundary detection:
21
+ * 1. Finds .git directory to establish repo root boundary
22
+ * 2. Searches UP from current directory to repo root
23
+ * 3. Searches DOWN recursively within repo root
24
+ *
19
25
  * Search Priority:
20
26
  * 1. YAML configs (.appwrite/config.yaml, .appwrite/config.yml, etc.)
21
- * 2. TypeScript configs (appwriteConfig.ts)
22
- * 3. JSON configs (appwrite.json, appwrite.config.json)
23
- *
24
- * Features:
25
- * - Searches up directory tree (max 5 levels)
26
- * - Ignores common directories (node_modules, .git, etc.)
27
- * - Discovers both collections/ and tables/ directories
28
- * - Recursive subdirectory scanning for .appwrite folders
27
+ * 2. JSON configs (appwrite.config.json, appwrite.json)
28
+ * 3. TypeScript configs (appwriteConfig.ts)
29
29
  */
30
30
  export class ConfigDiscoveryService {
31
31
  /**
@@ -40,212 +40,188 @@ export class ConfigDiscoveryService {
40
40
  "appwrite.yml",
41
41
  ];
42
42
 
43
- /**
44
- * TypeScript configuration file names to search for
45
- */
46
- private readonly TS_FILENAMES = ["appwriteConfig.ts"];
47
-
48
43
  /**
49
44
  * JSON configuration file names to search for
50
45
  */
51
- private readonly JSON_FILENAMES = ["appwrite.json", "appwrite.config.json"];
46
+ private readonly JSON_FILENAMES = ["appwrite.config.json", "appwrite.json"];
52
47
 
53
48
  /**
54
- * Maximum levels to search up the directory tree
49
+ * TypeScript configuration file names to search for
55
50
  */
56
- private readonly MAX_SEARCH_DEPTH = 5;
51
+ private readonly TS_FILENAMES = ["appwriteConfig.ts"];
57
52
 
58
53
  /**
59
- * Finds any configuration file with priority: YAML → TypeScript → JSON
54
+ * Finds the git repository root directory
60
55
  * @param startDir The directory to start searching from
61
- * @returns Path to the configuration file or null if not found
56
+ * @returns Path to the repository root, or startDir if no .git found
62
57
  */
63
- public findConfig(startDir: string): string | null {
64
- // Try YAML first (highest priority)
65
- const yamlConfig = this.findYamlConfig(startDir);
66
- if (yamlConfig) {
67
- return yamlConfig;
68
- }
69
-
70
- // Try TypeScript second
71
- const tsConfig = this.findTypeScriptConfig(startDir);
72
- if (tsConfig) {
73
- return tsConfig;
74
- }
58
+ private async findRepoRoot(startDir: string): Promise<string> {
59
+ const gitDir = await findUp(".git", {
60
+ cwd: startDir,
61
+ type: "directory",
62
+ });
75
63
 
76
- // Try JSON last (lowest priority)
77
- const jsonConfig = this.findProjectConfig(startDir);
78
- if (jsonConfig) {
79
- return jsonConfig;
80
- }
81
-
82
- return null;
64
+ return gitDir ? path.dirname(gitDir) : startDir;
83
65
  }
84
66
 
85
67
  /**
86
- * Finds YAML configuration files
87
- * Searches current directory, subdirectories, and parent directory
88
- * @param startDir The directory to start searching from
89
- * @returns Path to the YAML config file or null if not found
68
+ * Recursively searches downward for files matching patterns
69
+ * @param dir Directory to search in
70
+ * @param patterns File patterns to match
71
+ * @param maxDepth Maximum depth to search
72
+ * @param currentDepth Current recursion depth
73
+ * @returns First matching file path or null
90
74
  */
91
- public findYamlConfig(startDir: string): string | null {
92
- // First check current directory for YAML configs
93
- for (const fileName of this.YAML_FILENAMES) {
94
- const configPath = path.join(startDir, fileName);
95
- if (fs.existsSync(configPath)) {
96
- return configPath;
97
- }
98
- }
75
+ private async searchDownward(
76
+ dir: string,
77
+ patterns: string[],
78
+ maxDepth: number = 5,
79
+ currentDepth: number = 0
80
+ ): Promise<string | null> {
81
+ if (currentDepth > maxDepth) return null;
82
+ if (shouldIgnoreDirectory(path.basename(dir))) return null;
99
83
 
100
- // Recursively search subdirectories for .appwrite folders
101
- const yamlConfigInSubdirs = this.findYamlConfigRecursive(startDir);
102
- if (yamlConfigInSubdirs) {
103
- return yamlConfigInSubdirs;
104
- }
84
+ try {
85
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
105
86
 
106
- // Check one level up to avoid infinite traversal
107
- const parentDir = path.dirname(startDir);
108
- if (parentDir !== startDir && path.basename(parentDir) !== "node_modules") {
109
- for (const fileName of this.YAML_FILENAMES) {
110
- const configPath = path.join(parentDir, fileName);
111
- if (fs.existsSync(configPath)) {
112
- return configPath;
87
+ // Check current directory for matches
88
+ for (const pattern of patterns) {
89
+ const fullPath = path.join(dir, pattern);
90
+ if (fs.existsSync(fullPath)) {
91
+ return fullPath;
113
92
  }
114
93
  }
115
- }
116
-
117
- return null;
118
- }
119
-
120
- /**
121
- * Recursively searches for YAML configs in .appwrite subdirectories
122
- * @param dir The directory to search
123
- * @param depth Current search depth
124
- * @returns Path to YAML config or null
125
- */
126
- private findYamlConfigRecursive(dir: string, depth: number = 0): string | null {
127
- // Limit search depth to prevent infinite recursion
128
- if (depth > this.MAX_SEARCH_DEPTH) {
129
- return null;
130
- }
131
-
132
- if (shouldIgnoreDirectory(path.basename(dir))) {
133
- return null;
134
- }
135
-
136
- try {
137
- const entries = fs.readdirSync(dir, { withFileTypes: true });
138
94
 
95
+ // Recurse into subdirectories
139
96
  for (const entry of entries) {
140
97
  if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
141
- const fullPath = path.join(dir, entry.name);
142
-
143
- // Check if this is an .appwrite directory
144
- if (entry.name === ".appwrite") {
145
- const configPaths = [
146
- path.join(fullPath, "config.yaml"),
147
- path.join(fullPath, "config.yml"),
148
- path.join(fullPath, "appwriteConfig.yaml"),
149
- path.join(fullPath, "appwriteConfig.yml"),
150
- ];
151
-
152
- for (const configPath of configPaths) {
153
- if (fs.existsSync(configPath)) {
154
- return configPath;
155
- }
156
- }
157
- }
158
-
159
- // Recurse into other directories with increased depth
160
- const result = this.findYamlConfigRecursive(fullPath, depth + 1);
98
+ const result = await this.searchDownward(
99
+ path.join(dir, entry.name),
100
+ patterns,
101
+ maxDepth,
102
+ currentDepth + 1
103
+ );
161
104
  if (result) return result;
162
105
  }
163
106
  }
164
107
  } catch (error) {
165
- // Ignore directory access errors
108
+ // Ignore permission errors
166
109
  }
167
110
 
168
111
  return null;
169
112
  }
170
113
 
171
114
  /**
172
- * Finds TypeScript configuration files (appwriteConfig.ts)
115
+ * Finds any configuration file with configurable priority
173
116
  * @param startDir The directory to start searching from
174
- * @returns Path to the TypeScript config file or null if not found
117
+ * @param preferJson If true, prioritizes appwrite.config.json over YAML (default: false)
118
+ * @returns Path to the configuration file or null if not found
119
+ *
120
+ * Default priority: YAML → JSON → TypeScript
121
+ * With preferJson=true: JSON → YAML → TypeScript
175
122
  */
176
- public findTypeScriptConfig(startDir: string): string | null {
177
- return this.findTypeScriptConfigRecursive(startDir);
123
+ public async findConfig(startDir: string, preferJson: boolean = false): Promise<string | null> {
124
+ // Find repo root to establish boundary
125
+ const repoRoot = await this.findRepoRoot(startDir);
126
+
127
+ if (preferJson) {
128
+ // Try JSON first when --appwrite-config flag is used
129
+ const jsonConfig = await this.findProjectConfig(startDir, repoRoot);
130
+ if (jsonConfig) return jsonConfig;
131
+
132
+ // Try YAML second
133
+ const yamlConfig = await this.findYamlConfig(startDir, repoRoot);
134
+ if (yamlConfig) return yamlConfig;
135
+
136
+ // Try TypeScript last (lowest priority)
137
+ const tsConfig = await this.findTypeScriptConfig(startDir, repoRoot);
138
+ if (tsConfig) return tsConfig;
139
+ } else {
140
+ // Default priority: YAML → JSON → TypeScript
141
+ const yamlConfig = await this.findYamlConfig(startDir, repoRoot);
142
+ if (yamlConfig) return yamlConfig;
143
+
144
+ const jsonConfig = await this.findProjectConfig(startDir, repoRoot);
145
+ if (jsonConfig) return jsonConfig;
146
+
147
+ const tsConfig = await this.findTypeScriptConfig(startDir, repoRoot);
148
+ if (tsConfig) return tsConfig;
149
+ }
150
+
151
+ return null;
178
152
  }
179
153
 
180
154
  /**
181
- * Recursively searches for TypeScript configuration files
182
- * @param dir The directory to search
183
- * @param depth Current search depth
184
- * @returns Path to TypeScript config or null
155
+ * Finds YAML configuration files
156
+ * Searches UP to repo root, then DOWN from repo root
157
+ * @param startDir The directory to start searching from
158
+ * @param repoRoot The repository root boundary
159
+ * @returns Path to the YAML config file or null if not found
185
160
  */
186
- private findTypeScriptConfigRecursive(dir: string, depth: number = 0): string | null {
187
- // Limit search depth to prevent infinite recursion
188
- if (depth > 10) {
189
- return null;
190
- }
191
-
192
- if (shouldIgnoreDirectory(path.basename(dir))) {
193
- return null;
194
- }
195
-
196
- try {
197
- const entries = fs.readdirSync(dir, { withFileTypes: true });
198
-
199
- // First check current directory for appwriteConfig.ts
200
- for (const entry of entries) {
201
- if (entry.isFile() && this.TS_FILENAMES.includes(entry.name)) {
202
- return path.join(dir, entry.name);
203
- }
204
- }
205
-
206
- // Then search subdirectories
207
- for (const entry of entries) {
208
- if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
209
- const result = this.findTypeScriptConfigRecursive(
210
- path.join(dir, entry.name),
211
- depth + 1
212
- );
213
- if (result) return result;
214
- }
215
- }
216
- } catch (error) {
217
- // Ignore directory access errors
218
- }
219
-
220
- return null;
161
+ public async findYamlConfig(
162
+ startDir: string,
163
+ repoRoot?: string
164
+ ): Promise<string | null> {
165
+ const boundary = repoRoot || (await this.findRepoRoot(startDir));
166
+
167
+ // Search UP to repo root
168
+ const upwardResult = await findUp(this.YAML_FILENAMES, {
169
+ cwd: startDir,
170
+ stopAt: boundary,
171
+ });
172
+
173
+ if (upwardResult) return upwardResult;
174
+
175
+ // Search DOWN from repo root
176
+ return await this.searchDownward(boundary, this.YAML_FILENAMES);
221
177
  }
222
178
 
223
179
  /**
224
- * Finds project configuration JSON files (appwrite.json, appwrite.config.json)
225
- * Searches up to 5 levels up the directory tree
180
+ * Finds JSON project configuration files (appwrite.config.json, appwrite.json)
226
181
  * @param startDir The directory to start searching from
182
+ * @param repoRoot The repository root boundary
227
183
  * @returns Path to the JSON config file or null if not found
228
184
  */
229
- public findProjectConfig(startDir: string = process.cwd()): string | null {
230
- let currentDir = startDir;
231
-
232
- // Search up to MAX_SEARCH_DEPTH levels up the directory tree
233
- for (let i = 0; i < this.MAX_SEARCH_DEPTH; i++) {
234
- for (const configName of this.JSON_FILENAMES) {
235
- const configPath = path.join(currentDir, configName);
236
- if (fs.existsSync(configPath)) {
237
- return configPath;
238
- }
239
- }
240
-
241
- const parentDir = path.dirname(currentDir);
242
- if (parentDir === currentDir) {
243
- break; // Reached filesystem root
244
- }
245
- currentDir = parentDir;
246
- }
185
+ public async findProjectConfig(
186
+ startDir: string,
187
+ repoRoot?: string
188
+ ): Promise<string | null> {
189
+ const boundary = repoRoot || (await this.findRepoRoot(startDir));
190
+
191
+ // Search UP to repo root
192
+ const upwardResult = await findUp(this.JSON_FILENAMES, {
193
+ cwd: startDir,
194
+ stopAt: boundary,
195
+ });
196
+
197
+ if (upwardResult) return upwardResult;
198
+
199
+ // Search DOWN from repo root
200
+ return await this.searchDownward(boundary, this.JSON_FILENAMES);
201
+ }
247
202
 
248
- return null;
203
+ /**
204
+ * Finds TypeScript configuration files (appwriteConfig.ts)
205
+ * @param startDir The directory to start searching from
206
+ * @param repoRoot The repository root boundary
207
+ * @returns Path to the TypeScript config file or null if not found
208
+ */
209
+ public async findTypeScriptConfig(
210
+ startDir: string,
211
+ repoRoot?: string
212
+ ): Promise<string | null> {
213
+ const boundary = repoRoot || (await this.findRepoRoot(startDir));
214
+
215
+ // Search UP to repo root
216
+ const upwardResult = await findUp(this.TS_FILENAMES, {
217
+ cwd: startDir,
218
+ stopAt: boundary,
219
+ });
220
+
221
+ if (upwardResult) return upwardResult;
222
+
223
+ // Search DOWN from repo root
224
+ return await this.searchDownward(boundary, this.TS_FILENAMES);
249
225
  }
250
226
 
251
227
  /**
@@ -367,24 +343,20 @@ export class ConfigDiscoveryService {
367
343
  * @param startDir The directory to start searching from
368
344
  * @returns Path to .appwrite directory or null if not found
369
345
  */
370
- public findAppwriteDirectory(startDir: string): string | null {
371
- let currentDir = startDir;
372
-
373
- // Search up to MAX_SEARCH_DEPTH levels up the directory tree
374
- for (let i = 0; i < this.MAX_SEARCH_DEPTH; i++) {
375
- const appwriteDir = path.join(currentDir, ".appwrite");
376
- if (fs.existsSync(appwriteDir) && fs.statSync(appwriteDir).isDirectory()) {
377
- return appwriteDir;
378
- }
346
+ public async findAppwriteDirectory(startDir: string): Promise<string | null> {
347
+ const repoRoot = await this.findRepoRoot(startDir);
379
348
 
380
- const parentDir = path.dirname(currentDir);
381
- if (parentDir === currentDir) {
382
- break; // Reached filesystem root
383
- }
384
- currentDir = parentDir;
385
- }
349
+ // Search UP to repo root
350
+ const upwardResult = await findUp(".appwrite", {
351
+ cwd: startDir,
352
+ type: "directory",
353
+ stopAt: repoRoot,
354
+ });
386
355
 
387
- return null;
356
+ if (upwardResult) return upwardResult;
357
+
358
+ // Search DOWN from repo root
359
+ return await this.searchDownward(repoRoot, [".appwrite"]);
388
360
  }
389
361
 
390
362
  /**
@@ -392,49 +364,20 @@ export class ConfigDiscoveryService {
392
364
  * @param startDir The directory to start searching from
393
365
  * @returns Path to functions directory or null if not found
394
366
  */
395
- public findFunctionsDirectory(startDir: string): string | null {
396
- return this.findFunctionsDirectoryRecursive(startDir);
397
- }
367
+ public async findFunctionsDirectory(startDir: string): Promise<string | null> {
368
+ const repoRoot = await this.findRepoRoot(startDir);
398
369
 
399
- /**
400
- * Recursively searches for the functions directory
401
- * @param dir The directory to search
402
- * @param depth Current search depth
403
- * @returns Path to functions directory or null
404
- */
405
- private findFunctionsDirectoryRecursive(dir: string, depth: number = 0): string | null {
406
- // Limit search depth to prevent infinite recursion
407
- if (depth > this.MAX_SEARCH_DEPTH) {
408
- return null;
409
- }
370
+ // Search UP to repo root
371
+ const upwardResult = await findUp("functions", {
372
+ cwd: startDir,
373
+ type: "directory",
374
+ stopAt: repoRoot,
375
+ });
410
376
 
411
- if (shouldIgnoreDirectory(path.basename(dir))) {
412
- return null;
413
- }
377
+ if (upwardResult) return upwardResult;
414
378
 
415
- try {
416
- const files = fs.readdirSync(dir, { withFileTypes: true });
417
-
418
- for (const entry of files) {
419
- if (!entry.isDirectory() || shouldIgnoreDirectory(entry.name)) {
420
- continue;
421
- }
422
-
423
- if (entry.name === "functions") {
424
- return path.join(dir, entry.name);
425
- }
426
-
427
- const result = this.findFunctionsDirectoryRecursive(
428
- path.join(dir, entry.name),
429
- depth + 1
430
- );
431
- if (result) return result;
432
- }
433
- } catch (error) {
434
- // Ignore directory access errors
435
- }
436
-
437
- return null;
379
+ // Search DOWN from repo root
380
+ return await this.searchDownward(repoRoot, ["functions"]);
438
381
  }
439
382
 
440
383
  /**
@@ -443,21 +386,25 @@ export class ConfigDiscoveryService {
443
386
  * @param startDir The directory to start searching from
444
387
  * @returns Object containing paths to all discovered config types
445
388
  */
446
- public getConfigurationSummary(startDir: string): {
389
+ public async getConfigurationSummary(startDir: string): Promise<{
447
390
  yaml: string | null;
448
391
  typescript: string | null;
449
392
  json: string | null;
450
393
  appwriteDirectory: string | null;
451
394
  functionsDirectory: string | null;
452
395
  selectedConfig: string | null;
453
- } {
396
+ repoRoot: string;
397
+ }> {
398
+ const repoRoot = await this.findRepoRoot(startDir);
399
+
454
400
  return {
455
- yaml: this.findYamlConfig(startDir),
456
- typescript: this.findTypeScriptConfig(startDir),
457
- json: this.findProjectConfig(startDir),
458
- appwriteDirectory: this.findAppwriteDirectory(startDir),
459
- functionsDirectory: this.findFunctionsDirectory(startDir),
460
- selectedConfig: this.findConfig(startDir),
401
+ yaml: await this.findYamlConfig(startDir, repoRoot),
402
+ typescript: await this.findTypeScriptConfig(startDir, repoRoot),
403
+ json: await this.findProjectConfig(startDir, repoRoot),
404
+ appwriteDirectory: await this.findAppwriteDirectory(startDir),
405
+ functionsDirectory: await this.findFunctionsDirectory(startDir),
406
+ selectedConfig: await this.findConfig(startDir),
407
+ repoRoot,
461
408
  };
462
409
  }
463
410
  }
@@ -336,8 +336,8 @@ export class ConfigLoaderService {
336
336
  throw new Error(`Failed to load project config from: ${jsonPath}`);
337
337
  }
338
338
 
339
- // Convert project config to AppwriteConfig format
340
- const appwriteConfig = projectConfigToAppwriteConfig(projectConfig);
339
+ // Convert project config to AppwriteConfig format (includes function path normalization)
340
+ const appwriteConfig = projectConfigToAppwriteConfig(projectConfig, jsonPath);
341
341
 
342
342
  // Get collections from project config
343
343
  const collections = getCollectionsFromProject(projectConfig);
@@ -345,14 +345,6 @@ export class ConfigLoaderService {
345
345
  appwriteConfig.collections = collections;
346
346
  }
347
347
 
348
- // Normalize function paths
349
- const configDir = path.dirname(jsonPath);
350
- if (appwriteConfig.functions) {
351
- appwriteConfig.functions = appwriteConfig.functions.map(func =>
352
- this.normalizeFunctionPath(func, configDir)
353
- );
354
- }
355
-
356
348
  MessageFormatter.success(`Loaded project config from: ${jsonPath}`, {
357
349
  prefix: "Config",
358
350
  });
@@ -13,6 +13,7 @@ import {
13
13
  type FunctionScope,
14
14
  type Specification,
15
15
  type Runtime as AppwriteUtilsRuntime,
16
+ EventTypeSchema,
16
17
  } from "appwrite-utils";
17
18
  import chalk from "chalk";
18
19
  import { extract as extractTar } from "tar";
@@ -22,14 +23,26 @@ import { expandTildePath, normalizeFunctionName } from "./pathResolution.js";
22
23
  /**
23
24
  * Validates and filters events array for Appwrite functions
24
25
  * - Filters out empty/invalid strings
26
+ * - Validates against EventTypeSchema
25
27
  * - Limits to 100 items maximum (Appwrite limit)
26
28
  * - Returns empty array if input is invalid
27
29
  */
28
30
  const validateEvents = (events?: string[]): string[] => {
29
31
  if (!events || !Array.isArray(events)) return [];
30
-
32
+
31
33
  return events
32
- .filter(event => event && typeof event === 'string' && event.trim().length > 0)
34
+ .filter(event => {
35
+ if (!event || typeof event !== 'string' || event.trim().length === 0) {
36
+ return false;
37
+ }
38
+ // Validate against EventTypeSchema
39
+ const result = EventTypeSchema.safeParse(event);
40
+ if (!result.success) {
41
+ MessageFormatter.warning(`Invalid event type "${event}" will be filtered out`, { prefix: "Functions" });
42
+ return false;
43
+ }
44
+ return true;
45
+ })
33
46
  .slice(0, 100);
34
47
  };
35
48