appwrite-utils-cli 1.8.8 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/adapters/DatabaseAdapter.d.ts +9 -0
  3. package/dist/adapters/LegacyAdapter.js +1 -1
  4. package/dist/adapters/TablesDBAdapter.js +29 -4
  5. package/dist/cli/commands/databaseCommands.d.ts +1 -0
  6. package/dist/cli/commands/databaseCommands.js +90 -0
  7. package/dist/cli/commands/functionCommands.js +53 -48
  8. package/dist/config/ConfigManager.d.ts +5 -0
  9. package/dist/config/ConfigManager.js +1 -1
  10. package/dist/config/services/ConfigDiscoveryService.d.ts +43 -47
  11. package/dist/config/services/ConfigDiscoveryService.js +155 -207
  12. package/dist/config/services/ConfigLoaderService.js +2 -7
  13. package/dist/config/yamlConfig.d.ts +2 -2
  14. package/dist/main.js +9 -1
  15. package/dist/migrations/appwriteToX.d.ts +1 -1
  16. package/dist/migrations/appwriteToX.js +14 -2
  17. package/dist/migrations/dataLoader.d.ts +3 -3
  18. package/dist/storage/schemas.d.ts +4 -4
  19. package/dist/utils/projectConfig.d.ts +4 -1
  20. package/dist/utils/projectConfig.js +41 -6
  21. package/dist/utilsController.d.ts +1 -0
  22. package/dist/utilsController.js +2 -1
  23. package/package.json +2 -1
  24. package/src/adapters/DatabaseAdapter.ts +12 -0
  25. package/src/adapters/LegacyAdapter.ts +28 -28
  26. package/src/adapters/TablesDBAdapter.ts +46 -4
  27. package/src/cli/commands/databaseCommands.ts +141 -11
  28. package/src/cli/commands/functionCommands.ts +72 -67
  29. package/src/config/ConfigManager.ts +10 -1
  30. package/src/config/services/ConfigDiscoveryService.ts +180 -233
  31. package/src/config/services/ConfigLoaderService.ts +2 -10
  32. package/src/main.ts +213 -204
  33. package/src/migrations/appwriteToX.ts +34 -22
  34. package/src/utils/projectConfig.ts +57 -16
  35. package/src/utilsController.ts +73 -72
package/CHANGELOG.md CHANGED
@@ -25,3 +25,11 @@ All notable changes to this project will be documented in this file.
25
25
  - Enhancement: Interactive selection adds “Use same selection as before” when choosing tables per database, speeding up multi-DB workflows.
26
26
  - Behavior: Non-interactive `--push` with both `--dbIds` and `--collectionIds` is fully non-interactive. The provided IDs are applied as-is to every selected database and the confirmation summary is skipped.
27
27
  - Improvement: Selective push reloads local config from disk before pushing to ensure the latest YAML/TS changes are used.
28
+ - Fix: Sync-from-Appwrite now captures function `scopes` and writes them to config; deployments use `scopes` from either central config or `.fnconfig.yaml`.
29
+ - Change: Removed global prompt to choose between central `config.yaml` and per-function `.fnconfig.yaml` during function deployments. The tool now prompts per function only when both sources exist for that function (with an option to merge where `.fnconfig` overrides).
30
+
31
+ ## 1.8.9 - 2025-10-29
32
+
33
+ - Fix: Functions sync now fetches full details via `getFunction` per function to reliably capture `scopes` (prevents empty scopes in some environments).
34
+ - Confirmed schema: YAML `appwrite-config.schema.json` supports `functions[].dirPath`; writers preserve `dirPath` when present.
35
+ - Polish: Per-function config source selection is now applied at deploy time when both central and `.fnconfig.yaml` are present.
@@ -105,6 +105,15 @@ export interface CreateAttributeParams {
105
105
  default?: any;
106
106
  array?: boolean;
107
107
  encrypt?: boolean;
108
+ min?: number;
109
+ max?: number;
110
+ elements?: string[];
111
+ relatedCollection?: string;
112
+ relationType?: 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany';
113
+ twoWay?: boolean;
114
+ twoWayKey?: string;
115
+ onDelete?: 'setNull' | 'cascade' | 'restrict';
116
+ side?: 'parent' | 'child';
108
117
  [key: string]: any;
109
118
  }
110
119
  export interface UpdateAttributeParams {
@@ -316,7 +316,7 @@ export class LegacyAdapter extends BaseAdapter {
316
316
  relatedCollectionId: params.relatedCollection || '',
317
317
  type: (params.type || 'oneToOne'),
318
318
  twoWay: params.twoWay ?? false,
319
- onDelete: params.onDelete || 'restrict'
319
+ onDelete: (params.onDelete || 'restrict')
320
320
  });
321
321
  break;
322
322
  default:
@@ -308,10 +308,10 @@ export class TablesDBAdapter extends BaseAdapter {
308
308
  tableId: params.tableId,
309
309
  key: params.key,
310
310
  relatedTableId: params.relatedCollection || '',
311
- type: (params.type || 'oneToOne'),
311
+ type: (params.relationType || 'oneToOne'),
312
312
  twoWay: params.twoWay ?? false,
313
313
  twoWayKey: params.twoWayKey,
314
- onDelete: params.onDelete || 'restrict'
314
+ onDelete: (params.onDelete || 'restrict')
315
315
  });
316
316
  break;
317
317
  default:
@@ -505,7 +505,21 @@ export class TablesDBAdapter extends BaseAdapter {
505
505
  let queries;
506
506
  // Wipe mode: use Query.limit for deleting without fetching
507
507
  if (params.rowIds.length === 0) {
508
- const batchSize = params.batchSize || 250;
508
+ const batchSize = params.batchSize || 1000;
509
+ // Validation: Ensure table exists before wiping
510
+ try {
511
+ await this.getTable({
512
+ databaseId: params.databaseId,
513
+ tableId: params.tableId
514
+ });
515
+ }
516
+ catch (error) {
517
+ const errorMessage = error?.message || String(error);
518
+ if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
519
+ throw new AdapterError(`Table '${params.tableId}' not found in database '${params.databaseId}'`, 'TABLE_NOT_FOUND', error);
520
+ }
521
+ throw error; // Re-throw other errors
522
+ }
509
523
  queries = [Query.limit(batchSize)];
510
524
  }
511
525
  // Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
@@ -526,7 +540,18 @@ export class TablesDBAdapter extends BaseAdapter {
526
540
  };
527
541
  }
528
542
  catch (error) {
529
- throw new AdapterError(`Failed to bulk delete rows: ${error instanceof Error ? error.message : 'Unknown error'}`, 'BULK_DELETE_ROWS_FAILED', error instanceof Error ? error : undefined);
543
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
544
+ // Enhanced error categorization
545
+ if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
546
+ throw new AdapterError(`Table or rows not found: ${errorMessage}`, 'NOT_FOUND', error instanceof Error ? error : undefined);
547
+ }
548
+ else if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized') || error?.code === 403) {
549
+ throw new AdapterError(`Permission denied for bulk delete: ${errorMessage}`, 'PERMISSION_DENIED', error instanceof Error ? error : undefined);
550
+ }
551
+ else if (errorMessage.toLowerCase().includes('rate limit') || error?.code === 429) {
552
+ throw new AdapterError(`Rate limit exceeded during bulk delete: ${errorMessage}`, 'RATE_LIMIT', error instanceof Error ? error : undefined);
553
+ }
554
+ throw new AdapterError(`Failed to bulk delete rows: ${errorMessage}`, 'BULK_DELETE_ROWS_FAILED', error instanceof Error ? error : undefined);
530
555
  }
531
556
  }
532
557
  // Metadata and Capabilities
@@ -10,4 +10,5 @@ export declare const databaseCommands: {
10
10
  executeUnifiedBackup(cli: InteractiveCLI, trackingDatabaseId: string, scope: any): Promise<void>;
11
11
  wipeDatabase(cli: InteractiveCLI): Promise<void>;
12
12
  wipeCollections(cli: InteractiveCLI): Promise<void>;
13
+ wipeTablesData(cli: InteractiveCLI): Promise<void>;
13
14
  };
@@ -1,6 +1,7 @@
1
1
  import inquirer from "inquirer";
2
2
  import chalk from "chalk";
3
3
  import { join } from "node:path";
4
+ import { Query } from "node-appwrite";
4
5
  import { MessageFormatter } from "../../shared/messageFormatter.js";
5
6
  import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
6
7
  import { SelectionDialogs } from "../../shared/selectionDialogs.js";
@@ -8,6 +9,7 @@ import { logger } from "../../shared/logging.js";
8
9
  import { fetchAllDatabases } from "../../databases/methods.js";
9
10
  import { listBuckets } from "../../storage/methods.js";
10
11
  import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
12
+ import { wipeTableRows } from "../../collections/wipeOperations.js";
11
13
  export const databaseCommands = {
12
14
  async syncDb(cli) {
13
15
  MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
@@ -550,5 +552,93 @@ export const databaseCommands = {
550
552
  }
551
553
  }
552
554
  MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
555
+ },
556
+ async wipeTablesData(cli) {
557
+ const controller = cli.controller;
558
+ if (!controller?.adapter) {
559
+ throw new Error("Database adapter is not initialized. TablesDB operations require adapter support.");
560
+ }
561
+ try {
562
+ // Step 1: Select database (single selection for clearer UX)
563
+ const databases = await fetchAllDatabases(controller.database);
564
+ if (!databases || databases.length === 0) {
565
+ MessageFormatter.warning("No databases found", { prefix: "Wipe" });
566
+ return;
567
+ }
568
+ const { selectedDatabase } = await inquirer.prompt([
569
+ {
570
+ type: "list",
571
+ name: "selectedDatabase",
572
+ message: "Select database containing tables to wipe:",
573
+ choices: databases.map((db) => ({
574
+ name: `${db.name} (${db.$id})`,
575
+ value: db
576
+ }))
577
+ }
578
+ ]);
579
+ const database = selectedDatabase;
580
+ // Step 2: Get available tables
581
+ const adapter = controller.adapter;
582
+ const tablesResponse = await adapter.listTables({
583
+ databaseId: database.$id,
584
+ queries: [Query.limit(500)]
585
+ });
586
+ const availableTables = tablesResponse.tables || [];
587
+ if (availableTables.length === 0) {
588
+ MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
589
+ return;
590
+ }
591
+ // Step 3: Select tables using existing SelectionDialogs
592
+ const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(database.$id, database.name, availableTables, [], // No configured tables context needed for wipe
593
+ {
594
+ showSelectAll: true,
595
+ allowNewOnly: false,
596
+ defaultSelected: []
597
+ });
598
+ if (selectedTableIds.length === 0) {
599
+ MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
600
+ return;
601
+ }
602
+ // Step 4: Show confirmation with table details
603
+ const selectedTables = availableTables.filter((t) => selectedTableIds.includes(t.$id));
604
+ const tableNames = selectedTables.map((t) => t.name);
605
+ console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
606
+ console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
607
+ console.log(chalk.yellow("The table structures will remain intact.\n"));
608
+ console.log(chalk.cyan("Database:"), chalk.white(database.name));
609
+ console.log(chalk.cyan("Tables to wipe:"));
610
+ tableNames.forEach((name) => console.log(chalk.white(` • ${name}`)));
611
+ console.log();
612
+ const { confirmed } = await inquirer.prompt([
613
+ {
614
+ type: "confirm",
615
+ name: "confirmed",
616
+ message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
617
+ default: false
618
+ }
619
+ ]);
620
+ if (!confirmed) {
621
+ MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
622
+ return;
623
+ }
624
+ // Step 5: Execute wipe using existing wipeTableRows function
625
+ MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
626
+ for (const table of selectedTables) {
627
+ try {
628
+ MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
629
+ // Use existing wipeTableRows from wipeOperations.ts
630
+ await wipeTableRows(adapter, database.$id, table.$id);
631
+ MessageFormatter.success(`Successfully wiped rows from table: ${table.name}`, { prefix: "Wipe" });
632
+ }
633
+ catch (error) {
634
+ MessageFormatter.error(`Failed to wipe table ${table.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
635
+ }
636
+ }
637
+ MessageFormatter.success(`Wipe operation completed for ${selectedTables.length} table(s)`, { prefix: "Wipe" });
638
+ }
639
+ catch (error) {
640
+ MessageFormatter.error("Table wipe operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
641
+ throw error;
642
+ }
553
643
  }
554
644
  };
@@ -111,38 +111,14 @@ export const functionCommands = {
111
111
  MessageFormatter.error("Failed to initialize controller or load config", undefined, { prefix: "Functions" });
112
112
  return;
113
113
  }
114
- // Offer choice of function config sources: central YAML, .fnconfig.yaml, or both
115
- let sourceChoice = 'both';
114
+ // Discover per-function .fnconfig.yaml definitions and merge with central list for selection
115
+ // No global prompt; we'll handle conflicts per-function if both exist.
116
+ let discovered = [];
117
+ let central = cli.controller.config.functions || [];
116
118
  try {
117
- const answer = await inquirer.prompt([
118
- {
119
- type: 'list',
120
- name: 'source',
121
- message: 'Select function config source:',
122
- choices: [
123
- { name: 'config.yaml functions (central)', value: 'central' },
124
- { name: '.fnconfig.yaml (discovered per-function)', value: 'fnconfig' },
125
- { name: 'Both (merge; .fnconfig overrides)', value: 'both' },
126
- ],
127
- default: 'both'
128
- }
129
- ]);
130
- sourceChoice = answer.source;
131
- }
132
- catch { }
133
- try {
134
- const discovered = discoverFnConfigs(cli.currentDir);
135
- const central = cli.controller.config.functions || [];
136
- if (sourceChoice === 'central') {
137
- cli.controller.config.functions = central;
138
- }
139
- else if (sourceChoice === 'fnconfig') {
140
- cli.controller.config.functions = discovered;
141
- }
142
- else {
143
- const merged = mergeDiscoveredFunctions(central, discovered);
144
- cli.controller.config.functions = merged;
145
- }
119
+ discovered = discoverFnConfigs(cli.currentDir);
120
+ const merged = mergeDiscoveredFunctions(central, discovered);
121
+ cli.controller.config.functions = merged;
146
122
  }
147
123
  catch { }
148
124
  const functions = await cli.selectFunctions("Select function(s) to deploy:", true, true);
@@ -155,22 +131,51 @@ export const functionCommands = {
155
131
  MessageFormatter.error("Invalid function configuration", undefined, { prefix: "Functions" });
156
132
  return;
157
133
  }
134
+ // Resolve effective config for this function (prefer per-function choice if both sources exist)
135
+ const byIdOrName = (arr) => arr.find((f) => f?.$id === functionConfig.$id || f?.name === functionConfig.name);
136
+ const centralDef = byIdOrName(central);
137
+ const discoveredDef = byIdOrName(discovered);
138
+ let effectiveConfig = functionConfig;
139
+ if (centralDef && discoveredDef) {
140
+ try {
141
+ const answer = await inquirer.prompt([
142
+ {
143
+ type: 'list',
144
+ name: 'cfgChoice',
145
+ message: `Multiple configs found for '${functionConfig.name}'. Which to use?`,
146
+ choices: [
147
+ { name: 'config.yaml (central)', value: 'central' },
148
+ { name: '.fnconfig.yaml (local file)', value: 'fnconfig' },
149
+ { name: 'Merge (.fnconfig overrides central)', value: 'merge' },
150
+ ],
151
+ default: 'fnconfig'
152
+ }
153
+ ]);
154
+ if (answer.cfgChoice === 'central')
155
+ effectiveConfig = centralDef;
156
+ else if (answer.cfgChoice === 'fnconfig')
157
+ effectiveConfig = discoveredDef;
158
+ else
159
+ effectiveConfig = { ...centralDef, ...discoveredDef };
160
+ }
161
+ catch { }
162
+ }
158
163
  // Ensure functions array exists
159
164
  if (!cli.controller.config.functions) {
160
165
  cli.controller.config.functions = [];
161
166
  }
162
- const functionNameLower = functionConfig.name
167
+ const functionNameLower = effectiveConfig.name
163
168
  .toLowerCase()
164
169
  .replace(/\s+/g, "-");
165
170
  // Debug logging
166
171
  MessageFormatter.info(`🔍 Function deployment debug:`, { prefix: "Functions" });
167
- MessageFormatter.info(` Function name: ${functionConfig.name}`, { prefix: "Functions" });
168
- MessageFormatter.info(` Function ID: ${functionConfig.$id}`, { prefix: "Functions" });
169
- MessageFormatter.info(` Config dirPath: ${functionConfig.dirPath || 'undefined'}`, { prefix: "Functions" });
170
- if (functionConfig.dirPath) {
171
- const expandedPath = functionConfig.dirPath.startsWith('~/')
172
- ? functionConfig.dirPath.replace('~', os.homedir())
173
- : functionConfig.dirPath;
172
+ MessageFormatter.info(` Function name: ${effectiveConfig.name}`, { prefix: "Functions" });
173
+ MessageFormatter.info(` Function ID: ${effectiveConfig.$id}`, { prefix: "Functions" });
174
+ MessageFormatter.info(` Config dirPath: ${effectiveConfig.dirPath || 'undefined'}`, { prefix: "Functions" });
175
+ if (effectiveConfig.dirPath) {
176
+ const expandedPath = effectiveConfig.dirPath.startsWith('~/')
177
+ ? effectiveConfig.dirPath.replace('~', os.homedir())
178
+ : effectiveConfig.dirPath;
174
179
  MessageFormatter.info(` Expanded dirPath: ${expandedPath}`, { prefix: "Functions" });
175
180
  }
176
181
  MessageFormatter.info(` Appwrite folder: ${cli.controller.getAppwriteFolderPath()}`, { prefix: "Functions" });
@@ -182,10 +187,10 @@ export const functionCommands = {
182
187
  // Check locations in priority order:
183
188
  const priorityLocations = [
184
189
  // 1. Config dirPath if specified (with tilde expansion)
185
- functionConfig.dirPath
186
- ? (require('node:path').isAbsolute(expandTildePath(functionConfig.dirPath))
187
- ? expandTildePath(functionConfig.dirPath)
188
- : require('node:path').resolve(yamlBaseDir, expandTildePath(functionConfig.dirPath)))
190
+ effectiveConfig.dirPath
191
+ ? (require('node:path').isAbsolute(expandTildePath(effectiveConfig.dirPath))
192
+ ? expandTildePath(effectiveConfig.dirPath)
193
+ : require('node:path').resolve(yamlBaseDir, expandTildePath(effectiveConfig.dirPath)))
189
194
  : undefined,
190
195
  // 2. Appwrite config folder/functions/name
191
196
  join(cli.controller.getAppwriteFolderPath(), "functions", functionNameLower),
@@ -226,10 +231,10 @@ export const functionCommands = {
226
231
  if (shouldDownload) {
227
232
  try {
228
233
  MessageFormatter.progress("Downloading latest deployment...", { prefix: "Functions" });
229
- const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(cli.controller.appwriteServer, functionConfig.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
234
+ const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(cli.controller.appwriteServer, effectiveConfig.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
230
235
  MessageFormatter.success(`✨ Function downloaded to ${downloadedPath}`, { prefix: "Functions" });
231
236
  functionPath = downloadedPath;
232
- functionConfig.dirPath = downloadedPath;
237
+ effectiveConfig.dirPath = downloadedPath;
233
238
  const existingIndex = cli.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
234
239
  if (existingIndex >= 0) {
235
240
  cli.controller.config.functions[existingIndex].dirPath =
@@ -243,7 +248,7 @@ export const functionCommands = {
243
248
  }
244
249
  }
245
250
  else {
246
- MessageFormatter.error(`Function ${functionConfig.name} not found locally. Cannot deploy.`, undefined, { prefix: "Functions" });
251
+ MessageFormatter.error(`Function ${effectiveConfig.name} not found locally. Cannot deploy.`, undefined, { prefix: "Functions" });
247
252
  return;
248
253
  }
249
254
  }
@@ -252,8 +257,8 @@ export const functionCommands = {
252
257
  return;
253
258
  }
254
259
  try {
255
- await deployLocalFunction(cli.controller.appwriteServer, functionConfig.name, {
256
- ...functionConfig,
260
+ await deployLocalFunction(cli.controller.appwriteServer, effectiveConfig.name, {
261
+ ...effectiveConfig,
257
262
  dirPath: functionPath,
258
263
  }, functionPath);
259
264
  MessageFormatter.success("Function deployed successfully!", { prefix: "Functions" });
@@ -54,6 +54,11 @@ export interface ConfigLoadOptions {
54
54
  * Override session authentication (used for preserving session on reload)
55
55
  */
56
56
  sessionOverride?: SessionAuthInfo;
57
+ /**
58
+ * Prefer loading from appwrite.config.json over config.yaml
59
+ * @default false
60
+ */
61
+ preferJson?: boolean;
57
62
  }
58
63
  /**
59
64
  * Filter function for collections (accepts both Collection and CollectionCreate)
@@ -139,7 +139,7 @@ export class ConfigManager {
139
139
  }
140
140
  logger.debug("Loading config from file", { prefix: "ConfigManager", options });
141
141
  // 2. Discover config file
142
- const configPath = this.discoveryService.findConfig(options.configDir || process.cwd());
142
+ const configPath = await this.discoveryService.findConfig(options.configDir || process.cwd(), options.preferJson || false);
143
143
  if (!configPath) {
144
144
  const searchDir = options.configDir || process.cwd();
145
145
  throw new Error(`No Appwrite configuration found in "${searchDir}".\n` +
@@ -10,74 +10,76 @@ export interface DiscoveryResult {
10
10
  /**
11
11
  * Service for discovering Appwrite configuration files and collection/table definitions.
12
12
  *
13
+ * Uses find-up for intelligent searching with git repository boundary detection:
14
+ * 1. Finds .git directory to establish repo root boundary
15
+ * 2. Searches UP from current directory to repo root
16
+ * 3. Searches DOWN recursively within repo root
17
+ *
13
18
  * Search Priority:
14
19
  * 1. YAML configs (.appwrite/config.yaml, .appwrite/config.yml, etc.)
15
- * 2. TypeScript configs (appwriteConfig.ts)
16
- * 3. JSON configs (appwrite.json, appwrite.config.json)
17
- *
18
- * Features:
19
- * - Searches up directory tree (max 5 levels)
20
- * - Ignores common directories (node_modules, .git, etc.)
21
- * - Discovers both collections/ and tables/ directories
22
- * - Recursive subdirectory scanning for .appwrite folders
20
+ * 2. JSON configs (appwrite.config.json, appwrite.json)
21
+ * 3. TypeScript configs (appwriteConfig.ts)
23
22
  */
24
23
  export declare class ConfigDiscoveryService {
25
24
  /**
26
25
  * YAML configuration file names to search for
27
26
  */
28
27
  private readonly YAML_FILENAMES;
28
+ /**
29
+ * JSON configuration file names to search for
30
+ */
31
+ private readonly JSON_FILENAMES;
29
32
  /**
30
33
  * TypeScript configuration file names to search for
31
34
  */
32
35
  private readonly TS_FILENAMES;
33
36
  /**
34
- * JSON configuration file names to search for
37
+ * Finds the git repository root directory
38
+ * @param startDir The directory to start searching from
39
+ * @returns Path to the repository root, or startDir if no .git found
35
40
  */
36
- private readonly JSON_FILENAMES;
41
+ private findRepoRoot;
37
42
  /**
38
- * Maximum levels to search up the directory tree
43
+ * Recursively searches downward for files matching patterns
44
+ * @param dir Directory to search in
45
+ * @param patterns File patterns to match
46
+ * @param maxDepth Maximum depth to search
47
+ * @param currentDepth Current recursion depth
48
+ * @returns First matching file path or null
39
49
  */
40
- private readonly MAX_SEARCH_DEPTH;
50
+ private searchDownward;
41
51
  /**
42
- * Finds any configuration file with priority: YAML → TypeScript → JSON
52
+ * Finds any configuration file with configurable priority
43
53
  * @param startDir The directory to start searching from
54
+ * @param preferJson If true, prioritizes appwrite.config.json over YAML (default: false)
44
55
  * @returns Path to the configuration file or null if not found
56
+ *
57
+ * Default priority: YAML → JSON → TypeScript
58
+ * With preferJson=true: JSON → YAML → TypeScript
45
59
  */
46
- findConfig(startDir: string): string | null;
60
+ findConfig(startDir: string, preferJson?: boolean): Promise<string | null>;
47
61
  /**
48
62
  * Finds YAML configuration files
49
- * Searches current directory, subdirectories, and parent directory
63
+ * Searches UP to repo root, then DOWN from repo root
50
64
  * @param startDir The directory to start searching from
65
+ * @param repoRoot The repository root boundary
51
66
  * @returns Path to the YAML config file or null if not found
52
67
  */
53
- findYamlConfig(startDir: string): string | null;
68
+ findYamlConfig(startDir: string, repoRoot?: string): Promise<string | null>;
54
69
  /**
55
- * Recursively searches for YAML configs in .appwrite subdirectories
56
- * @param dir The directory to search
57
- * @param depth Current search depth
58
- * @returns Path to YAML config or null
70
+ * Finds JSON project configuration files (appwrite.config.json, appwrite.json)
71
+ * @param startDir The directory to start searching from
72
+ * @param repoRoot The repository root boundary
73
+ * @returns Path to the JSON config file or null if not found
59
74
  */
60
- private findYamlConfigRecursive;
75
+ findProjectConfig(startDir: string, repoRoot?: string): Promise<string | null>;
61
76
  /**
62
77
  * Finds TypeScript configuration files (appwriteConfig.ts)
63
78
  * @param startDir The directory to start searching from
79
+ * @param repoRoot The repository root boundary
64
80
  * @returns Path to the TypeScript config file or null if not found
65
81
  */
66
- findTypeScriptConfig(startDir: string): string | null;
67
- /**
68
- * Recursively searches for TypeScript configuration files
69
- * @param dir The directory to search
70
- * @param depth Current search depth
71
- * @returns Path to TypeScript config or null
72
- */
73
- private findTypeScriptConfigRecursive;
74
- /**
75
- * Finds project configuration JSON files (appwrite.json, appwrite.config.json)
76
- * Searches up to 5 levels up the directory tree
77
- * @param startDir The directory to start searching from
78
- * @returns Path to the JSON config file or null if not found
79
- */
80
- findProjectConfig(startDir?: string): string | null;
82
+ findTypeScriptConfig(startDir: string, repoRoot?: string): Promise<string | null>;
81
83
  /**
82
84
  * Discovers collection YAML files in a collections/ directory
83
85
  * @param collectionsDir Path to the collections directory
@@ -95,32 +97,26 @@ export declare class ConfigDiscoveryService {
95
97
  * @param startDir The directory to start searching from
96
98
  * @returns Path to .appwrite directory or null if not found
97
99
  */
98
- findAppwriteDirectory(startDir: string): string | null;
100
+ findAppwriteDirectory(startDir: string): Promise<string | null>;
99
101
  /**
100
102
  * Finds the functions directory
101
103
  * @param startDir The directory to start searching from
102
104
  * @returns Path to functions directory or null if not found
103
105
  */
104
- findFunctionsDirectory(startDir: string): string | null;
105
- /**
106
- * Recursively searches for the functions directory
107
- * @param dir The directory to search
108
- * @param depth Current search depth
109
- * @returns Path to functions directory or null
110
- */
111
- private findFunctionsDirectoryRecursive;
106
+ findFunctionsDirectory(startDir: string): Promise<string | null>;
112
107
  /**
113
108
  * Gets a summary of all discoverable configuration files
114
109
  * Useful for debugging configuration issues
115
110
  * @param startDir The directory to start searching from
116
111
  * @returns Object containing paths to all discovered config types
117
112
  */
118
- getConfigurationSummary(startDir: string): {
113
+ getConfigurationSummary(startDir: string): Promise<{
119
114
  yaml: string | null;
120
115
  typescript: string | null;
121
116
  json: string | null;
122
117
  appwriteDirectory: string | null;
123
118
  functionsDirectory: string | null;
124
119
  selectedConfig: string | null;
125
- };
120
+ repoRoot: string;
121
+ }>;
126
122
  }