diff --git a/src/cli.ts b/src/cli.ts index d30a3c3..54f3515 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,9 +21,14 @@ cli( flags: { generate: { type: Number, - description: 'Number of messages to generate. (Warning: generating multiple costs more) (default: 1)', + description: 'Number of messages to generate (Warning: generating multiple costs more) (default: 1)', alias: 'g', }, + exclude: { + type: [String], + description: 'Files to exclude from AI analysis', + alias: 'x', + }, }, commands: [ @@ -43,6 +48,7 @@ cli( } else { aicommits( argv.flags.generate, + argv.flags.exclude, rawArgv, ); } diff --git a/src/commands/aicommits.ts b/src/commands/aicommits.ts index 9902348..2ca21b7 100644 --- a/src/commands/aicommits.ts +++ b/src/commands/aicommits.ts @@ -16,15 +16,15 @@ import { KnownError, handleCliError } from '../utils/error.js'; export default async ( generate: number | undefined, + excludeFiles: string[], rawArgv: string[], ) => (async () => { intro(bgCyan(black(' aicommits '))); - await assertGitRepo(); const detectingFiles = spinner(); detectingFiles.start('Detecting staged files'); - const staged = await getStagedDiff(); + const staged = await getStagedDiff(excludeFiles); if (!staged) { detectingFiles.stop('Detecting staged files'); diff --git a/src/utils/git.ts b/src/utils/git.ts index 943f91d..65bb7fb 100644 --- a/src/utils/git.ts +++ b/src/utils/git.ts @@ -9,19 +9,30 @@ export const assertGitRepo = async () => { } }; -const excludeFromDiff = [ +const excludeFromDiff = (path: string) => `:(exclude)${path}`; + +const filesToExclude = [ 'package-lock.json', 'pnpm-lock.yaml', // yarn.lock, Cargo.lock, Gemfile.lock, Pipfile.lock, etc. '*.lock', -].map(file => `:(exclude)${file}`); +].map(excludeFromDiff); -export const getStagedDiff = async () => { +export const getStagedDiff = async (excludeFiles?: string[]) => { const diffCached = ['diff', '--cached']; const { stdout: files } = await execa( 'git', - [...diffCached, '--name-only', ...excludeFromDiff], + [ + ...diffCached, + '--name-only', + ...filesToExclude, + ...( + excludeFiles + ? excludeFiles.map(excludeFromDiff) + : [] + ), + ], ); if (!files) { @@ -30,7 +41,10 @@ export const getStagedDiff = async () => { const { stdout: diff } = await execa( 'git', - [...diffCached, ...excludeFromDiff], + [ + ...diffCached, + ...filesToExclude, + ], ); return { diff --git a/tests/specs/cli.ts b/tests/specs/cli.ts index 3d6dab6..fd3ebe1 100644 --- a/tests/specs/cli.ts +++ b/tests/specs/cli.ts @@ -43,18 +43,23 @@ export default testSuite(({ describe }) => { expect(stdout).toMatch('No staged changes found. Make sure to stage your changes with `git add`.'); }); - await test('Generates commit message', async () => { + await aicommits([ + 'config', + 'set', + `OPENAI_KEY=${OPENAI_KEY}`, + ]); + + await test('Excludes files', async () => { await git('add', ['data.json']); - - await aicommits([ - 'config', - 'set', - `OPENAI_KEY=${OPENAI_KEY}`, - ]); - const statusBefore = await git('status', ['--porcelain', '--untracked-files=no']); expect(statusBefore.stdout).toBe('A data.json'); + const { stdout, exitCode } = await aicommits(['--exclude', 'data.json'], { reject: false }); + expect(exitCode).toBe(1); + expect(stdout).toMatch('No staged changes found. Make sure to stage your changes with `git add`.'); + }); + + await test('Generates commit message', async () => { const committing = aicommits(); committing.stdout!.on('data', (buffer: Buffer) => { const stdout = buffer.toString();