formatting and fixed type error

This commit is contained in:
Hassan El Mghari
2024-01-26 10:02:33 -08:00
parent 723c171417
commit 604def8284
8 changed files with 165 additions and 113 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
github: privatenumber

View File

@@ -13,7 +13,6 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@@ -171,7 +171,7 @@ export const generateCommitMessage = async (
return deduplicateMessages( return deduplicateMessages(
completion.choices completion.choices
.filter((choice) => choice.message?.content) .filter((choice) => choice.message?.content)
.map((choice) => sanitizeMessage(choice.message!.content)) .map((choice) => sanitizeMessage(choice.message!.content as string))
); );
} catch (error) { } catch (error) {
const errorAsAny = error as any; const errorAsAny = error as any;

View File

@@ -9,7 +9,9 @@ import {
export default testSuite(({ describe }) => { export default testSuite(({ describe }) => {
if (process.platform === 'win32') { if (process.platform === 'win32') {
// https://github.com/nodejs/node/issues/31409 // https://github.com/nodejs/node/issues/31409
console.warn('Skipping tests on Windows because Node.js spawn cant open TTYs'); console.warn(
'Skipping tests on Windows because Node.js spawn cant open TTYs'
);
return; return;
} }
@@ -21,10 +23,15 @@ export default testSuite(({ describe }) => {
const git = await createGit(fixture.path); const git = await createGit(fixture.path);
await git('add', ['data.json']); await git('add', ['data.json']);
const statusBefore = await git('status', ['--porcelain', '--untracked-files=no']); const statusBefore = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusBefore.stdout).toBe('A data.json'); expect(statusBefore.stdout).toBe('A data.json');
const { stdout, exitCode } = await aicommits(['--exclude', 'data.json'], { reject: false }); const { stdout, exitCode } = await aicommits(['--exclude', 'data.json'], {
reject: false,
});
expect(exitCode).toBe(1); expect(exitCode).toBe(1);
expect(stdout).toMatch('No staged changes found.'); expect(stdout).toMatch('No staged changes found.');
await fixture.rm(); await fixture.rm();
@@ -47,10 +54,15 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -81,7 +93,9 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -118,7 +132,10 @@ export default testSuite(({ describe }) => {
const statusAfter = await git('status', ['--short']); const statusAfter = await git('status', ['--short']);
expect(statusAfter.stdout).toBe('?? .aicommits'); expect(statusAfter.stdout).toBe('?? .aicommits');
const { stdout: commitMessage } = await git('log', ['-n1', '--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'-n1',
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -128,7 +145,9 @@ export default testSuite(({ describe }) => {
await fixture.rm(); await fixture.rm();
}); });
test('Accepts --generate flag, overriding config', async ({ onTestFail }) => { test('Accepts --generate flag, overriding config', async ({
onTestFail,
}) => {
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
'.aicommits': `${files['.aicommits']}\ngenerate=4`, '.aicommits': `${files['.aicommits']}\ngenerate=4`,
@@ -138,9 +157,7 @@ export default testSuite(({ describe }) => {
await git('add', ['data.json']); await git('add', ['data.json']);
// Generate flag should override generate config // Generate flag should override generate config
const committing = aicommits([ const committing = aicommits(['--generate', '2']);
'--generate', '2',
]);
// Hit enter to accept the commit message // Hit enter to accept the commit message
committing.stdout!.on('data', function onPrompt(buffer: Buffer) { committing.stdout!.on('data', function onPrompt(buffer: Buffer) {
@@ -158,10 +175,15 @@ export default testSuite(({ describe }) => {
onTestFail(() => console.log({ stdout })); onTestFail(() => console.log({ stdout }));
expect(countChoices).toBe(2); expect(countChoices).toBe(2);
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -173,7 +195,8 @@ export default testSuite(({ describe }) => {
test('Generates Japanese commit message via locale config', async () => { test('Generates Japanese commit message via locale config', async () => {
// https://stackoverflow.com/a/15034560/911407 // https://stackoverflow.com/a/15034560/911407
const japanesePattern = /[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/; const japanesePattern =
/[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/;
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
@@ -195,10 +218,15 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -211,7 +239,8 @@ export default testSuite(({ describe }) => {
describe('commit types', ({ test }) => { describe('commit types', ({ test }) => {
test('Should not use conventional commits by default', async () => { test('Should not use conventional commits by default', async () => {
const conventionalCommitPattern = /(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/; const conventionalCommitPattern =
/(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/;
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
}); });
@@ -231,7 +260,10 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--oneline']); const { stdout: commitMessage } = await git('log', ['--oneline']);
@@ -242,7 +274,8 @@ export default testSuite(({ describe }) => {
}); });
test('Conventional commits', async () => { test('Conventional commits', async () => {
const conventionalCommitPattern = /(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/; const conventionalCommitPattern =
/(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/;
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
'.aicommits': `${files['.aicommits']}\ntype=conventional`, '.aicommits': `${files['.aicommits']}\ntype=conventional`,
@@ -263,7 +296,10 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--oneline']); const { stdout: commitMessage } = await git('log', ['--oneline']);
@@ -274,7 +310,8 @@ export default testSuite(({ describe }) => {
}); });
test('Accepts --type flag, overriding config', async () => { test('Accepts --type flag, overriding config', async () => {
const conventionalCommitPattern = /(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/; const conventionalCommitPattern =
/(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/;
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
'.aicommits': `${files['.aicommits']}\ntype=other`, '.aicommits': `${files['.aicommits']}\ntype=other`,
@@ -284,9 +321,7 @@ export default testSuite(({ describe }) => {
await git('add', ['data.json']); await git('add', ['data.json']);
// Generate flag should override generate config // Generate flag should override generate config
const committing = aicommits([ const committing = aicommits(['--type', 'conventional']);
'--type', 'conventional',
]);
committing.stdout!.on('data', (buffer: Buffer) => { committing.stdout!.on('data', (buffer: Buffer) => {
const stdout = buffer.toString(); const stdout = buffer.toString();
@@ -298,7 +333,10 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--oneline']); const { stdout: commitMessage } = await git('log', ['--oneline']);
@@ -309,7 +347,8 @@ export default testSuite(({ describe }) => {
}); });
test('Accepts empty --type flag', async () => { test('Accepts empty --type flag', async () => {
const conventionalCommitPattern = /(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/; const conventionalCommitPattern =
/(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test):\s/;
const { fixture, aicommits } = await createFixture({ const { fixture, aicommits } = await createFixture({
...files, ...files,
'.aicommits': `${files['.aicommits']}\ntype=conventional`, '.aicommits': `${files['.aicommits']}\ntype=conventional`,
@@ -318,9 +357,7 @@ export default testSuite(({ describe }) => {
await git('add', ['data.json']); await git('add', ['data.json']);
const committing = aicommits([ const committing = aicommits(['--type', '']);
'--type', '',
]);
committing.stdout!.on('data', (buffer: Buffer) => { committing.stdout!.on('data', (buffer: Buffer) => {
const stdout = buffer.toString(); const stdout = buffer.toString();
@@ -332,7 +369,10 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--oneline']); const { stdout: commitMessage } = await git('log', ['--oneline']);
@@ -394,10 +434,15 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,
@@ -429,10 +474,15 @@ export default testSuite(({ describe }) => {
await committing; await committing;
const statusAfter = await git('status', ['--porcelain', '--untracked-files=no']); const statusAfter = await git('status', [
'--porcelain',
'--untracked-files=no',
]);
expect(statusAfter.stdout).toBe(''); expect(statusAfter.stdout).toBe('');
const { stdout: commitMessage } = await git('log', ['--pretty=format:%s']); const { stdout: commitMessage } = await git('log', [
'--pretty=format:%s',
]);
console.log({ console.log({
commitMessage, commitMessage,
length: commitMessage.length, length: commitMessage.length,

View File

@@ -17,7 +17,9 @@ export default testSuite(({ describe }) => {
const { stdout, exitCode } = await aicommits([], { reject: false }); const { stdout, exitCode } = await aicommits([], { reject: false });
expect(exitCode).toBe(1); expect(exitCode).toBe(1);
expect(stdout).toMatch('No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.'); expect(stdout).toMatch(
'No staged changes found. Stage your changes manually, or automatically stage all changes with the `--all` flag.'
);
await fixture.rm(); await fixture.rm();
}); });
}); });

View File

@@ -22,7 +22,9 @@ export default testSuite(({ describe }) => {
reject: false, reject: false,
}); });
expect(stderr).toMatch('Invalid config property OPENAI_KEY: Must start with "sk-"'); expect(stderr).toMatch(
'Invalid config property OPENAI_KEY: Must start with "sk-"'
);
}); });
await test('set config file', async () => { await test('set config file', async () => {
@@ -71,9 +73,12 @@ export default testSuite(({ describe }) => {
await describe('max-length', ({ test }) => { await describe('max-length', ({ test }) => {
test('must be an integer', async () => { test('must be an integer', async () => {
const { stderr } = await aicommits(['config', 'set', 'max-length=abc'], { const { stderr } = await aicommits(
['config', 'set', 'max-length=abc'],
{
reject: false, reject: false,
}); }
);
expect(stderr).toMatch('Must be an integer'); expect(stderr).toMatch('Must be an integer');
}); });

View File

@@ -1,7 +1,5 @@
import { expect, testSuite } from 'manten'; import { expect, testSuite } from 'manten';
import { import { generateCommitMessage } from '../../../src/utils/openai.js';
generateCommitMessage,
} from '../../../src/utils/openai.js';
import type { ValidConfig } from '../../../src/utils/config.js'; import type { ValidConfig } from '../../../src/utils/config.js';
import { getDiff } from '../../utils.js'; import { getDiff } from '../../utils.js';
@@ -9,13 +7,16 @@ const { OPENAI_KEY } = process.env;
export default testSuite(({ describe }) => { export default testSuite(({ describe }) => {
if (!OPENAI_KEY) { if (!OPENAI_KEY) {
console.warn('⚠️ process.env.OPENAI_KEY is necessary to run these tests. Skipping...'); console.warn(
'⚠️ process.env.OPENAI_KEY is necessary to run these tests. Skipping...'
);
return; return;
} }
describe('Conventional Commits', async ({ test }) => { describe('Conventional Commits', async ({ test }) => {
await test('Should not translate conventional commit type to Japanase when locale config is set to japanese', async () => { await test('Should not translate conventional commit type to Japanase when locale config is set to japanese', async () => {
const japaneseConventionalCommitPattern = /(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(.*\))?: [\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/; const japaneseConventionalCommitPattern =
/(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(.*\))?: [\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/;
const gitDiff = await getDiff('new-feature.diff'); const gitDiff = await getDiff('new-feature.diff');
@@ -58,9 +59,7 @@ export default testSuite(({ describe }) => {
}); });
await test('Should use "build:" conventional commit when change relate to github action build pipeline', async () => { await test('Should use "build:" conventional commit when change relate to github action build pipeline', async () => {
const gitDiff = await getDiff( const gitDiff = await getDiff('github-action-build-pipeline.diff');
'github-action-build-pipeline.diff',
);
const commitMessage = await runGenerateCommitMessage(gitDiff); const commitMessage = await runGenerateCommitMessage(gitDiff);
@@ -128,8 +127,10 @@ export default testSuite(({ describe }) => {
console.log('Generated message:', commitMessage); console.log('Generated message:', commitMessage);
}); });
async function runGenerateCommitMessage(gitDiff: string, async function runGenerateCommitMessage(
configOverrides: Partial<ValidConfig> = {}): Promise<string> { gitDiff: string,
configOverrides: Partial<ValidConfig> = {}
): Promise<string> {
const config = { const config = {
locale: 'en', locale: 'en',
type: 'conventional', type: 'conventional',
@@ -137,7 +138,16 @@ export default testSuite(({ describe }) => {
'max-length': 50, 'max-length': 50,
...configOverrides, ...configOverrides,
} as ValidConfig; } as ValidConfig;
const commitMessages = await generateCommitMessage(OPENAI_KEY!, 'gpt-3.5-turbo', config.locale, gitDiff, config.generate, config['max-length'], config.type, 7000); const commitMessages = await generateCommitMessage(
OPENAI_KEY!,
'gpt-3.5-turbo',
config.locale,
gitDiff,
config.generate,
config['max-length'],
config.type,
7000
);
return commitMessages[0]; return commitMessages[0];
} }

View File

@@ -15,10 +15,8 @@ const createAicommits = (fixture: FsFixture) => {
USERPROFILE: fixture.path, // Windows USERPROFILE: fixture.path, // Windows
}; };
return ( return (args?: string[], options?: Options) =>
args?: string[], execaNode(aicommitsPath, args, {
options?: Options,
) => execaNode(aicommitsPath, args, {
cwd: fixture.path, cwd: fixture.path,
...options, ...options,
extendEnv: false, extendEnv: false,
@@ -33,28 +31,16 @@ const createAicommits = (fixture: FsFixture) => {
}; };
export const createGit = async (cwd: string) => { export const createGit = async (cwd: string) => {
const git = ( const git = (command: string, args?: string[], options?: Options) =>
command: string, execa('git', [command, ...(args || [])], {
args?: string[],
options?: Options,
) => (
execa(
'git',
[command, ...(args || [])],
{
cwd, cwd,
...options, ...options,
}, });
)
);
await git( await git('init', [
'init',
[
// In case of different default branch name // In case of different default branch name
'--initial-branch=master', '--initial-branch=master',
], ]);
);
await git('config', ['user.name', 'name']); await git('config', ['user.name', 'name']);
await git('config', ['user.email', 'email']); await git('config', ['user.email', 'email']);
@@ -62,9 +48,7 @@ export const createGit = async (cwd: string) => {
return git; return git;
}; };
export const createFixture = async ( export const createFixture = async (source?: string | FileTree) => {
source?: string | FileTree,
) => {
const fixture = await createFixtureBase(source); const fixture = await createFixtureBase(source);
const aicommits = createAicommits(fixture); const aicommits = createAicommits(fixture);
@@ -76,17 +60,20 @@ export const createFixture = async (
export const files = Object.freeze({ export const files = Object.freeze({
'.aicommits': `OPENAI_KEY=${process.env.OPENAI_KEY}`, '.aicommits': `OPENAI_KEY=${process.env.OPENAI_KEY}`,
'data.json': Array.from({ length: 10 }, (_, i) => `${i}. Lorem ipsum dolor sit amet`).join('\n'), 'data.json': Array.from(
{ length: 10 },
(_, i) => `${i}. Lorem ipsum dolor sit amet`
).join('\n'),
}); });
export const assertOpenAiToken = () => { export const assertOpenAiToken = () => {
if (!process.env.OPENAI_KEY) { if (!process.env.OPENAI_KEY) {
throw new Error('⚠️ process.env.OPENAI_KEY is necessary to run these tests. Skipping...'); throw new Error(
'⚠️ process.env.OPENAI_KEY is necessary to run these tests. Skipping...'
);
} }
}; };
// See ./diffs/README.md in order to generate diff files // See ./diffs/README.md in order to generate diff files
export const getDiff = async (diffName: string): Promise<string> => fs.readFile( export const getDiff = async (diffName: string): Promise<string> =>
new URL(`fixtures/${diffName}`, import.meta.url), fs.readFile(new URL(`fixtures/${diffName}`, import.meta.url), 'utf8');
'utf8',
);