diff --git a/bin/smartcommit.ts b/bin/smartcommit.ts index 04817cc..828664c 100644 --- a/bin/smartcommit.ts +++ b/bin/smartcommit.ts @@ -1,7 +1,9 @@ #!/usr/bin/env node import { program } from "commander"; +import chalk from "chalk"; import { run } from "../src/index"; +import { CancellationError } from "../src/utils/errors"; import pkg from "../package.json"; program .name("smartcommit") @@ -11,7 +13,22 @@ program .option("--auto", "Auto accept commit without confirmation") .option("--model ", "Specify Ollama model"); program.action(async (options) => { - await run(options); + try { + await run(options); + process.exit(0); + } catch (error: unknown) { + if (error instanceof CancellationError) { + console.log(error.message); + process.exit(0); + } + + if (error instanceof Error) { + console.error(chalk.red(error.message)); + } else { + console.error(chalk.red("An unknown error occurred.")); + } + process.exit(1); + } }); program.parse(); diff --git a/src/index.test.ts b/src/index.test.ts index ef99784..5d47053 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; import { detectScope } from './analyzer/scopeDetector'; +import { ValidationError, CancellationError } from './utils/errors'; describe('detectScope', () => { it('should detect scope from src directory', () => { @@ -17,3 +18,17 @@ describe('detectScope', () => { expect(detectScope(files)).toBe('ui'); }); }); + +describe('Errors', () => { + it('should create ValidationError with correct name and message', () => { + const error = new ValidationError('test error'); + expect(error.name).toBe('ValidationError'); + expect(error.message).toBe('test error'); + }); + + it('should create CancellationError with default message', () => { + const error = new CancellationError(); + expect(error.name).toBe('CancellationError'); + expect(error.message).toBe('Commit cancelled.'); + }); +}); diff --git a/src/index.ts b/src/index.ts index 1d75832..f0237e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import { commit } from "./git/commit"; import { enhanceCommit } from "./llm/ollamaEnhancer"; import { loadConfig } from "./config/loadConfig"; import { isOllamaRunning, getBestModel } from "./llm/checkOllama"; +import { ValidationError, CancellationError } from "./utils/errors"; interface CliOptions { ai?: boolean; @@ -25,16 +26,13 @@ export async function run(options: CliOptions) { // Ensure we are inside a Git repo const repo = await isGitRepo(); if (!repo) { - console.log(chalk.red("Not inside a Git repository.")); - process.exit(1); + throw new ValidationError("Not inside a Git repository."); } const stagedFiles = await getStagedFiles(); if (stagedFiles.length === 0) { - console.log(chalk.yellow("No staged changes found.")); - console.log("Stage changes using: git add "); - process.exit(0); + throw new ValidationError("No staged changes found.\nStage changes using: git add "); } // Enrich file stats @@ -98,8 +96,7 @@ export async function run(options: CliOptions) { const result = await confirmCommit(commitMessage); if (!result) { - console.log("Commit cancelled."); - process.exit(0); + throw new CancellationError(); } finalMessage = result; diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..c6c9f1f --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,20 @@ +export class GitBunError extends Error { + constructor(message: string) { + super(message); + this.name = "GitBunError"; + } +} + +export class ValidationError extends GitBunError { + constructor(message: string) { + super(message); + this.name = "ValidationError"; + } +} + +export class CancellationError extends GitBunError { + constructor(message: string = "Commit cancelled.") { + super(message); + this.name = "CancellationError"; + } +}