Skip to content

Commit

Permalink
feat: show error stack on unknown error (#124)
Browse files Browse the repository at this point in the history
* feat: show error stack on unknown error

* improve message
  • Loading branch information
privatenumber committed Mar 3, 2023
1 parent 915a4ad commit 5fe127d
Show file tree
Hide file tree
Showing 8 changed files with 45 additions and 14 deletions.
6 changes: 4 additions & 2 deletions src/commands/aicommits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '../utils/git.js';
import { getConfig } from '../utils/config.js';
import { generateCommitMessage } from '../utils/openai.js';
import { KnownError, handleCliError } from '../utils/error.js';

export default async (
generate: number,
Expand All @@ -26,7 +27,7 @@ export default async (
const staged = await getStagedDiff();

if (!staged) {
throw new Error('No staged changes found. Make sure to stage your changes with `git add`.');
throw new KnownError('No staged changes found. Make sure to stage your changes with `git add`.');
}

detectingFiles.stop(`${getDetectedMessage(staged.files)}:\n${
Expand All @@ -36,7 +37,7 @@ export default async (
const config = await getConfig();
const OPENAI_KEY = process.env.OPENAI_KEY ?? process.env.OPENAI_API_KEY ?? config.OPENAI_KEY;
if (!OPENAI_KEY) {
throw new Error('Please set your OpenAI API key in ~/.aicommits');
throw new KnownError('Please set your OpenAI API key via `aicommits config set OPENAI_KEY=<your token>`');
}

const s = spinner();
Expand Down Expand Up @@ -78,5 +79,6 @@ export default async (
outro(`${green('✔')} Successfully committed!`);
})().catch((error) => {
outro(`${red('✖')} ${error.message}`);
handleCliError(error);
process.exit(1);
});
4 changes: 3 additions & 1 deletion src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { command } from 'cleye';
import { red } from 'kolorist';
import { getConfig, setConfigs } from '../utils/config.js';
import { KnownError, handleCliError } from '../utils/error.js';

export default command({
name: 'config',
Expand All @@ -25,9 +26,10 @@ export default command({
return;
}

throw new Error(`Invalid mode: ${mode}`);
throw new KnownError(`Invalid mode: ${mode}`);
})().catch((error) => {
console.error(`${red('✖')} ${error.message}`);
handleCliError(error);
process.exit(1);
});
});
6 changes: 4 additions & 2 deletions src/commands/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { green, red } from 'kolorist';
import { command } from 'cleye';
import { assertGitRepo } from '../utils/git.js';
import { fileExists } from '../utils/fs.js';
import { KnownError, handleCliError } from '../utils/error.js';

const hookName = 'prepare-commit-msg';
const symlinkPath = `.git/hooks/${hookName}`;
Expand Down Expand Up @@ -32,7 +33,7 @@ export default command({
console.warn('The hook is already installed');
return;
}
throw new Error(`A different ${hookName} hook seems to be installed. Please remove it before installing aicommits.`);
throw new KnownError(`A different ${hookName} hook seems to be installed. Please remove it before installing aicommits.`);
}

await fs.mkdir(path.dirname(symlinkPath), { recursive: true });
Expand All @@ -58,9 +59,10 @@ export default command({
return;
}

throw new Error(`Invalid mode: ${mode}`);
throw new KnownError(`Invalid mode: ${mode}`);
})().catch((error) => {
console.error(`${red('✖')} ${error.message}`);
handleCliError(error);
process.exit(1);
});
});
6 changes: 4 additions & 2 deletions src/commands/prepare-commit-msg-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {
import { getStagedDiff } from '../utils/git.js';
import { getConfig } from '../utils/config.js';
import { generateCommitMessage } from '../utils/openai.js';
import { KnownError, handleCliError } from '../utils/error.js';

const [messageFilePath, commitSource] = process.argv.slice(2);

export default () => (async () => {
if (!messageFilePath) {
throw new Error('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');
throw new KnownError('Commit message file path is missing. This file should be called from the "prepare-commit-msg" git hook');
}

// If a commit message is passed in, ignore
Expand All @@ -31,7 +32,7 @@ export default () => (async () => {

const { OPENAI_KEY, generate } = await getConfig();
if (!OPENAI_KEY) {
throw new Error('Please set your OpenAI API key in ~/.aicommits');
throw new KnownError('Please set your OpenAI API key in ~/.aicommits');
}

const s = spinner();
Expand Down Expand Up @@ -61,5 +62,6 @@ export default () => (async () => {
outro(`${green('✔')} Saved commit message!`);
})().catch((error) => {
outro(`${red('✖')} ${error.message}`);
handleCliError(error);
process.exit(1);
});
5 changes: 3 additions & 2 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import path from 'path';
import os from 'os';
import ini from 'ini';
import { fileExists } from './fs.js';
import { KnownError } from './error.js';

const parseAssert = (
name: string,
condition: any,
message: string,
) => {
if (!condition) {
throw new Error(`Invalid config property ${name}: ${message}`);
throw new KnownError(`Invalid config property ${name}: ${message}`);
}
};

Expand Down Expand Up @@ -67,7 +68,7 @@ export const setConfigs = async (

for (const [key, value] of keyValues) {
if (!hasOwn(configParsers, key)) {
throw new Error(`Invalid config property: ${key}`);
throw new KnownError(`Invalid config property: ${key}`);
}

const parsed = configParsers[key as ValidKeys](value);
Expand Down
20 changes: 20 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { dim } from 'kolorist';
import { version } from '../../package.json';

export class KnownError extends Error {}

const indent = ' ';

export const handleCliError = (error: any) => {
if (
error instanceof Error
&& !(error instanceof KnownError)
) {
if (error.stack) {
console.error(dim(error.stack.split('\n').slice(1).join('\n')));
}
console.error(`\n${indent}${dim(`aicommits v${version}`)}`);
console.error(`\n${indent}Please open a Bug report with the information above:`);
console.error(`${indent}https://github.com/Nutlope/aicommits/issues/new/choose`);
}
};
3 changes: 2 additions & 1 deletion src/utils/git.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { execa } from 'execa';
import { KnownError } from './error.js';

export const assertGitRepo = async () => {
const { stdout } = await execa('git', ['rev-parse', '--is-inside-work-tree'], { reject: false });

if (stdout !== 'true') {
throw new Error('The current directory must be a Git repository!');
throw new KnownError('The current directory must be a Git repository!');
}
};

Expand Down
9 changes: 5 additions & 4 deletions src/utils/openai.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import https from 'https';
import type { CreateCompletionRequest, CreateCompletionResponse } from 'openai';
import { encoding_for_model as encodingForModel } from '@dqbd/tiktoken';
import { KnownError } from './error.js';

const createCompletion = (
apiKey: string,
Expand Down Expand Up @@ -31,7 +32,7 @@ const createCompletion = (
errorMessage += '; Check the API status: https://status.openai.com';
}

return reject(new Error(errorMessage));
return reject(new KnownError(errorMessage));
}

const body: Buffer[] = [];
Expand All @@ -46,7 +47,7 @@ const createCompletion = (
request.on('error', reject);
request.on('timeout', () => {
request.destroy();
reject(new Error('Request timed out'));
reject(new KnownError('Request timed out'));
});

request.write(postContent);
Expand Down Expand Up @@ -74,7 +75,7 @@ export const generateCommitMessage = async (
* https://platform.openai.com/docs/models/overview#:~:text=to%20Sep%202021-,text%2Ddavinci%2D003,-Can%20do%20any
*/
if (encoder.encode(prompt).length > 4000) {
throw new Error('The diff is too large for the OpenAI API. Try reducing the number of staged changes, or write your own commit message.');
throw new KnownError('The diff is too large for the OpenAI API. Try reducing the number of staged changes, or write your own commit message.');
}

try {
Expand All @@ -97,7 +98,7 @@ export const generateCommitMessage = async (
} catch (error) {
const errorAsAny = error as any;
if (errorAsAny.code === 'ENOTFOUND') {
throw new Error(`Error connecting to ${errorAsAny.hostname} (${errorAsAny.syscall}). Are you connected to the internet?`);
throw new KnownError(`Error connecting to ${errorAsAny.hostname} (${errorAsAny.syscall}). Are you connected to the internet?`);
}

throw errorAsAny;
Expand Down

0 comments on commit 5fe127d

Please sign in to comment.