From be462f6498c9330cda93628fd0381cd9f6e5555e Mon Sep 17 00:00:00 2001 From: Hiroki Osame Date: Thu, 9 Mar 2023 21:50:52 +0900 Subject: [PATCH] refactor: reuse config validators on cli config --- src/cli.ts | 3 +- src/commands/aicommits.ts | 18 +++---- src/commands/prepare-commit-msg-hook.ts | 11 ++-- src/utils/config.ts | 71 +++++++++++++++---------- 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 18c3863..d30a3c3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,9 +21,8 @@ cli( flags: { generate: { type: Number, - description: 'Number of messages to generate. (Warning: generating multiple costs more)', + description: 'Number of messages to generate. (Warning: generating multiple costs more) (default: 1)', alias: 'g', - default: 1, }, }, diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index 0313474..b30415b 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -15,7 +15,7 @@ import { generateCommitMessage } from '../utils/openai.js'; import { KnownError, handleCliError } from '../utils/error.js'; export default async ( - generate: number, + generate: number | undefined, rawArgv: string[], ) => (async () => { intro(bgCyan(black(' aicommits '))); @@ -34,20 +34,18 @@ export default async ( staged.files.map(file => ` ${file}`).join('\n') }`); - const config = await getConfig(); - const OPENAI_KEY = process.env.OPENAI_KEY ?? process.env.OPENAI_API_KEY ?? config.OPENAI_KEY; - const locale = config.locale ?? 'en'; - if (!OPENAI_KEY) { - throw new KnownError('Please set your OpenAI API key via `aicommits config set OPENAI_KEY=`'); - } + const config = await getConfig({ + OPENAI_KEY: process.env.OPENAI_KEY ?? process.env.OPENAI_API_KEY, + generate: generate?.toString(), + }); const s = spinner(); s.start('The AI is analyzing your changes'); const messages = await generateCommitMessage( - OPENAI_KEY, - locale, + config.OPENAI_KEY, + config.locale, staged.diff, - generate, + config.generate, ); s.stop('Changes analyzed'); diff --git a/src/commands/prepare-commit-msg-hook.ts b/src/commands/prepare-commit-msg-hook.ts index 6f0cdf2..7652abd 100644 --- a/src/commands/prepare-commit-msg-hook.ts +++ b/src/commands/prepare-commit-msg-hook.ts @@ -30,18 +30,15 @@ export default () => (async () => { intro(bgCyan(black(' aicommits '))); - const { OPENAI_KEY, locale = 'en', generate } = await getConfig(); - if (!OPENAI_KEY) { - throw new KnownError('Please set your OpenAI API key in ~/.aicommits'); - } + const config = await getConfig(); const s = spinner(); s.start('The AI is analyzing your changes'); const messages = await generateCommitMessage( - OPENAI_KEY, - locale, + config.OPENAI_KEY, + config.locale, staged!.diff, - generate || 1, + config.generate, ); s.stop('Changes analyzed'); diff --git a/src/utils/config.ts b/src/utils/config.ts index b52ebde..445e594 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -19,24 +19,33 @@ const parseAssert = ( }; const configParsers = { - OPENAI_KEY(key: string) { - parseAssert('OPENAI_KEY', key, 'Cannot be empty'); + OPENAI_KEY(key?: string) { + if (!key) { + throw new KnownError('Please set your OpenAI API key via `aicommits config set OPENAI_KEY=`'); + } parseAssert('OPENAI_KEY', key.startsWith('sk-'), 'Must start with "sk-"'); parseAssert('OPENAI_KEY', key.length === 51, 'Must be 51 characters long'); return key; }, - locale(key: string) { - parseAssert('locale', key, 'Cannot be empty'); - parseAssert('locale', /^[a-z-]+$/i.test(key), 'Must be a valid locale (letters and dashes/underscores). You can consult the list of codes in: https://wikipedia.org/wiki/List_of_ISO_639-1_codes'); + locale(locale?: string) { + if (!locale) { + return 'en'; + } - return key; + parseAssert('locale', locale, 'Cannot be empty'); + parseAssert('locale', /^[a-z-]+$/i.test(locale), 'Must be a valid locale (letters and dashes/underscores). You can consult the list of codes in: https://wikipedia.org/wiki/List_of_ISO_639-1_codes'); + + return locale; }, - generate(key: string) { - parseAssert('generate', key, 'Cannot be empty'); - parseAssert('generate', /^\d+$/.test(key), 'Must be an integer'); + generate(count?: string) { + if (!count) { + return 1; + } - const parsed = Number(key); + parseAssert('generate', /^\d+$/.test(count), 'Must be an integer'); + + const parsed = Number(count); parseAssert('generate', parsed > 0, 'Must be greater than 0'); parseAssert('generate', parsed <= 5, 'Must be less or equal to 5'); @@ -44,45 +53,53 @@ const configParsers = { }, } as const; -type ValidKeys = keyof typeof configParsers; -type ConfigType = { - [key in ValidKeys]?: ReturnType; +type ConfigKeys = keyof typeof configParsers; + +type RawConfig = { + [key in ConfigKeys]?: string; +}; + +type ValidConfig = { + [Key in ConfigKeys]: ReturnType; }; const configPath = path.join(os.homedir(), '.aicommits'); -export const getConfig = async (): Promise => { +const readConfigFile = async (): Promise => { const configExists = await fileExists(configPath); if (!configExists) { - return {}; + return Object.create(null); } const configString = await fs.readFile(configPath, 'utf8'); - const config = ini.parse(configString); - for (const key of Object.keys(config)) { - if (hasOwn(configParsers, key)) { - const parsed = configParsers[key as ValidKeys](config[key]); - config[key as ValidKeys] = parsed; - } else { - console.warn(`\n⚠️ Unknown config property "${key}" found in ${configPath}`); - } + return ini.parse(configString); +}; + +export const getConfig = async (cliConfig?: RawConfig): Promise => { + const config = await readConfigFile(); + const parsedConfig: Record = {}; + + for (const key of Object.keys(configParsers) as ConfigKeys[]) { + const parser = configParsers[key]; + const value = cliConfig?.[key] ?? config[key]; + parsedConfig[key] = parser(value); } - return config; + return parsedConfig as ValidConfig; }; export const setConfigs = async ( keyValues: [key: string, value: string][], ) => { - const config = await getConfig(); + const config = await readConfigFile(); for (const [key, value] of keyValues) { if (!hasOwn(configParsers, key)) { throw new KnownError(`Invalid config property: ${key}`); } - const parsed = configParsers[key as ValidKeys](value); - config[key as ValidKeys] = parsed as any; + const parsed = configParsers[key as ConfigKeys](value); + config[key as ConfigKeys] = parsed as any; } await fs.writeFile(configPath, ini.stringify(config), 'utf8');