appwrite-utils-cli 1.6.3 → 1.6.4

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 (55) hide show
  1. package/CONFIG_TODO.md +1189 -0
  2. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  3. package/dist/cli/commands/configCommands.js +7 -1
  4. package/dist/collections/attributes.js +102 -30
  5. package/dist/config/ConfigManager.d.ts +445 -0
  6. package/dist/config/ConfigManager.js +625 -0
  7. package/dist/config/index.d.ts +8 -0
  8. package/dist/config/index.js +7 -0
  9. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  10. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  11. package/dist/config/services/ConfigLoaderService.d.ts +105 -0
  12. package/dist/config/services/ConfigLoaderService.js +410 -0
  13. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  14. package/dist/config/services/ConfigMergeService.js +307 -0
  15. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  16. package/dist/config/services/ConfigValidationService.js +310 -0
  17. package/dist/config/services/SessionAuthService.d.ts +225 -0
  18. package/dist/config/services/SessionAuthService.js +456 -0
  19. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  20. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  21. package/dist/config/services/index.d.ts +13 -0
  22. package/dist/config/services/index.js +10 -0
  23. package/dist/interactiveCLI.js +8 -6
  24. package/dist/main.js +2 -2
  25. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  26. package/dist/shared/operationQueue.js +1 -1
  27. package/dist/utils/ClientFactory.d.ts +87 -0
  28. package/dist/utils/ClientFactory.js +164 -0
  29. package/dist/utils/getClientFromConfig.js +4 -3
  30. package/dist/utils/helperFunctions.d.ts +1 -0
  31. package/dist/utils/helperFunctions.js +21 -5
  32. package/dist/utils/yamlConverter.d.ts +2 -2
  33. package/dist/utils/yamlConverter.js +2 -2
  34. package/dist/utilsController.d.ts +18 -15
  35. package/dist/utilsController.js +83 -131
  36. package/package.json +1 -1
  37. package/src/cli/commands/configCommands.ts +8 -1
  38. package/src/collections/attributes.ts +118 -31
  39. package/src/config/ConfigManager.ts +808 -0
  40. package/src/config/index.ts +10 -0
  41. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  42. package/src/config/services/ConfigLoaderService.ts +560 -0
  43. package/src/config/services/ConfigMergeService.ts +386 -0
  44. package/src/config/services/ConfigValidationService.ts +394 -0
  45. package/src/config/services/SessionAuthService.ts +565 -0
  46. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  47. package/src/config/services/index.ts +29 -0
  48. package/src/interactiveCLI.ts +9 -7
  49. package/src/main.ts +2 -2
  50. package/src/shared/operationQueue.ts +1 -1
  51. package/src/utils/ClientFactory.ts +186 -0
  52. package/src/utils/getClientFromConfig.ts +4 -3
  53. package/src/utils/helperFunctions.ts +27 -7
  54. package/src/utils/yamlConverter.ts +4 -4
  55. package/src/utilsController.ts +99 -187
@@ -83,6 +83,7 @@ export const finalizeByAttributeMap = async (appwriteFolderPath, collection, ite
83
83
  export let numTimesFailedTotal = 0;
84
84
  /**
85
85
  * Tries to execute the given createFunction and retries up to 5 times if it fails.
86
+ * Only retries on transient errors (network failures, 5xx errors). Does NOT retry validation errors (4xx).
86
87
  *
87
88
  * @param {() => Promise<any>} createFunction - The function to be executed.
88
89
  * @param {number} [attemptNum=0] - The number of attempts made so far (default: 0).
@@ -93,11 +94,25 @@ export const tryAwaitWithRetry = async (createFunction, attemptNum = 0, throwErr
93
94
  return await createFunction();
94
95
  }
95
96
  catch (error) {
96
- if ((error instanceof AppwriteException &&
97
- (error.message.toLowerCase().includes("fetch failed") ||
98
- error.message.toLowerCase().includes("server error"))) ||
99
- (error.code === 522 || error.code === "522")) {
100
- if (error.code === 522) {
97
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
98
+ const errorCode = error.code;
99
+ // Check if this is a validation error that should NOT be retried
100
+ const isValidationError = errorCode === 400 || errorCode === 409 || errorCode === 422 ||
101
+ errorMessage.includes("already exists") ||
102
+ errorMessage.includes("attribute with the same key") ||
103
+ errorMessage.includes("invalid") && !errorMessage.includes("fetch failed") ||
104
+ errorMessage.includes("conflict") ||
105
+ errorMessage.includes("bad request");
106
+ // Check if this is a transient error that SHOULD be retried
107
+ const isTransientError = errorCode === 522 || errorCode === "522" || // Cloudflare error
108
+ errorCode >= 500 && errorCode < 600 || // 5xx server errors
109
+ errorMessage.includes("fetch failed") || // Network failures
110
+ errorMessage.includes("timeout") ||
111
+ errorMessage.includes("econnrefused") ||
112
+ errorMessage.includes("network error");
113
+ // Only retry if it's a transient error AND not a validation error
114
+ if (isTransientError && !isValidationError) {
115
+ if (errorCode === 522 || errorCode === "522") {
101
116
  console.log("Cloudflare error. Retrying...");
102
117
  }
103
118
  else {
@@ -110,6 +125,7 @@ export const tryAwaitWithRetry = async (createFunction, attemptNum = 0, throwErr
110
125
  await delay(2500);
111
126
  return tryAwaitWithRetry(createFunction, attemptNum + 1);
112
127
  }
128
+ // For validation errors or non-transient errors, throw immediately
113
129
  if (throwError) {
114
130
  throw error;
115
131
  }
@@ -14,7 +14,7 @@ export interface YamlCollectionData {
14
14
  size?: number;
15
15
  required?: boolean;
16
16
  array?: boolean;
17
- encrypt?: boolean;
17
+ encrypted?: boolean;
18
18
  default?: any;
19
19
  min?: number;
20
20
  max?: number;
@@ -32,7 +32,7 @@ export interface YamlCollectionData {
32
32
  size?: number;
33
33
  required?: boolean;
34
34
  array?: boolean;
35
- encrypt?: boolean;
35
+ encrypted?: boolean;
36
36
  default?: any;
37
37
  min?: number;
38
38
  max?: number;
@@ -39,9 +39,9 @@ export function collectionToYaml(collection, config = {
39
39
  yamlAttr.required = attr.required;
40
40
  if (attr.array !== undefined)
41
41
  yamlAttr.array = attr.array;
42
- // Always include encrypt field for string attributes (default to false)
42
+ // Always include encrypted field for string attributes (default to false)
43
43
  if (attr.type === 'string') {
44
- yamlAttr.encrypt = ('encrypted' in attr && attr.encrypted === true) ? true : false;
44
+ yamlAttr.encrypted = ('encrypted' in attr && attr.encrypted === true) ? true : false;
45
45
  }
46
46
  if ('xdefault' in attr && attr.xdefault !== undefined)
47
47
  yamlAttr.default = attr.xdefault;
@@ -19,8 +19,23 @@ export interface SetupOptions {
19
19
  shouldWriteFile?: boolean;
20
20
  }
21
21
  export declare class UtilsController {
22
+ private static instance;
23
+ private isInitialized;
24
+ /**
25
+ * Get the UtilsController singleton instance
26
+ */
27
+ static getInstance(currentUserDir: string, directConfig?: {
28
+ appwriteEndpoint?: string;
29
+ appwriteProject?: string;
30
+ appwriteKey?: string;
31
+ }): UtilsController;
32
+ /**
33
+ * Clear the singleton instance (useful for testing)
34
+ */
35
+ static clearInstance(): void;
22
36
  private appwriteFolderPath?;
23
37
  private appwriteConfigPath?;
38
+ private currentUserDir;
24
39
  config?: AppwriteConfig;
25
40
  appwriteServer?: Client;
26
41
  database?: Databases;
@@ -29,9 +44,6 @@ export declare class UtilsController {
29
44
  converterDefinitions: ConverterFunctions;
30
45
  validityRuleDefinitions: ValidationRules;
31
46
  afterImportActionsDefinitions: AfterImportActions;
32
- private sessionCookie?;
33
- private authMethod?;
34
- private sessionMetadata?;
35
47
  constructor(currentUserDir: string, directConfig?: {
36
48
  appwriteEndpoint?: string;
37
49
  appwriteProject?: string;
@@ -75,23 +87,14 @@ export declare class UtilsController {
75
87
  * Validates the current configuration for collections/tables conflicts
76
88
  */
77
89
  validateConfiguration(strictMode?: boolean): Promise<ValidationResult>;
78
- /**
79
- * Extract session information from the current authenticated client
80
- * This preserves session context for use across config reloads and adapter operations
81
- */
82
- private extractSessionInfo;
83
- /**
84
- * Create session preservation options for passing to loadConfig
85
- * Returns current session state to maintain authentication across config reloads
86
- */
87
- private createSessionPreservationOptions;
88
90
  /**
89
91
  * Get current session information for debugging/logging purposes
92
+ * Delegates to ConfigManager for session info
90
93
  */
91
- getSessionInfo(): {
94
+ getSessionInfo(): Promise<{
92
95
  hasSession: boolean;
93
96
  authMethod?: string;
94
97
  email?: string;
95
98
  expiresAt?: string;
96
- };
99
+ }>;
97
100
  }
@@ -16,20 +16,45 @@ import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferSt
16
16
  import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
17
17
  import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
18
18
  import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie } from "./utils/sessionAuth.js";
19
- import { createSessionPreservation } from "./utils/loadConfigs.js";
20
19
  import { fetchAllDatabases } from "./databases/methods.js";
21
20
  import { listFunctions, updateFunctionSpecifications, } from "./functions/methods.js";
22
21
  import chalk from "chalk";
23
22
  import { deployLocalFunction } from "./functions/deployments.js";
24
23
  import fs from "node:fs";
25
- import { configureLogging, updateLogger } from "./shared/logging.js";
24
+ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
26
25
  import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
27
26
  import { SchemaGenerator } from "./shared/schemaGenerator.js";
28
27
  import { findYamlConfig } from "./config/yamlConfig.js";
29
28
  import { validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode } from "./config/configValidation.js";
29
+ import { ConfigManager } from "./config/ConfigManager.js";
30
+ import { ClientFactory } from "./utils/ClientFactory.js";
30
31
  export class UtilsController {
32
+ // ──────────────────────────────────────────────────
33
+ // SINGLETON PATTERN
34
+ // ──────────────────────────────────────────────────
35
+ static instance = null;
36
+ isInitialized = false;
37
+ /**
38
+ * Get the UtilsController singleton instance
39
+ */
40
+ static getInstance(currentUserDir, directConfig) {
41
+ if (!UtilsController.instance) {
42
+ UtilsController.instance = new UtilsController(currentUserDir, directConfig);
43
+ }
44
+ return UtilsController.instance;
45
+ }
46
+ /**
47
+ * Clear the singleton instance (useful for testing)
48
+ */
49
+ static clearInstance() {
50
+ UtilsController.instance = null;
51
+ }
52
+ // ──────────────────────────────────────────────────
53
+ // INSTANCE FIELDS
54
+ // ──────────────────────────────────────────────────
31
55
  appwriteFolderPath;
32
56
  appwriteConfigPath;
57
+ currentUserDir;
33
58
  config;
34
59
  appwriteServer;
35
60
  database;
@@ -38,11 +63,8 @@ export class UtilsController {
38
63
  converterDefinitions = converterFunctions;
39
64
  validityRuleDefinitions = validationRules;
40
65
  afterImportActionsDefinitions = afterImportActions;
41
- // Session preservation fields
42
- sessionCookie;
43
- authMethod;
44
- sessionMetadata;
45
66
  constructor(currentUserDir, directConfig) {
67
+ this.currentUserDir = currentUserDir;
46
68
  const basePath = currentUserDir;
47
69
  if (directConfig) {
48
70
  let hasErrors = false;
@@ -107,96 +129,58 @@ export class UtilsController {
107
129
  }
108
130
  }
109
131
  async init(options = {}) {
110
- const { validate = false, strictMode = false, useSession = false, sessionCookie } = options;
111
- if (!this.config) {
112
- if (this.appwriteFolderPath && this.appwriteConfigPath) {
113
- MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
114
- try {
115
- const { config, actualConfigPath, validation } = await loadConfigWithPath(this.appwriteFolderPath, { validate, strictMode, reportValidation: false });
116
- this.config = config;
117
- MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
118
- // Report validation results if validation was requested
119
- if (validation && validate) {
120
- reportValidationResults(validation, { verbose: false });
121
- // In strict mode, throw if validation fails
122
- if (strictMode && !validation.isValid) {
123
- throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
124
- }
125
- }
126
- }
127
- catch (error) {
128
- MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
129
- return;
130
- }
131
- }
132
- else {
133
- MessageFormatter.error("No configuration available", undefined, { prefix: "Config" });
134
- return;
135
- }
132
+ const { validate = false, strictMode = false } = options;
133
+ const configManager = ConfigManager.getInstance();
134
+ // Load config if not already loaded
135
+ if (!configManager.hasConfig()) {
136
+ await configManager.loadConfig({
137
+ configDir: this.currentUserDir,
138
+ validate,
139
+ strictMode,
140
+ });
136
141
  }
142
+ const config = configManager.getConfig();
137
143
  // Configure logging based on config
138
- if (this.config.logging) {
139
- configureLogging(this.config.logging);
144
+ if (config.logging) {
145
+ configureLogging(config.logging);
140
146
  updateLogger();
141
147
  }
142
- // Use enhanced client with session authentication support
143
- // Pass session cookie from options if provided
144
- const clientSessionCookie = sessionCookie || this.sessionCookie;
145
- this.appwriteServer = getClientWithAuth(this.config.appwriteEndpoint, this.config.appwriteProject, this.config.appwriteKey || undefined, clientSessionCookie);
148
+ // Create client and adapter (session already in config from ConfigManager)
149
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
150
+ this.appwriteServer = client;
151
+ this.adapter = adapter;
152
+ this.config = config;
146
153
  this.database = new Databases(this.appwriteServer);
147
154
  this.storage = new Storage(this.appwriteServer);
148
155
  this.config.appwriteClient = this.appwriteServer;
149
- // Initialize adapter with version detection
150
- try {
151
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, clientSessionCookie);
152
- this.adapter = adapter;
153
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, {
154
- prefix: "Adapter"
155
- });
156
+ // Log only on FIRST initialization to avoid spam
157
+ if (!this.isInitialized) {
158
+ const apiMode = adapter.getApiMode();
159
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, { prefix: "Adapter" });
160
+ this.isInitialized = true;
156
161
  }
157
- catch (error) {
158
- MessageFormatter.warning('Database adapter initialization failed - some features may not work', { prefix: "Adapter" });
162
+ else {
163
+ logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
159
164
  }
160
- // Extract and store session information after successful authentication
161
- this.extractSessionInfo();
162
165
  }
163
166
  async reloadConfig() {
164
- if (!this.appwriteFolderPath) {
165
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
166
- return;
167
- }
168
- // Preserve session authentication during config reload
169
- const preserveAuth = this.createSessionPreservationOptions();
170
- this.config = await loadConfig(this.appwriteFolderPath, {
171
- preserveAuth
172
- });
173
- if (!this.config) {
174
- MessageFormatter.error("Failed to load config", undefined, { prefix: "Controller" });
175
- return;
176
- }
167
+ const configManager = ConfigManager.getInstance();
168
+ // Session preservation is automatic in ConfigManager
169
+ const config = await configManager.reloadConfig();
177
170
  // Configure logging based on updated config
178
- if (this.config.logging) {
179
- configureLogging(this.config.logging);
171
+ if (config.logging) {
172
+ configureLogging(config.logging);
180
173
  updateLogger();
181
174
  }
182
- // Use enhanced client with session authentication support, passing preserved session
183
- this.appwriteServer = getClientWithAuth(this.config.appwriteEndpoint, this.config.appwriteProject, this.config.appwriteKey || undefined, this.sessionCookie);
175
+ // Recreate client and adapter
176
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
177
+ this.appwriteServer = client;
178
+ this.adapter = adapter;
179
+ this.config = config;
184
180
  this.database = new Databases(this.appwriteServer);
185
181
  this.storage = new Storage(this.appwriteServer);
186
182
  this.config.appwriteClient = this.appwriteServer;
187
- // Re-initialize adapter with version detection after config reload
188
- try {
189
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
190
- this.adapter = adapter;
191
- MessageFormatter.info(`Database adapter re-initialized (apiMode: ${apiMode})`, {
192
- prefix: "Adapter"
193
- });
194
- }
195
- catch (error) {
196
- MessageFormatter.warning('Database adapter re-initialization failed - some features may not work', { prefix: "Adapter" });
197
- }
198
- // Re-extract session information after reload
199
- this.extractSessionInfo();
183
+ logger.debug("Config reloaded, adapter refreshed", { prefix: "UtilsController" });
200
184
  }
201
185
  async ensureDatabaseConfigBucketsExist(databases = []) {
202
186
  await this.init();
@@ -339,7 +323,8 @@ export class UtilsController {
339
323
  if (!this.database || !this.config)
340
324
  throw new Error("Database not initialized");
341
325
  try {
342
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
326
+ // Session is already in config from ConfigManager
327
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
343
328
  if (apiMode === 'tablesdb') {
344
329
  await wipeAllTables(adapter, database.$id);
345
330
  }
@@ -382,7 +367,8 @@ export class UtilsController {
382
367
  if (!this.database || !this.config)
383
368
  throw new Error("Database not initialized");
384
369
  try {
385
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
370
+ // Session is already in config from ConfigManager
371
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
386
372
  if (apiMode === 'tablesdb') {
387
373
  await wipeTableRows(adapter, database.$id, collection.$id);
388
374
  }
@@ -618,59 +604,25 @@ export class UtilsController {
618
604
  return validation;
619
605
  }
620
606
  /**
621
- * Extract session information from the current authenticated client
622
- * This preserves session context for use across config reloads and adapter operations
607
+ * Get current session information for debugging/logging purposes
608
+ * Delegates to ConfigManager for session info
623
609
  */
624
- extractSessionInfo() {
625
- if (!this.config) {
626
- return;
627
- }
628
- // Try to extract session from current config first
629
- if (this.config.sessionCookie && isValidSessionCookie(this.config.sessionCookie)) {
630
- this.sessionCookie = this.config.sessionCookie;
631
- this.authMethod = "session";
632
- this.sessionMetadata = this.config.sessionMetadata;
633
- MessageFormatter.debug("Extracted session from config", { prefix: "Session" });
634
- return;
635
- }
636
- // Fall back to finding session from Appwrite CLI preferences
637
- const sessionAuth = findSessionByEndpointAndProject(this.config.appwriteEndpoint, this.config.appwriteProject);
638
- if (sessionAuth && isValidSessionCookie(sessionAuth.sessionCookie)) {
639
- this.sessionCookie = sessionAuth.sessionCookie;
640
- this.authMethod = "session";
641
- this.sessionMetadata = {
642
- email: sessionAuth.email
610
+ async getSessionInfo() {
611
+ const configManager = ConfigManager.getInstance();
612
+ try {
613
+ const authStatus = await configManager.getAuthStatus();
614
+ return {
615
+ hasSession: authStatus.hasValidSession,
616
+ authMethod: authStatus.authMethod,
617
+ email: authStatus.sessionInfo?.email,
618
+ expiresAt: authStatus.sessionInfo?.expiresAt
643
619
  };
644
- MessageFormatter.debug(`Extracted session from CLI preferences for ${sessionAuth.email || 'unknown user'}`, { prefix: "Session" });
645
- return;
646
620
  }
647
- // No session found, using API key authentication
648
- if (this.config.appwriteKey) {
649
- this.authMethod = "apikey";
650
- this.sessionCookie = undefined;
651
- this.sessionMetadata = undefined;
652
- MessageFormatter.debug("Using API key authentication", { prefix: "Session" });
653
- }
654
- }
655
- /**
656
- * Create session preservation options for passing to loadConfig
657
- * Returns current session state to maintain authentication across config reloads
658
- */
659
- createSessionPreservationOptions() {
660
- if (!this.sessionCookie || !isValidSessionCookie(this.sessionCookie)) {
661
- return undefined;
621
+ catch (error) {
622
+ // If config not loaded, return empty status
623
+ return {
624
+ hasSession: false
625
+ };
662
626
  }
663
- return createSessionPreservation(this.sessionCookie, this.sessionMetadata?.email, this.sessionMetadata?.expiresAt);
664
- }
665
- /**
666
- * Get current session information for debugging/logging purposes
667
- */
668
- getSessionInfo() {
669
- return {
670
- hasSession: !!this.sessionCookie && isValidSessionCookie(this.sessionCookie),
671
- authMethod: this.authMethod,
672
- email: this.sessionMetadata?.email,
673
- expiresAt: this.sessionMetadata?.expiresAt
674
- };
675
627
  }
676
628
  }
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.6.3",
4
+ "version": "1.6.4",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -14,6 +14,8 @@ import {
14
14
  import { createEmptyCollection } from "../../utils/setupFiles.js";
15
15
  import chalk from "chalk";
16
16
  import type { InteractiveCLI } from "../../interactiveCLI.js";
17
+ import { ConfigManager } from "../../config/ConfigManager.js";
18
+ import { UtilsController } from "../../utilsController.js";
17
19
 
18
20
  export const configCommands = {
19
21
  async migrateTypeScriptConfig(cli: InteractiveCLI): Promise<void> {
@@ -23,6 +25,10 @@ export const configCommands = {
23
25
  // Perform the migration
24
26
  await migrateConfig((cli as any).currentDir);
25
27
 
28
+ // Clear instances after migration to reload new config
29
+ UtilsController.clearInstance();
30
+ ConfigManager.resetInstance();
31
+
26
32
  // Reset the detection flag
27
33
  (cli as any).isUsingTypeScriptConfig = false;
28
34
 
@@ -173,7 +179,8 @@ export const configCommands = {
173
179
  }
174
180
 
175
181
  // Reinitialize controller with session preservation
176
- (cli as any).controller = new UtilsController((cli as any).currentDir, directConfig);
182
+ UtilsController.clearInstance();
183
+ (cli as any).controller = UtilsController.getInstance((cli as any).currentDir, directConfig);
177
184
  await (cli as any).controller.init();
178
185
 
179
186
  MessageFormatter.success("Configuration reloaded with session preserved", { prefix: "Config" });