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
@@ -103,8 +103,11 @@ export declare function loadAppwriteProjectConfig(configPath?: string): Appwrite
103
103
  export declare function detectApiModeFromProject(projectConfig: AppwriteProjectConfig): "legacy" | "tablesdb" | "auto";
104
104
  /**
105
105
  * Convert project config to AppwriteConfig format
106
+ * @param projectConfig The project config to convert
107
+ * @param configPath Optional path to the config file (for resolving relative paths)
108
+ * @param existingConfig Optional existing config to merge with
106
109
  */
107
- export declare function projectConfigToAppwriteConfig(projectConfig: AppwriteProjectConfig, existingConfig?: Partial<AppwriteConfig>): Partial<AppwriteConfig>;
110
+ export declare function projectConfigToAppwriteConfig(projectConfig: AppwriteProjectConfig, configPath?: string, existingConfig?: Partial<AppwriteConfig>): Partial<AppwriteConfig>;
108
111
  /**
109
112
  * Get collection/table definitions from project config
110
113
  */
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
- import { join, dirname } from "node:path";
2
+ import { join, dirname, resolve, isAbsolute } from "node:path";
3
3
  import { MessageFormatter } from "../shared/messageFormatter.js";
4
4
  /**
5
5
  * Find appwrite.json or appwrite.config.json in current directory or parents
@@ -70,9 +70,13 @@ export function detectApiModeFromProject(projectConfig) {
70
70
  }
71
71
  /**
72
72
  * Convert project config to AppwriteConfig format
73
+ * @param projectConfig The project config to convert
74
+ * @param configPath Optional path to the config file (for resolving relative paths)
75
+ * @param existingConfig Optional existing config to merge with
73
76
  */
74
- export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
77
+ export function projectConfigToAppwriteConfig(projectConfig, configPath, existingConfig) {
75
78
  const apiMode = detectApiModeFromProject(projectConfig);
79
+ const configDir = configPath ? dirname(configPath) : process.cwd();
76
80
  const baseConfig = {
77
81
  ...existingConfig,
78
82
  appwriteProject: projectConfig.projectId,
@@ -82,10 +86,20 @@ export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
82
86
  if (projectConfig.endpoint) {
83
87
  baseConfig.appwriteEndpoint = projectConfig.endpoint;
84
88
  }
85
- // Convert databases (works for both legacy and TablesDB)
86
- if (projectConfig.databases || projectConfig.tablesDB) {
87
- const databases = projectConfig.databases || projectConfig.tablesDB || [];
88
- baseConfig.databases = databases.map(db => ({
89
+ // Merge databases and tablesDB arrays (modern Appwrite may have both)
90
+ const allDatabases = [
91
+ ...(projectConfig.databases || []),
92
+ ...(projectConfig.tablesDB || [])
93
+ ];
94
+ // Remove duplicates by $id
95
+ const uniqueDatabasesMap = new Map();
96
+ for (const db of allDatabases) {
97
+ if (!uniqueDatabasesMap.has(db.$id)) {
98
+ uniqueDatabasesMap.set(db.$id, db);
99
+ }
100
+ }
101
+ if (uniqueDatabasesMap.size > 0) {
102
+ baseConfig.databases = Array.from(uniqueDatabasesMap.values()).map(db => ({
89
103
  $id: db.$id,
90
104
  name: db.name,
91
105
  // Add basic bucket configuration if not exists
@@ -112,6 +126,27 @@ export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
112
126
  antivirus: bucket.antiVirus ?? false,
113
127
  }));
114
128
  }
129
+ // Convert functions with path normalization
130
+ if (projectConfig.functions) {
131
+ baseConfig.functions = projectConfig.functions.map(func => {
132
+ const normalizedFunc = {
133
+ $id: func.$id,
134
+ name: func.name,
135
+ runtime: func.runtime,
136
+ entrypoint: func.entrypoint,
137
+ commands: func.commands,
138
+ events: func.events,
139
+ };
140
+ // Convert path to dirPath and make absolute
141
+ if (func.path) {
142
+ const expandedPath = func.path;
143
+ normalizedFunc.dirPath = isAbsolute(expandedPath)
144
+ ? expandedPath
145
+ : resolve(configDir, expandedPath);
146
+ }
147
+ return normalizedFunc;
148
+ });
149
+ }
115
150
  return baseConfig;
116
151
  }
117
152
  /**
@@ -55,6 +55,7 @@ export declare class UtilsController {
55
55
  strictMode?: boolean;
56
56
  useSession?: boolean;
57
57
  sessionCookie?: string;
58
+ preferJson?: boolean;
58
59
  }): Promise<void>;
59
60
  reloadConfig(): Promise<void>;
60
61
  ensureDatabaseConfigBucketsExist(databases?: Models.Database[]): Promise<void>;
@@ -151,7 +151,7 @@ export class UtilsController {
151
151
  }
152
152
  }
153
153
  async init(options = {}) {
154
- const { validate = false, strictMode = false } = options;
154
+ const { validate = false, strictMode = false, preferJson = false } = options;
155
155
  const configManager = ConfigManager.getInstance();
156
156
  // Load config if not already loaded
157
157
  if (!configManager.hasConfig()) {
@@ -159,6 +159,7 @@ export class UtilsController {
159
159
  configDir: this.currentUserDir,
160
160
  validate,
161
161
  strictMode,
162
+ preferJson,
162
163
  });
163
164
  }
164
165
  const config = configManager.getConfig();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.8.9",
4
+ "version": "1.9.2",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -45,6 +45,7 @@
45
45
  "commander": "^12.1.0",
46
46
  "decimal.js": "^10.6.0",
47
47
  "es-toolkit": "^1.39.4",
48
+ "find-up": "^8.0.0",
48
49
  "ignore": "^6.0.2",
49
50
  "inquirer": "^9.3.7",
50
51
  "js-yaml": "^4.1.0",
@@ -126,6 +126,18 @@ export interface CreateAttributeParams {
126
126
  default?: any;
127
127
  array?: boolean;
128
128
  encrypt?: boolean;
129
+ // Numeric type parameters
130
+ min?: number;
131
+ max?: number;
132
+ // Enum type parameters
133
+ elements?: string[];
134
+ // Relationship type parameters
135
+ relatedCollection?: string;
136
+ relationType?: 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany';
137
+ twoWay?: boolean;
138
+ twoWayKey?: string;
139
+ onDelete?: 'setNull' | 'cascade' | 'restrict';
140
+ side?: 'parent' | 'child';
129
141
  // Additional type-specific parameters
130
142
  [key: string]: any;
131
143
  }
@@ -483,7 +483,7 @@ export class LegacyAdapter extends BaseAdapter {
483
483
  relatedCollectionId: params.relatedCollection || '',
484
484
  type: (params.type || 'oneToOne') as RelationshipType,
485
485
  twoWay: params.twoWay ?? false,
486
- onDelete: params.onDelete || 'restrict'
486
+ onDelete: (params.onDelete || 'restrict') as RelationMutate
487
487
  });
488
488
  break;
489
489
 
@@ -534,33 +534,33 @@ export class LegacyAdapter extends BaseAdapter {
534
534
  });
535
535
  break;
536
536
 
537
- case 'integer':
538
- const integerAttr = existingAttr as Models.AttributeInteger;
539
- result = await this.databases.updateIntegerAttribute({
540
- databaseId: params.databaseId,
541
- collectionId: params.tableId,
542
- key: params.key,
543
- required: params.required ?? integerAttr.required,
544
- xdefault: params.default !== undefined ? params.default : integerAttr.default,
545
- // Only include when explicitly provided to avoid resubmitting extreme values
546
- ...(params.min !== undefined ? { min: params.min } : {}),
547
- ...(params.max !== undefined ? { max: params.max } : {}),
548
- });
549
- break;
537
+ case 'integer':
538
+ const integerAttr = existingAttr as Models.AttributeInteger;
539
+ result = await this.databases.updateIntegerAttribute({
540
+ databaseId: params.databaseId,
541
+ collectionId: params.tableId,
542
+ key: params.key,
543
+ required: params.required ?? integerAttr.required,
544
+ xdefault: params.default !== undefined ? params.default : integerAttr.default,
545
+ // Only include when explicitly provided to avoid resubmitting extreme values
546
+ ...(params.min !== undefined ? { min: params.min } : {}),
547
+ ...(params.max !== undefined ? { max: params.max } : {}),
548
+ });
549
+ break;
550
550
 
551
- case 'float':
552
- case 'double':
553
- const floatAttr = existingAttr as Models.AttributeFloat;
554
- result = await this.databases.updateFloatAttribute({
555
- databaseId: params.databaseId,
556
- collectionId: params.tableId,
557
- key: params.key,
558
- required: params.required ?? floatAttr.required,
559
- xdefault: params.default !== undefined ? params.default : floatAttr.default,
560
- ...(params.min !== undefined ? { min: params.min } : {}),
561
- ...(params.max !== undefined ? { max: params.max } : {}),
562
- });
563
- break;
551
+ case 'float':
552
+ case 'double':
553
+ const floatAttr = existingAttr as Models.AttributeFloat;
554
+ result = await this.databases.updateFloatAttribute({
555
+ databaseId: params.databaseId,
556
+ collectionId: params.tableId,
557
+ key: params.key,
558
+ required: params.required ?? floatAttr.required,
559
+ xdefault: params.default !== undefined ? params.default : floatAttr.default,
560
+ ...(params.min !== undefined ? { min: params.min } : {}),
561
+ ...(params.max !== undefined ? { max: params.max } : {}),
562
+ });
563
+ break;
564
564
 
565
565
  case 'boolean':
566
566
  const booleanAttr = existingAttr as Models.AttributeBoolean;
@@ -838,4 +838,4 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
838
838
  };
839
839
  }
840
840
  }
841
- }
841
+ }
@@ -448,10 +448,10 @@ export class TablesDBAdapter extends BaseAdapter {
448
448
  tableId: params.tableId,
449
449
  key: params.key,
450
450
  relatedTableId: params.relatedCollection || '',
451
- type: (params.type || 'oneToOne') as RelationshipType,
451
+ type: (params.relationType || 'oneToOne') as RelationshipType,
452
452
  twoWay: params.twoWay ?? false,
453
453
  twoWayKey: params.twoWayKey,
454
- onDelete: params.onDelete || 'restrict'
454
+ onDelete: (params.onDelete || 'restrict') as RelationMutate
455
455
  });
456
456
  break;
457
457
 
@@ -693,7 +693,26 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
693
693
 
694
694
  // Wipe mode: use Query.limit for deleting without fetching
695
695
  if (params.rowIds.length === 0) {
696
- const batchSize = params.batchSize || 250;
696
+ const batchSize = params.batchSize || 1000;
697
+
698
+ // Validation: Ensure table exists before wiping
699
+ try {
700
+ await this.getTable({
701
+ databaseId: params.databaseId,
702
+ tableId: params.tableId
703
+ });
704
+ } catch (error: any) {
705
+ const errorMessage = error?.message || String(error);
706
+ if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
707
+ throw new AdapterError(
708
+ `Table '${params.tableId}' not found in database '${params.databaseId}'`,
709
+ 'TABLE_NOT_FOUND',
710
+ error
711
+ );
712
+ }
713
+ throw error; // Re-throw other errors
714
+ }
715
+
697
716
  queries = [Query.limit(batchSize)];
698
717
  }
699
718
  // Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
@@ -715,8 +734,31 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
715
734
  total: params.rowIds.length || (result as any).total || 0
716
735
  };
717
736
  } catch (error) {
737
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
738
+
739
+ // Enhanced error categorization
740
+ if (errorMessage.toLowerCase().includes('not found') || (error as any)?.code === 404) {
741
+ throw new AdapterError(
742
+ `Table or rows not found: ${errorMessage}`,
743
+ 'NOT_FOUND',
744
+ error instanceof Error ? error : undefined
745
+ );
746
+ } else if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized') || (error as any)?.code === 403) {
747
+ throw new AdapterError(
748
+ `Permission denied for bulk delete: ${errorMessage}`,
749
+ 'PERMISSION_DENIED',
750
+ error instanceof Error ? error : undefined
751
+ );
752
+ } else if (errorMessage.toLowerCase().includes('rate limit') || (error as any)?.code === 429) {
753
+ throw new AdapterError(
754
+ `Rate limit exceeded during bulk delete: ${errorMessage}`,
755
+ 'RATE_LIMIT',
756
+ error instanceof Error ? error : undefined
757
+ );
758
+ }
759
+
718
760
  throw new AdapterError(
719
- `Failed to bulk delete rows: ${error instanceof Error ? error.message : 'Unknown error'}`,
761
+ `Failed to bulk delete rows: ${errorMessage}`,
720
762
  'BULK_DELETE_ROWS_FAILED',
721
763
  error instanceof Error ? error : undefined
722
764
  );
@@ -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";
@@ -9,6 +10,7 @@ import { logger } from "../../shared/logging.js";
9
10
  import { fetchAllDatabases } from "../../databases/methods.js";
10
11
  import { listBuckets } from "../../storage/methods.js";
11
12
  import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
13
+ import { wipeTableRows } from "../../collections/wipeOperations.js";
12
14
  import type { InteractiveCLI } from "../../interactiveCLI.js";
13
15
 
14
16
  export const databaseCommands = {
@@ -29,11 +31,11 @@ export const databaseCommands = {
29
31
  // Push operations always use local configuration as source of truth
30
32
 
31
33
  // Select databases
32
- const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
33
- availableDatabases,
34
- configuredDatabases,
35
- { showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
36
- );
34
+ const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
35
+ availableDatabases,
36
+ configuredDatabases,
37
+ { showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
38
+ );
37
39
 
38
40
  if (selectedDatabaseIds.length === 0) {
39
41
  MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
@@ -93,12 +95,12 @@ export const databaseCommands = {
93
95
  MessageFormatter.warning("No storage buckets available in remote instance.", { prefix: "Database" });
94
96
  } else {
95
97
  // Select buckets using SelectionDialogs
96
- const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
97
- selectedDatabaseIds,
98
- availableBuckets,
99
- configuredBuckets,
100
- { showSelectAll: false, groupByDatabase: true, defaultSelected: [] }
101
- );
98
+ const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
99
+ selectedDatabaseIds,
100
+ availableBuckets,
101
+ configuredBuckets,
102
+ { showSelectAll: false, groupByDatabase: true, defaultSelected: [] }
103
+ );
102
104
 
103
105
  if (selectedBucketIds.length > 0) {
104
106
  // Create BucketSelection objects
@@ -745,5 +747,133 @@ export const databaseCommands = {
745
747
  }
746
748
  }
747
749
  MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
750
+ },
751
+
752
+ async wipeTablesData(cli: InteractiveCLI): Promise<void> {
753
+ const controller = (cli as any).controller;
754
+
755
+ if (!controller?.adapter) {
756
+ throw new Error(
757
+ "Database adapter is not initialized. TablesDB operations require adapter support."
758
+ );
759
+ }
760
+
761
+ try {
762
+ // Step 1: Select database (single selection for clearer UX)
763
+ const databases = await fetchAllDatabases(controller.database);
764
+
765
+ if (!databases || databases.length === 0) {
766
+ MessageFormatter.warning("No databases found", { prefix: "Wipe" });
767
+ return;
768
+ }
769
+
770
+ const { selectedDatabase } = await inquirer.prompt([
771
+ {
772
+ type: "list",
773
+ name: "selectedDatabase",
774
+ message: "Select database containing tables to wipe:",
775
+ choices: databases.map((db: any) => ({
776
+ name: `${db.name} (${db.$id})`,
777
+ value: db
778
+ }))
779
+ }
780
+ ]);
781
+
782
+ const database = selectedDatabase;
783
+
784
+ // Step 2: Get available tables
785
+ const adapter = controller.adapter;
786
+ const tablesResponse = await adapter.listTables({
787
+ databaseId: database.$id,
788
+ queries: [Query.limit(500)]
789
+ });
790
+ const availableTables = (tablesResponse as any).tables || [];
791
+
792
+ if (availableTables.length === 0) {
793
+ MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
794
+ return;
795
+ }
796
+
797
+ // Step 3: Select tables using existing SelectionDialogs
798
+ const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
799
+ database.$id,
800
+ database.name,
801
+ availableTables,
802
+ [], // No configured tables context needed for wipe
803
+ {
804
+ showSelectAll: true,
805
+ allowNewOnly: false,
806
+ defaultSelected: []
807
+ }
808
+ );
809
+
810
+ if (selectedTableIds.length === 0) {
811
+ MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
812
+ return;
813
+ }
814
+
815
+ // Step 4: Show confirmation with table details
816
+ const selectedTables = availableTables.filter((t: any) =>
817
+ selectedTableIds.includes(t.$id)
818
+ );
819
+ const tableNames = selectedTables.map((t: any) => t.name);
820
+
821
+ console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
822
+ console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
823
+ console.log(chalk.yellow("The table structures will remain intact.\n"));
824
+ console.log(chalk.cyan("Database:"), chalk.white(database.name));
825
+ console.log(chalk.cyan("Tables to wipe:"));
826
+ tableNames.forEach((name: string) => console.log(chalk.white(` • ${name}`)));
827
+ console.log();
828
+
829
+ const { confirmed } = await inquirer.prompt([
830
+ {
831
+ type: "confirm",
832
+ name: "confirmed",
833
+ message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
834
+ default: false
835
+ }
836
+ ]);
837
+
838
+ if (!confirmed) {
839
+ MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
840
+ return;
841
+ }
842
+
843
+ // Step 5: Execute wipe using existing wipeTableRows function
844
+ MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
845
+
846
+ for (const table of selectedTables) {
847
+ try {
848
+ MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
849
+
850
+ // Use existing wipeTableRows from wipeOperations.ts
851
+ await wipeTableRows(adapter, database.$id, table.$id);
852
+
853
+ MessageFormatter.success(
854
+ `Successfully wiped rows from table: ${table.name}`,
855
+ { prefix: "Wipe" }
856
+ );
857
+ } catch (error) {
858
+ MessageFormatter.error(
859
+ `Failed to wipe table ${table.name}`,
860
+ error instanceof Error ? error : new Error(String(error)),
861
+ { prefix: "Wipe" }
862
+ );
863
+ }
864
+ }
865
+
866
+ MessageFormatter.success(
867
+ `Wipe operation completed for ${selectedTables.length} table(s)`,
868
+ { prefix: "Wipe" }
869
+ );
870
+ } catch (error) {
871
+ MessageFormatter.error(
872
+ "Table wipe operation failed",
873
+ error instanceof Error ? error : new Error(String(error)),
874
+ { prefix: "Wipe" }
875
+ );
876
+ throw error;
877
+ }
748
878
  }
749
879
  };
@@ -77,6 +77,12 @@ export interface ConfigLoadOptions {
77
77
  * Override session authentication (used for preserving session on reload)
78
78
  */
79
79
  sessionOverride?: SessionAuthInfo;
80
+
81
+ /**
82
+ * Prefer loading from appwrite.config.json over config.yaml
83
+ * @default false
84
+ */
85
+ preferJson?: boolean;
80
86
  }
81
87
 
82
88
  /**
@@ -233,7 +239,10 @@ export class ConfigManager {
233
239
  logger.debug("Loading config from file", { prefix: "ConfigManager", options });
234
240
 
235
241
  // 2. Discover config file
236
- const configPath = this.discoveryService.findConfig(options.configDir || process.cwd());
242
+ const configPath = await this.discoveryService.findConfig(
243
+ options.configDir || process.cwd(),
244
+ options.preferJson || false
245
+ );
237
246
 
238
247
  if (!configPath) {
239
248
  const searchDir = options.configDir || process.cwd();