import { note } from '@clack/prompts';
import { OpenAI } from 'openai';
import { getConfig } from './commands/config';
import { i18n, I18nLocals } from './i18n';
import { configureCommitlintIntegration } from './modules/commitlint/config';
import { commitlintPrompts } from './modules/commitlint/prompts';
import { ConsistencyPrompt } from './modules/commitlint/types';
import * as utils from './modules/commitlint/utils';
import { removeConventionalCommitWord } from './utils/removeConventionalCommitWord';
const config = getConfig();
const translation = i18n[(config.OCO_LANGUAGE as I18nLocals) || 'en'];
export const IDENTITY =
'You are to act as an author of a commit message in git.';
const GITMOJI_HELP = `Use GitMoji convention to preface the commit. Here are some help to choose the right emoji (emoji, description):
๐, Fix a bug;
โจ, Introduce new features;
๐, Add or update documentation;
๐, Deploy stuff;
โ
, Add, update, or pass tests;
โป๏ธ, Refactor code;
โฌ๏ธ, Upgrade dependencies;
๐ง, Add or update configuration files;
๐, Internationalization and localization;
๐ก, Add or update comments in source code;`;
const FULL_GITMOJI_SPEC = `${GITMOJI_HELP}
๐จ, Improve structure / format of the code;
โก๏ธ, Improve performance;
๐ฅ, Remove code or files;
๐๏ธ, Critical hotfix;
๐, Add or update the UI and style files;
๐, Begin a project;
๐๏ธ, Fix security issues;
๐, Add or update secrets;
๐, Release / Version tags;
๐จ, Fix compiler / linter warnings;
๐ง, Work in progress;
๐, Fix CI Build;
โฌ๏ธ, Downgrade dependencies;
๐, Pin dependencies to specific versions;
๐ท, Add or update CI build system;
๐, Add or update analytics or track code;
โ, Add a dependency;
โ, Remove a dependency;
๐จ, Add or update development scripts;
โ๏ธ, Fix typos;
๐ฉ, Write bad code that needs to be improved;
โช๏ธ, Revert changes;
๐, Merge branches;
๐ฆ๏ธ, Add or update compiled files or packages;
๐ฝ๏ธ, Update code due to external API changes;
๐, Move or rename resources (e.g.: files, paths, routes);
๐, Add or update license;
๐ฅ, Introduce breaking changes;
๐ฑ, Add or update assets;
โฟ๏ธ, Improve accessibility;
๐ป, Write code drunkenly;
๐ฌ, Add or update text and literals;
๐๏ธ, Perform database related changes;
๐, Add or update logs;
๐, Remove logs;
๐ฅ, Add or update contributor(s);
๐ธ, Improve user experience / usability;
๐๏ธ, Make architectural changes;
๐ฑ, Work on responsive design;
๐คก, Mock things;
๐ฅ, Add or update an easter egg;
๐, Add or update a .gitignore file;
๐ธ, Add or update snapshots;
โ๏ธ, Perform experiments;
๐๏ธ, Improve SEO;
๐ท๏ธ, Add or update types;
๐ฑ, Add or update seed files;
๐ฉ, Add, update, or remove feature flags;
๐ฅ
, Catch errors;
๐ซ, Add or update animations and transitions;
๐๏ธ, Deprecate code that needs to be cleaned up;
๐, Work on code related to authorization, roles and permissions;
๐ฉน, Simple fix for a non-critical issue;
๐ง, Data exploration/inspection;
โฐ๏ธ, Remove dead code;
๐งช, Add a failing test;
๐, Add or update business logic;
๐ฉบ, Add or update healthcheck;
๐งฑ, Infrastructure related changes;
๐งโ๐ป, Improve developer experience;
๐ธ, Add sponsorships or money related infrastructure;
๐งต, Add or update code related to multithreading or concurrency;
๐ฆบ, Add or update code related to validation.`;
const CONVENTIONAL_COMMIT_KEYWORDS =
'Do not preface the commit with anything, except for the conventional commit keywords: fix, feat, build, chore, ci, docs, style, refactor, perf, test.';
const getCommitConvention = (fullGitMojiSpec: boolean) =>
config.OCO_EMOJI
? fullGitMojiSpec
? FULL_GITMOJI_SPEC
: GITMOJI_HELP
: CONVENTIONAL_COMMIT_KEYWORDS;
const getDescriptionInstruction = () =>
config.OCO_DESCRIPTION
? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.'
: "Don't add any descriptions to the commit, only commit message.";
const getOneLineCommitInstruction = () =>
config.OCO_ONE_LINE_COMMIT
? 'Craft a concise commit message that encapsulates all changes made, with an emphasis on the primary updates. If the modifications share a common theme or scope, mention it succinctly; otherwise, leave the scope out to maintain focus. The goal is to provide a clear and unified overview of the changes in a one single message, without diverging into a list of commit per file change.'
: '';
/**
* Get the context of the user input
* @param extraArgs - The arguments passed to the command line
* @example
* $ oco -- This is a context used to generate the commit message
* @returns - The context of the user input
*/
const userInputCodeContext = (context: string) => {
if (context !== '' && context !== ' ') {
return `Additional context provided by the user: ${context}\nConsider this context when generating the commit message, incorporating relevant information when appropriate.`;
}
return '';
};
const INIT_MAIN_PROMPT = (
language: string,
fullGitMojiSpec: boolean,
context: string
): OpenAI.Chat.Completions.ChatCompletionMessageParam => ({
role: 'system',
content: (() => {
const commitConvention = fullGitMojiSpec
? 'GitMoji specification'
: 'Conventional Commit Convention';
const missionStatement = `${IDENTITY} Your mission is to create clean and comprehensive commit messages as per the ${commitConvention} and explain WHAT were the changes and mainly WHY the changes were done.`;
const diffInstruction =
"I'll send you an output of 'git diff --staged' command, and you are to convert it into a commit message.";
const conventionGuidelines = getCommitConvention(fullGitMojiSpec);
const descriptionGuideline = getDescriptionInstruction();
const oneLineCommitGuideline = getOneLineCommitInstruction();
const generalGuidelines = `Use the present tense. Lines must not be longer than 74 characters. Use ${language} for the commit message.`;
const userInputContext = userInputCodeContext(context);
return `${missionStatement}\n${diffInstruction}\n${conventionGuidelines}\n${descriptionGuideline}\n${oneLineCommitGuideline}\n${generalGuidelines}\n${userInputContext}`;
})()
});
export const INIT_DIFF_PROMPT: OpenAI.Chat.Completions.ChatCompletionMessageParam =
{
role: 'user',
content: `diff --git a/src/server.ts b/src/server.ts
index ad4db42..f3b18a9 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -10,7 +10,7 @@
import {
initWinstonLogger();
const app = express();
-const port = 7799;
+const PORT = 7799;
app.use(express.json());
@@ -34,6 +34,6 @@
app.use((_, res, next) => {
// ROUTES
app.use(PROTECTED_ROUTER_URL, protectedRouter);
-app.listen(port, () => {
- console.log(\`Server listening on port \${port}\`);
+app.listen(process.env.PORT || PORT, () => {
+ console.log(\`Server listening on port \${PORT}\`);
});`
};
const getContent = (translation: ConsistencyPrompt) => {
const fix = config.OCO_EMOJI
? `๐ ${removeConventionalCommitWord(translation.commitFix)}`
: translation.commitFix;
const feat = config.OCO_EMOJI
? `โจ ${removeConventionalCommitWord(translation.commitFeat)}`
: translation.commitFeat;
const description = config.OCO_DESCRIPTION
? translation.commitDescription
: '';
return `${fix}\n${feat}\n${description}`;
};
const INIT_CONSISTENCY_PROMPT = (
translation: ConsistencyPrompt
): OpenAI.Chat.Completions.ChatCompletionMessageParam => ({
role: 'assistant',
content: getContent(translation)
});
export const getMainCommitPrompt = async (
fullGitMojiSpec: boolean,
context: string
): Promise> => {
switch (config.OCO_PROMPT_MODULE) {
case '@commitlint':
if (!(await utils.commitlintLLMConfigExists())) {
note(
`OCO_PROMPT_MODULE is @commitlint but you haven't generated consistency for this project yet.`
);
await configureCommitlintIntegration();
}
// Replace example prompt with a prompt that's generated by OpenAI for the commitlint config.
const commitLintConfig = await utils.getCommitlintLLMConfig();
return [
commitlintPrompts.INIT_MAIN_PROMPT(
translation.localLanguage,
commitLintConfig.prompts
),
INIT_DIFF_PROMPT,
INIT_CONSISTENCY_PROMPT(
commitLintConfig.consistency[
translation.localLanguage
] as ConsistencyPrompt
)
];
default:
return [
INIT_MAIN_PROMPT(translation.localLanguage, fullGitMojiSpec, context),
INIT_DIFF_PROMPT,
INIT_CONSISTENCY_PROMPT(translation)
];
}
};