mirror of
https://github.com/di-sukharev/opencommit.git
synced 2026-01-12 23:28:16 -05:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5837d1fa2e | ||
|
|
36f282d8a5 | ||
|
|
9f65c450e3 | ||
|
|
d5f53fec5a | ||
|
|
b6651a4c47 | ||
|
|
2b10dc089c | ||
|
|
a5e60ac23c | ||
|
|
e519752938 | ||
|
|
1ba5e20228 | ||
|
|
4103bafc4f | ||
|
|
5a9b460033 | ||
|
|
3364289034 | ||
|
|
5185f3365c | ||
|
|
17ed061f73 | ||
|
|
06fa6daa72 | ||
|
|
af457473be | ||
|
|
33b418e399 | ||
|
|
7e5ed6de0b | ||
|
|
6ba0f97163 | ||
|
|
0d559d4499 | ||
|
|
686f876cb0 | ||
|
|
acf3e450ae | ||
|
|
8be30a734d | ||
|
|
e2f68b7256 | ||
|
|
eacc750952 | ||
|
|
3fe57537ad | ||
|
|
db9cff1ae1 | ||
|
|
1c29b91408 | ||
|
|
425eeef732 | ||
|
|
52c396eb16 | ||
|
|
f5bcf58f7b | ||
|
|
4b53a08653 | ||
|
|
95d3d8b6c9 | ||
|
|
2c79bf22df | ||
|
|
e8c1a75a46 | ||
|
|
5d064ac873 | ||
|
|
8d01829a9b | ||
|
|
e9c66ae168 | ||
|
|
18b0004b81 | ||
|
|
4d4157087e | ||
|
|
3edb6e2fc1 | ||
|
|
d428102a67 | ||
|
|
9404f5d410 | ||
|
|
8c1eb4a5ad | ||
|
|
bafe7e9ede | ||
|
|
a4716b35a4 | ||
|
|
c1e9062ce0 | ||
|
|
c7efa6f935 | ||
|
|
1b70de1d20 | ||
|
|
853662acc4 | ||
|
|
0e1ad33179 | ||
|
|
e7eaa5425e | ||
|
|
4b96670374 | ||
|
|
e128cdece1 | ||
|
|
4cc73208cd | ||
|
|
ea864d18f4 | ||
|
|
5d131e66fa | ||
|
|
bf24be92a1 | ||
|
|
3103ae18b8 | ||
|
|
7c9feba3ba | ||
|
|
58369e4df9 |
91
.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
91
.github/ISSUE_TEMPLATE/bug.yaml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: 🐞 Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
assignees:
|
||||
- octocat
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: input
|
||||
id: opencommit-version
|
||||
attributes:
|
||||
label: Opencommit Version
|
||||
description: What version of our software are you running?
|
||||
placeholder: ex. 1.1.22
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: node-version
|
||||
attributes:
|
||||
label: Node Version
|
||||
description: What version of node are you running?
|
||||
placeholder: ex. 19.8.1
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: npm-version
|
||||
attributes:
|
||||
label: NPM Version
|
||||
description: What version of npm are you running?
|
||||
placeholder: ex. 9.6.2
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: OS
|
||||
attributes:
|
||||
label: What OS are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Mac
|
||||
- Windows
|
||||
- Other Linux Distro
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you expected to happen!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current-behavior
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
description: Also tell us, what is currently happening?
|
||||
placeholder: Tell us what is happening now.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: Do you have a solution for the issue?
|
||||
placeholder: Tell us what the solution could look like.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Tell us how to reproduce the issue?
|
||||
placeholder: Tell us how to reproduce the issue?
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
48
.github/ISSUE_TEMPLATE/featureRequest.yaml
vendored
Normal file
48
.github/ISSUE_TEMPLATE/featureRequest.yaml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: 🛠️ Feature Request
|
||||
description: Suggest an idea to help us improve Opencommit
|
||||
title: "[Feature]: "
|
||||
labels:
|
||||
- "feature_request"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Thanks :heart: for taking the time to fill out this feature request report!**
|
||||
We kindly ask that you search to see if an issue [already exists](https://github.com/di-sukharev/opencommit/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature.
|
||||
|
||||
We are also happy to accept contributions from our users. For more details see [here](https://github.com/di-sukharev/opencommit/blob/master/.github/CONTRIBUTING.md).
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
A clear and concise description of the feature you're interested in.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Suggested Solution
|
||||
description: |
|
||||
Describe the solution you'd like. A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: |
|
||||
Describe alternatives you've considered.
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: |
|
||||
Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
1
.github/TODO.md
vendored
1
.github/TODO.md
vendored
@@ -8,3 +8,4 @@
|
||||
- [x] batch small files in one request
|
||||
- [] add tests
|
||||
- [] optimize prompt, maybe no prompt would be cleaner
|
||||
- [] try setting max commit msg length, maybe it will make commits short and more concise
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,4 +11,5 @@ logfile.log
|
||||
uncaughtExceptions.log
|
||||
.vscode
|
||||
src/*.json
|
||||
.idea
|
||||
.idea
|
||||
test.ts
|
||||
14
esbuild.config.js
Normal file
14
esbuild.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { build } from 'esbuild'
|
||||
import fs from 'fs'
|
||||
|
||||
await build({
|
||||
entryPoints: ['./src/cli.ts'],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
outfile: './out/cli.cjs',
|
||||
});
|
||||
|
||||
const wasmFile = fs.readFileSync('./node_modules/@dqbd/tiktoken/lite/tiktoken_bg.wasm')
|
||||
|
||||
fs.writeFileSync('./out/tiktoken_bg.wasm', wasmFile)
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "1.1.23",
|
||||
"version": "1.1.44",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "opencommit",
|
||||
"version": "1.1.23",
|
||||
"version": "1.1.44",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "1.1.23",
|
||||
"version": "1.1.44",
|
||||
"description": "GPT CLI to auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
|
||||
"keywords": [
|
||||
"git",
|
||||
@@ -40,7 +40,7 @@
|
||||
"watch": "npm run -S build -- --sourcemap --watch",
|
||||
"start": "node ./out/cli.cjs",
|
||||
"dev": "ts-node ./src/cli.ts",
|
||||
"build": "rimraf out && esbuild ./src/cli.ts --bundle --outfile=out/cli.cjs --format=cjs --platform=node",
|
||||
"build": "rimraf out && node esbuild.config.js",
|
||||
"deploy": "npm run build && npm version patch && npm publish --tag latest",
|
||||
"lint": "eslint src --ext ts && tsc --noEmit",
|
||||
"format": "prettier --write src"
|
||||
@@ -60,6 +60,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.6.1",
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"chalk": "^5.2.0",
|
||||
"cleye": "^1.3.2",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { CONFIG_MODES, getConfig } from './commands/config';
|
||||
const config = getConfig();
|
||||
|
||||
let apiKey = config?.OPENAI_API_KEY;
|
||||
let basePath = config?.OPENAI_BASE_PATH;
|
||||
|
||||
const [command, mode] = process.argv.slice(2);
|
||||
|
||||
@@ -32,8 +33,14 @@ class OpenAi {
|
||||
private openAiApiConfiguration = new OpenAiApiConfiguration({
|
||||
apiKey: apiKey
|
||||
});
|
||||
private openAI!: OpenAIApi;
|
||||
|
||||
private openAI = new OpenAIApi(this.openAiApiConfiguration);
|
||||
constructor() {
|
||||
if (basePath) {
|
||||
this.openAiApiConfiguration.basePath = basePath;
|
||||
}
|
||||
this.openAI = new OpenAIApi(this.openAiApiConfiguration);
|
||||
}
|
||||
|
||||
public generateCommitMessage = async (
|
||||
messages: Array<ChatCompletionRequestMessage>
|
||||
|
||||
@@ -22,7 +22,8 @@ cli(
|
||||
},
|
||||
async () => {
|
||||
// await checkIsLatestVersion();
|
||||
if (isHookCalled) {
|
||||
|
||||
if (await isHookCalled()) {
|
||||
prepareCommitMessageHook();
|
||||
} else {
|
||||
commit(extraArgs);
|
||||
|
||||
@@ -22,10 +22,9 @@ import {
|
||||
import chalk from 'chalk';
|
||||
import { trytm } from '../utils/trytm';
|
||||
|
||||
// Adding a function to get the list of remotes
|
||||
const getGitRemotes = async () => {
|
||||
const { stdout } = await execa('git', ['remote']);
|
||||
return stdout.split('\n').filter((remote) => remote.trim() !== '');
|
||||
return stdout.split('\n').filter((remote) => Boolean(remote.trim()));
|
||||
};
|
||||
|
||||
const generateCommitMessageFromGitDiff = async (
|
||||
@@ -77,8 +76,15 @@ ${chalk.grey('——————————————————')}`
|
||||
outro(`${chalk.green('✔')} successfully committed`);
|
||||
|
||||
outro(stdout);
|
||||
|
||||
const remotes = await getGitRemotes();
|
||||
|
||||
if (!remotes.length) {
|
||||
const { stdout } = await execa('git', ['push']);
|
||||
if (stdout) outro(stdout);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (remotes.length === 1) {
|
||||
const isPushConfirmedByUser = await confirm({
|
||||
message: 'Do you want to run `git push`?'
|
||||
@@ -100,6 +106,9 @@ ${chalk.grey('——————————————————')}`
|
||||
);
|
||||
|
||||
if (stdout) outro(stdout);
|
||||
} else {
|
||||
outro('`git push` aborted');
|
||||
process.exit(0);
|
||||
}
|
||||
} else {
|
||||
const selectedRemote = (await select({
|
||||
@@ -207,5 +216,6 @@ export async function commit(
|
||||
outro(`${chalk.red('✖')} ${generateCommitError}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getI18nLocal } from '../i18n';
|
||||
|
||||
export enum CONFIG_KEYS {
|
||||
OPENAI_API_KEY = 'OPENAI_API_KEY',
|
||||
OPENAI_BASE_PATH = 'OPENAI_BASE_PATH',
|
||||
description = 'description',
|
||||
emoji = 'emoji',
|
||||
language = 'language'
|
||||
@@ -78,6 +79,15 @@ export const configValidators = {
|
||||
`${value} is not supported yet`
|
||||
);
|
||||
return getI18nLocal(value);
|
||||
},
|
||||
|
||||
[CONFIG_KEYS.OPENAI_BASE_PATH](value: any) {
|
||||
validateConfig(
|
||||
CONFIG_KEYS.OPENAI_BASE_PATH,
|
||||
typeof value == 'string',
|
||||
`${value} is not supported yet`
|
||||
);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
37
src/commands/githook.ts
Normal file → Executable file
37
src/commands/githook.ts
Normal file → Executable file
@@ -1,18 +1,33 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { command } from 'cleye';
|
||||
import { assertGitRepo } from '../utils/git.js';
|
||||
import { assertGitRepo, getCoreHooksPath } from '../utils/git.js';
|
||||
import { existsSync } from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import { intro, outro } from '@clack/prompts';
|
||||
import { COMMANDS } from '../CommandsEnum.js';
|
||||
|
||||
const HOOK_NAME = 'prepare-commit-msg';
|
||||
const SYMLINK_URL = `.git/hooks/${HOOK_NAME}`;
|
||||
const DEFAULT_SYMLINK_URL = path.join('.git', 'hooks', HOOK_NAME);
|
||||
|
||||
export const isHookCalled = process.argv[1].endsWith(`/${SYMLINK_URL}`);
|
||||
const getHooksPath = async (): Promise<string> => {
|
||||
try {
|
||||
const hooksPath = await getCoreHooksPath();
|
||||
return path.join(hooksPath, HOOK_NAME);
|
||||
} catch (error) {
|
||||
return DEFAULT_SYMLINK_URL;
|
||||
}
|
||||
};
|
||||
|
||||
const isHookExists = existsSync(SYMLINK_URL);
|
||||
export const isHookCalled = async (): Promise<boolean> => {
|
||||
const hooksPath = await getHooksPath();
|
||||
return process.argv[1].endsWith(hooksPath);
|
||||
};
|
||||
|
||||
const isHookExists = async (): Promise<boolean> => {
|
||||
const hooksPath = await getHooksPath();
|
||||
return existsSync(hooksPath);
|
||||
};
|
||||
|
||||
export const hookCommand = command(
|
||||
{
|
||||
@@ -21,16 +36,16 @@ export const hookCommand = command(
|
||||
},
|
||||
async (argv) => {
|
||||
const HOOK_URL = __filename;
|
||||
|
||||
const SYMLINK_URL = await getHooksPath();
|
||||
try {
|
||||
await assertGitRepo();
|
||||
|
||||
const { setUnset: mode } = argv._;
|
||||
|
||||
if (mode === 'set') {
|
||||
intro(`setting opencommit as '${HOOK_NAME}' hook`);
|
||||
intro(`setting opencommit as '${HOOK_NAME}' hook at ${SYMLINK_URL}`);
|
||||
|
||||
if (isHookExists) {
|
||||
if (await isHookExists()) {
|
||||
let realPath;
|
||||
try {
|
||||
realPath = await fs.realpath(SYMLINK_URL);
|
||||
@@ -40,7 +55,7 @@ export const hookCommand = command(
|
||||
}
|
||||
|
||||
if (realPath === HOOK_URL)
|
||||
return outro(`opencommit is already set as '${HOOK_NAME}'`);
|
||||
return outro(`OpenCommit is already set as '${HOOK_NAME}'`);
|
||||
|
||||
throw new Error(
|
||||
`Different ${HOOK_NAME} is already set. Remove it before setting opencommit as '${HOOK_NAME}' hook.`
|
||||
@@ -55,9 +70,11 @@ export const hookCommand = command(
|
||||
}
|
||||
|
||||
if (mode === 'unset') {
|
||||
intro(`unsetting OpenCommit as '${HOOK_NAME}' hook`);
|
||||
intro(
|
||||
`unsetting opencommit as '${HOOK_NAME}' hook from ${SYMLINK_URL}`
|
||||
);
|
||||
|
||||
if (!isHookExists) {
|
||||
if (!(await isHookExists())) {
|
||||
return outro(
|
||||
`OpenCommit wasn't previously set as '${HOOK_NAME}' hook, nothing to remove`
|
||||
);
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import fs from 'fs/promises';
|
||||
import chalk from 'chalk';
|
||||
import { intro, outro } from '@clack/prompts';
|
||||
import { intro, outro, spinner } from '@clack/prompts';
|
||||
import { getChangedFiles, getDiff, getStagedFiles, gitAdd } from '../utils/git';
|
||||
import { getConfig } from './config';
|
||||
import { generateCommitMessageWithChatCompletion } from '../generateCommitMessageFromGitDiff';
|
||||
|
||||
const [messageFilePath, commitSource] = process.argv.slice(2);
|
||||
|
||||
export const prepareCommitMessageHook = async () => {
|
||||
export const prepareCommitMessageHook = async (
|
||||
isStageAllFlag: Boolean = false
|
||||
) => {
|
||||
try {
|
||||
if (!messageFilePath) {
|
||||
throw new Error(
|
||||
@@ -17,11 +19,14 @@ export const prepareCommitMessageHook = async () => {
|
||||
|
||||
if (commitSource) return;
|
||||
|
||||
const changedFiles = await getChangedFiles();
|
||||
if (changedFiles) await gitAdd({ files: changedFiles });
|
||||
else {
|
||||
outro("No changes detected, write some code and run `oc` again");
|
||||
if (isStageAllFlag) {
|
||||
const changedFiles = await getChangedFiles();
|
||||
|
||||
if (changedFiles) await gitAdd({ files: changedFiles });
|
||||
else {
|
||||
outro('No changes detected, write some code and run `oc` again');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const staged = await getStagedFiles();
|
||||
@@ -38,11 +43,15 @@ export const prepareCommitMessageHook = async () => {
|
||||
);
|
||||
}
|
||||
|
||||
const spin = spinner();
|
||||
spin.start('Generating commit message');
|
||||
const commitMessage = await generateCommitMessageWithChatCompletion(
|
||||
await getDiff({ files: staged })
|
||||
);
|
||||
|
||||
if (typeof commitMessage !== 'string') throw new Error(commitMessage.error);
|
||||
if (typeof commitMessage !== 'string') {
|
||||
spin.stop('Error');
|
||||
throw new Error(commitMessage.error);
|
||||
} else spin.stop('Done');
|
||||
|
||||
const fileContent = await fs.readFile(messageFilePath);
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
} from 'openai';
|
||||
import { api } from './api';
|
||||
import { getConfig } from './commands/config';
|
||||
import { mergeStrings } from './utils/mergeStrings';
|
||||
import { mergeDiffs } from './utils/mergeDiffs';
|
||||
import { i18n, I18nLocals } from './i18n';
|
||||
import { tokenCount } from './utils/tokenCount';
|
||||
|
||||
const config = getConfig();
|
||||
const translation = i18n[(config?.language as I18nLocals) || 'en'];
|
||||
@@ -13,40 +14,38 @@ const translation = i18n[(config?.language as I18nLocals) || 'en'];
|
||||
const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.System,
|
||||
content: `You are to act as the author of a commit message in git. Your mission is to create clean and comprehensive commit messages in the conventional commit convention. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message. ${
|
||||
config?.emoji
|
||||
? 'Use Gitmoji convention to preface the commit'
|
||||
: 'Do not preface the commit with anything'
|
||||
}, use the present tense. ${
|
||||
config?.description
|
||||
? 'Add a short description of what commit is about 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."
|
||||
} Use ${translation.localLanguage} to answer.`
|
||||
// prettier-ignore
|
||||
content: `You are to act as the author of a commit message in git. Your mission is to create clean and comprehensive commit messages in the conventional commit convention and explain why a change was done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
|
||||
${config?.emoji? 'Use GitMoji convention to preface the commit.': 'Do not preface the commit with anything.'}
|
||||
${config?.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."}
|
||||
Use the present tense. Lines must not be longer than 74 characters. Use ${translation.localLanguage} to answer.`
|
||||
},
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.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}\`);
|
||||
});`
|
||||
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}\`);
|
||||
});`
|
||||
},
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
@@ -80,8 +79,8 @@ interface GenerateCommitMessageError {
|
||||
}
|
||||
|
||||
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
|
||||
(msg) => msg.content
|
||||
).join('').length;
|
||||
(msg) => tokenCount(msg.content) + 4
|
||||
).reduce((a, b) => a + b, 0);
|
||||
|
||||
const MAX_REQ_TOKENS = 3900 - INIT_MESSAGES_PROMPT_LENGTH;
|
||||
|
||||
@@ -89,8 +88,11 @@ export const generateCommitMessageWithChatCompletion = async (
|
||||
diff: string
|
||||
): Promise<string | GenerateCommitMessageError> => {
|
||||
try {
|
||||
if (diff.length >= MAX_REQ_TOKENS) {
|
||||
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(diff);
|
||||
if (tokenCount(diff) >= MAX_REQ_TOKENS) {
|
||||
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
|
||||
diff,
|
||||
MAX_REQ_TOKENS
|
||||
);
|
||||
|
||||
const commitMessages = await Promise.all(commitMessagePromises);
|
||||
|
||||
@@ -110,22 +112,28 @@ export const generateCommitMessageWithChatCompletion = async (
|
||||
}
|
||||
};
|
||||
|
||||
function getMessagesPromisesByLines(fileDiff: string, separator: string) {
|
||||
const lineSeparator = '\n@@';
|
||||
const [fileHeader, ...fileDiffByLines] = fileDiff.split(lineSeparator);
|
||||
function getMessagesPromisesByChangesInFile(
|
||||
fileDiff: string,
|
||||
separator: string,
|
||||
maxChangeLength: number
|
||||
) {
|
||||
const hunkHeaderSeparator = '@@ ';
|
||||
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
|
||||
|
||||
// merge multiple line-diffs into 1 to save tokens
|
||||
const mergedLines = mergeStrings(
|
||||
fileDiffByLines.map((line) => lineSeparator + line),
|
||||
MAX_REQ_TOKENS
|
||||
const mergedChanges = mergeDiffs(
|
||||
fileDiffByLines.map((line) => hunkHeaderSeparator + line),
|
||||
maxChangeLength
|
||||
);
|
||||
|
||||
const lineDiffsWithHeader = mergedLines.map(
|
||||
(d) => fileHeader + lineSeparator + d
|
||||
const lineDiffsWithHeader = mergedChanges.map(
|
||||
(change) => fileHeader + change
|
||||
);
|
||||
|
||||
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((d) => {
|
||||
const messages = generateCommitMessageChatCompletionPrompt(separator + d);
|
||||
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((lineDiff) => {
|
||||
const messages = generateCommitMessageChatCompletionPrompt(
|
||||
separator + lineDiff
|
||||
);
|
||||
|
||||
return api.generateCommitMessage(messages);
|
||||
});
|
||||
@@ -133,20 +141,27 @@ function getMessagesPromisesByLines(fileDiff: string, separator: string) {
|
||||
return commitMsgsFromFileLineDiffs;
|
||||
}
|
||||
|
||||
function getCommitMsgsPromisesFromFileDiffs(diff: string) {
|
||||
export function getCommitMsgsPromisesFromFileDiffs(
|
||||
diff: string,
|
||||
maxDiffLength: number
|
||||
) {
|
||||
const separator = 'diff --git ';
|
||||
|
||||
const diffByFiles = diff.split(separator).slice(1);
|
||||
|
||||
// merge multiple files-diffs into 1 prompt to save tokens
|
||||
const mergedFilesDiffs = mergeStrings(diffByFiles, MAX_REQ_TOKENS);
|
||||
const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength);
|
||||
|
||||
const commitMessagePromises = [];
|
||||
|
||||
for (const fileDiff of mergedFilesDiffs) {
|
||||
if (fileDiff.length >= MAX_REQ_TOKENS) {
|
||||
if (tokenCount(fileDiff) >= maxDiffLength) {
|
||||
// if file-diff is bigger than gpt context — split fileDiff into lineDiff
|
||||
const messagesPromises = getMessagesPromisesByLines(fileDiff, separator);
|
||||
const messagesPromises = getMessagesPromisesByChangesInFile(
|
||||
fileDiff,
|
||||
separator,
|
||||
maxDiffLength
|
||||
);
|
||||
|
||||
commitMessagePromises.push(...messagesPromises);
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"localLanguage": "spanish",
|
||||
"commitFix": "fix(server.ts): cambiar la variable port de minúsculas a mayúsculas PORT",
|
||||
"commitFeat": "feat(server.ts): añadir soporte para la variable de entorno process.env.PORT",
|
||||
"commitDescription": "La variable port ahora se llama PORT, lo que mejora la coherencia con las convenciones de nomenclatura, ya que PORT es una constante. El soporte para una variable de entorno permite que la aplicación sea más flexible, ya que ahora puede ejecutarse en cualquier puerto disponible especificado a través de la variable de entorno process.env.PORT."
|
||||
}
|
||||
|
||||
{
|
||||
"localLanguage": "spanish",
|
||||
"commitFix": "fix(server.ts): cambiar la variable port de minúsculas a mayúsculas PORT",
|
||||
"commitFeat": "feat(server.ts): añadir soporte para la variable de entorno process.env.PORT",
|
||||
"commitDescription": "La variable port ahora se llama PORT, lo que mejora la coherencia con las convenciones de nomenclatura, ya que PORT es una constante. El soporte para una variable de entorno permite que la aplicación sea más flexible, ya que ahora puede ejecutarse en cualquier puerto disponible especificado a través de la variable de entorno process.env.PORT."
|
||||
}
|
||||
|
||||
6
src/i18n/id_ID.json
Normal file
6
src/i18n/id_ID.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "bahasa",
|
||||
"commitFix": "fix(server.ts): mengubah huruf port variable dari huruf kecil ke huruf besar PORT",
|
||||
"commitFeat": "feat(server.ts): menambahkan support di process.env.PORT environment variabel",
|
||||
"commitDescription": "Port variabel bernama PORT, yang membantu konsistensi dengan memberi nama yaitu PORT yang konstan. Bantuan environment variabel membantu aplikasi lebih fleksibel, dan dapat di jalankan di port manapun yang tertulis pada process.env.PORT"
|
||||
}
|
||||
@@ -9,6 +9,12 @@ import ja from '../i18n/ja.json' assert { type: 'json' };
|
||||
import pt_br from '../i18n/pt_br.json' assert { type: 'json' };
|
||||
import vi_VN from '../i18n/vi_VN.json' assert { type: 'json' };
|
||||
import es_ES from '../i18n/es_ES.json' assert { type: 'json' };
|
||||
import sv from '../i18n/sv.json' assert { type: 'json' };
|
||||
import nl from '../i18n/nl.json' assert { type: 'json' };
|
||||
import ru from '../i18n/ru.json' assert { type: 'json' };
|
||||
import id_ID from '../i18n/id_ID.json' assert { type: 'json' };
|
||||
import pl from '../i18n/pl.json' assert { type: 'json' };
|
||||
import th from '../i18n/th.json' assert { type: 'json' };
|
||||
|
||||
export enum I18nLocals {
|
||||
'en' = 'en',
|
||||
@@ -17,11 +23,17 @@ export enum I18nLocals {
|
||||
'ja' = 'ja',
|
||||
'de' = 'de',
|
||||
'fr' = 'fr',
|
||||
'nl' = 'nl',
|
||||
'it' = 'it',
|
||||
'ko' = 'ko',
|
||||
'pt_br' = 'pt_br',
|
||||
'es_ES' = 'es_ES'
|
||||
};
|
||||
'es_ES' = 'es_ES',
|
||||
'sv' = 'sv',
|
||||
'ru' = 'ru',
|
||||
'id_ID' = 'id_ID',
|
||||
'pl' = 'pl',
|
||||
'th' = 'th',
|
||||
}
|
||||
|
||||
export const i18n = {
|
||||
en,
|
||||
@@ -34,7 +46,13 @@ export const i18n = {
|
||||
ko,
|
||||
pt_br,
|
||||
vi_VN,
|
||||
es_ES
|
||||
es_ES,
|
||||
sv,
|
||||
id_ID,
|
||||
nl,
|
||||
ru,
|
||||
pl,
|
||||
th
|
||||
};
|
||||
|
||||
export const I18N_CONFIG_ALIAS: { [key: string]: string[] } = {
|
||||
@@ -42,13 +60,19 @@ export const I18N_CONFIG_ALIAS: { [key: string]: string[] } = {
|
||||
zh_TW: ['zh_TW', '繁體中文', '繁體'],
|
||||
ja: ['ja', 'Japanese', 'にほんご'],
|
||||
ko: ['ko', 'Korean', '한국어'],
|
||||
de: ['de', 'German' ,'Deutsch'],
|
||||
de: ['de', 'German', 'Deutsch'],
|
||||
fr: ['fr', 'French', 'française'],
|
||||
it: ['it', 'Italian', 'italiano'],
|
||||
nl: ['nl', 'Dutch', 'Nederlands'],
|
||||
pt_br: ['pt_br', 'Portuguese', 'português'],
|
||||
vi_VN: ['vi_VN', 'Vietnamese', 'tiếng Việt'],
|
||||
en: ['en', 'English', 'english'],
|
||||
es_ES: ['es_ES', 'Spanish', 'español'],
|
||||
sv: ['sv', 'Swedish', 'Svenska'],
|
||||
ru: ['ru', 'Russian', 'русский'],
|
||||
id_ID: ['id_ID', 'Bahasa', 'bahasa'],
|
||||
pl: ['pl', 'Polish', 'Polski'],
|
||||
th: ['th', 'Thai', 'ไทย']
|
||||
};
|
||||
|
||||
export function getI18nLocal(value: string): string | boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"localLanguage": "italiano",
|
||||
"commitFix": "fix(server.ts): cambia il caso della variabile di porta da minuscolo port a maiuscolo PORT",
|
||||
"commitFeat": "funzionalità(server.ts): aggiungi supporto per la variabile di ambiente process.env.PORT",
|
||||
"commitDescription": "La variabile di porta è ora chiamata PORT, il che migliora la coerenza con le convenzioni di denominazione in quanto PORT è una costante. Il supporto per una variabile di ambiente consente all'applicazione di essere più flessibile poiché ora può essere eseguita su qualsiasi porta disponibile specificata tramite la variabile di ambiente process.env.PORT."
|
||||
"commitFix": "fix(server.ts): cambia la grafia della variabile della porta dal minuscolo port al maiuscolo PORT",
|
||||
"commitFeat": "feat(server.ts): aggiunge il supporto per la variabile di ambiente process.env.PORT",
|
||||
"commitDescription": "La variabile port è ora chiamata PORT, migliorando la coerenza con le convenzioni di denominazione in quanto PORT è una costante. Il supporto per una variabile di ambiente consente all'applicazione di essere più flessibile poiché ora può essere eseguita su qualsiasi porta disponibile specificata tramite la variabile di ambiente process.env.PORT."
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"localLanguage": "にほんご",
|
||||
"commitFix": "修正(server.ts): ポート変数のケースを小文字のポートから大文字のPORTに変更",
|
||||
"commitFeat": "新機能(server.ts): process.env.PORT環境変数のサポートを追加する",
|
||||
"commitDescription": "ポート変数は現在PORTという名前になっており、PORTは定数であるため命名規則に一貫性があります。環境変数のサポートにより、アプリケーションはより柔軟になり、process.env.PORT環境変数で指定された任意の利用可能なポートで実行できるようになりまし"
|
||||
"localLanguage": "日本語",
|
||||
"commitFix": "修正(server.ts): ポート変数を小文字のportから大文字のPORTに変更",
|
||||
"commitFeat": "新機能(server.ts): 環境変数process.env.PORTのサポートを追加",
|
||||
"commitDescription": "ポート変数は現在PORTという名前になり、定数であるPORTを使うことで命名規則に一貫性が生まれました。環境変数をサポートすることで、環境変数process.env.PORTで指定された任意の利用可能なポートで実行できるようになり、アプリケーションはより柔軟になりました。"
|
||||
}
|
||||
|
||||
6
src/i18n/nl.json
Normal file
6
src/i18n/nl.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "Nederlands",
|
||||
"commitFix": "fix(server.ts): verander poortvariabele van kleine letters poort naar hoofdletters PORT",
|
||||
"commitFeat": "feat(server.ts): voeg ondersteuning toe voor process.env.PORT omgevingsvariabele",
|
||||
"commitDescription": "De poortvariabele heet nu PORT, wat de consistentie met de naamgevingsconventies verbetert omdat PORT een constante is. Ondersteuning voor een omgevingsvariabele maakt de applicatie flexibeler, omdat deze nu kan draaien op elke beschikbare poort die is gespecificeerd via de process.env.PORT omgevingsvariabele."
|
||||
}
|
||||
6
src/i18n/pl.json
Normal file
6
src/i18n/pl.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "polski",
|
||||
"commitFix": "fix(server.ts): poprawa wielkości zmiennej port na pisane z dużymi literami PORT",
|
||||
"commitFeat": "feat(server.ts): dodanie obsługi zmiennej środowiskowej process.env.PORT",
|
||||
"commitDescription": "Zmienna port jest teraz nazwana PORT, co jest zgodne z konwencją nazewniczą ponieważ PORT jest stałą. Obsługa zmiennej środowiskowej process.env.PORT pozwala łatwiej zarządzać ustawieniami przy starcie."
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"localLanguage": "português",
|
||||
"commitFix": "fix(server.ts): altera o caso da variável de porta de port minúscula para PORT maiúscula",
|
||||
"commitFeat": "feat(server.ts): adiciona suporte para a variável de ambiente process.env.PORT",
|
||||
"commitDescription": "A variável de porta agora é denominada PORT, o que melhora a consistência com as convenções de nomenclatura, pois PORT é uma constante. O suporte para uma variável de ambiente permite que o aplicativo seja mais flexível, pois agora pode ser executado em qualquer porta disponível especificada por meio da variável de ambiente process.env.PORT."
|
||||
"localLanguage": "português",
|
||||
"commitFix": "fix(server.ts): altera o caso da variável de porta de port minúscula para PORT maiúscula",
|
||||
"commitFeat": "feat(server.ts): adiciona suporte para a variável de ambiente process.env.PORT",
|
||||
"commitDescription": "A variável de porta agora é denominada PORT, o que melhora a consistência com as convenções de nomenclatura, pois PORT é uma constante. O suporte para uma variável de ambiente permite que o aplicativo seja mais flexível, pois agora pode ser executado em qualquer porta disponível especificada por meio da variável de ambiente process.env.PORT."
|
||||
}
|
||||
|
||||
6
src/i18n/ru.json
Normal file
6
src/i18n/ru.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "русский",
|
||||
"commitFix": "fix(server.ts): изменение регистра переменной порта с нижнего регистра port на верхний регистр PORT",
|
||||
"commitFeat": "feat(server.ts): добавлена поддержка переменной окружения process.env.PORT",
|
||||
"commitDescription": "Переменная port теперь называется PORT, что улучшает согласованность с соглашениями об именовании констант. Поддержка переменной окружения позволяет приложению быть более гибким, запускаясь на любом доступном порту, указанном с помощью переменной окружения process.env.PORT."
|
||||
}
|
||||
6
src/i18n/sv.json
Normal file
6
src/i18n/sv.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "svenska",
|
||||
"commitFix": "fixa(server.ts): ändra variabelnamnet för port från små bokstäver till stora bokstäver PORT",
|
||||
"commitFeat": "nyhet(server.ts): lägg till stöd för process.env.PORT miljövariabel",
|
||||
"commitDescription": "Variabeln som innehåller portnumret heter nu PORT vilket förbättrar konsekvensen med namngivningskonventionerna eftersom PORT är en konstant. Stöd för en miljövariabel gör att applikationen kan vara mer flexibel då den nu kan köras på vilken port som helst som specificeras via miljövariabeln process.env.PORT."
|
||||
}
|
||||
6
src/i18n/th.json
Normal file
6
src/i18n/th.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"localLanguage": "ไทย",
|
||||
"commitFix": "fix(server.ts): เปลี่ยนตัวพิมพ์ของตัวแปร จากตัวพิมพ์เล็ก port เป็นตัวพิมพ์ใหญ่ PORT",
|
||||
"commitFeat": "feat(server.ts): เพิ่มการรองรับสำหรับตัวแปรสภาพแวดล้อม process.env.PORT",
|
||||
"commitDescription": "ตอนนี้ตัวแปรพอร์ตมีชื่อว่า PORT, ซึ่งปรับปรุงความสอดคล้องกับหลักการตั้งชื่อเนื่องจาก PORT เป็นค่าคงที่. การสนับสนุนสำหรับตัวแปรสภาพแวดล้อม ช่วยให้แอปพลิเคชันมีความยืดหยุ่นมากขึ้นเนื่องจาก สามารถทำงานบนพอร์ตใด ๆ ตามที่กำหนด ซึ่งระบุผ่านตัวแปรสภาพแวดล้อม process.env.PORT"
|
||||
}
|
||||
@@ -25,17 +25,25 @@ export const getOpenCommitIgnore = (): Ignore => {
|
||||
return ig;
|
||||
};
|
||||
|
||||
export const getCoreHooksPath = async(): Promise<string> => {
|
||||
const { stdout } = await execa('git', [
|
||||
'config',
|
||||
'core.hooksPath']);
|
||||
|
||||
return stdout;
|
||||
}
|
||||
|
||||
export const getStagedFiles = async (): Promise<string[]> => {
|
||||
const { stdout: gitDir } = await execa("git", [
|
||||
"rev-parse",
|
||||
"--show-toplevel"
|
||||
const { stdout: gitDir } = await execa('git', [
|
||||
'rev-parse',
|
||||
'--show-toplevel'
|
||||
]);
|
||||
|
||||
const { stdout: files } = await execa("git", [
|
||||
"diff",
|
||||
"--name-only",
|
||||
"--cached",
|
||||
"--relative",
|
||||
const { stdout: files } = await execa('git', [
|
||||
'diff',
|
||||
'--name-only',
|
||||
'--cached',
|
||||
'--relative',
|
||||
gitDir
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export function mergeStrings(arr: string[], maxStringLength: number): string[] {
|
||||
import { tokenCount } from './tokenCount';
|
||||
export function mergeDiffs(arr: string[], maxStringLength: number): string[] {
|
||||
const mergedArr: string[] = [];
|
||||
let currentItem: string = arr[0];
|
||||
for (const item of arr.slice(1)) {
|
||||
if (currentItem.length + item.length <= maxStringLength) {
|
||||
if (tokenCount(currentItem + item) <= maxStringLength) {
|
||||
currentItem += item;
|
||||
} else {
|
||||
mergedArr.push(currentItem);
|
||||
15
src/utils/tokenCount.ts
Normal file
15
src/utils/tokenCount.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Tiktoken } from '@dqbd/tiktoken/lite';
|
||||
import cl100k_base from '@dqbd/tiktoken/encoders/cl100k_base.json' assert { type: 'json' };
|
||||
|
||||
export function tokenCount(content: string): number {
|
||||
const encoding = new Tiktoken(
|
||||
cl100k_base.bpe_ranks,
|
||||
cl100k_base.special_tokens,
|
||||
cl100k_base.pat_str
|
||||
);
|
||||
const tokens = encoding.encode(content);
|
||||
encoding.free();
|
||||
return tokens.length;
|
||||
|
||||
//return content.length / 2.7;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"target": "ESNext",
|
||||
"lib": ["ES5", "ES6"],
|
||||
|
||||
"module": "ESNext",
|
||||
|
||||
Reference in New Issue
Block a user