Skip to content

feat: replace cosmiconfig with c12 for configuration management #262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"license": "MIT",
"dependencies": {
"@sentry/node": "^9.3.0",
"c12": "^3.0.2",
"chalk": "^5",
"cosmiconfig": "^9.0.0",
"deepmerge": "^4.3.1",
"dotenv": "^16",
"mycoder-agent": "workspace:*",
Expand Down
65 changes: 40 additions & 25 deletions packages/cli/src/settings/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cosmiconfig } from 'cosmiconfig';
import { loadConfig as loadC12Config, watchConfig } from 'c12';
import { ArgumentsCamelCase } from 'yargs';

import { SharedOptions } from '../options';
Expand Down Expand Up @@ -107,12 +107,6 @@ export const getConfigFromArgv = (argv: ArgumentsCamelCase<SharedOptions>) => {
};
};

function removeUndefined(obj: any) {
return Object.fromEntries(
Object.entries(obj).filter(([_, value]) => value !== undefined),
);
}

/**
* Validates custom commands configuration
* @param config The configuration object
Expand All @@ -138,32 +132,53 @@ function validateCustomCommands(config: Config): void {
});
}
/**
* Load configuration using cosmiconfig
* Load configuration using c12
* @returns Merged configuration with default values
*/
export async function loadConfig(
cliOptions: Partial<Config> = {},
): Promise<Config> {
// Initialize cosmiconfig
const explorer = cosmiconfig('mycoder', {
searchStrategy: 'global',
// Load configuration using c12
const { config } = await loadC12Config({
name: 'mycoder',
defaults: defaultConfig,
overrides: cliOptions,
// Optionally enable .env support
// dotenv: true,
});

// Search for configuration file
const result = await explorer.search();

// Merge configurations with precedence: default < file < cli
const fileConfig = result?.config || {};
// Convert to Config type and validate custom commands
const typedConfig = config as unknown as Config;
validateCustomCommands(typedConfig);

// Return merged configuration
const mergedConfig = {
...defaultConfig,
...removeUndefined(fileConfig),
...removeUndefined(cliOptions),
};
return typedConfig;
}

// Validate custom commands if present
validateCustomCommands(mergedConfig);
/**
* Watch configuration for changes
* @param cliOptions CLI options to override configuration
* @param onUpdate Callback when configuration is updated
*/
export async function watchConfigForChanges(
cliOptions: Partial<Config> = {},
onUpdate?: (config: Config) => void,
) {
const { config, watchingFiles, unwatch } = await watchConfig({
name: 'mycoder',
defaults: defaultConfig,
overrides: cliOptions,
onUpdate: ({ newConfig }) => {
const typedConfig = newConfig as unknown as Config;
validateCustomCommands(typedConfig);
if (onUpdate) {
onUpdate(typedConfig);
}
},
});

return mergedConfig;
return {
config: config as unknown as Config,
watchingFiles,
unwatch,
};
}
Loading