mirror of
https://github.com/di-sukharev/opencommit.git
synced 2026-01-13 15:47:58 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3885ae5893 | ||
|
|
b8e05a5852 | ||
|
|
677b7ecad9 | ||
|
|
6ab06f9db3 | ||
|
|
38ac20612b | ||
|
|
54b8ba7419 | ||
|
|
ff81d7e1ca | ||
|
|
a6ccdb5f77 | ||
|
|
fef8027959 | ||
|
|
0f48cc616e | ||
|
|
b54ff02930 | ||
|
|
7fb46de105 | ||
|
|
2f6e98dc30 | ||
|
|
2aa6582c52 | ||
|
|
2acf833cd0 | ||
|
|
3f7025d50a | ||
|
|
d793bf1340 | ||
|
|
0092e92061 | ||
|
|
0f33b74942 | ||
|
|
8f0a32275e | ||
|
|
b3509e34d0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,4 +10,5 @@ application.log
|
||||
logfile.log
|
||||
uncaughtExceptions.log
|
||||
.vscode
|
||||
src/*.json
|
||||
src/*.json
|
||||
.idea
|
||||
44
README.md
44
README.md
@@ -84,6 +84,50 @@ To remove description:
|
||||
oc config set description=false
|
||||
```
|
||||
|
||||
### Internationalization support
|
||||
|
||||
To specify the language used to generate commit messages:
|
||||
|
||||
```sh
|
||||
# de, German ,Deutsch
|
||||
oc config set language=de
|
||||
oc config set language=German
|
||||
oc config set language=Deutsch
|
||||
|
||||
# fr, French, française
|
||||
oc config set language=fr
|
||||
oc config set language=French
|
||||
oc config set language=française
|
||||
```
|
||||
The default language set is **English**
|
||||
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder
|
||||
|
||||
### Git flags
|
||||
|
||||
The `opencommit` or `oc` commands can be used in place of the `git commit -m "${generatedMessage}"` command. This means that any regular flags that are used with the `git commit` command will also be applied when using `opencommit` or `oc`.
|
||||
|
||||
```sh
|
||||
oc --no-verify
|
||||
```
|
||||
|
||||
is translated to :
|
||||
|
||||
```sh
|
||||
git commit -m "${generatedMessage}" --no-verify
|
||||
```
|
||||
|
||||
### Ignore files
|
||||
You can ignore files from submission to OpenAI by creating a `.opencommitignore` file. For example:
|
||||
|
||||
```ignorelang
|
||||
path/to/large-asset.zip
|
||||
**/*.jpg
|
||||
```
|
||||
|
||||
This is useful for preventing opencommit from uploading artifacts and large files.
|
||||
|
||||
By default, opencommit ignores files matching: `*-lock.*` and `*.lock`
|
||||
|
||||
## Git hook
|
||||
|
||||
You can set OpenCommit as Git [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. Hook integrates with you IDE Source Control and allows you edit the message before commit.
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "1.1.12",
|
||||
"version": "1.1.21",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "opencommit",
|
||||
"version": "1.1.12",
|
||||
"version": "1.1.21",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.6.1",
|
||||
@@ -14,6 +14,7 @@
|
||||
"chalk": "^5.2.0",
|
||||
"cleye": "^1.3.2",
|
||||
"execa": "^7.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"ini": "^3.0.1",
|
||||
"inquirer": "^9.1.4",
|
||||
"openai": "^3.2.1"
|
||||
@@ -1880,7 +1881,6 @@
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "opencommit",
|
||||
"version": "1.1.12",
|
||||
"version": "1.1.21",
|
||||
"description": "GPT CLI to auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
|
||||
"keywords": [
|
||||
"git",
|
||||
@@ -64,6 +64,7 @@
|
||||
"chalk": "^5.2.0",
|
||||
"cleye": "^1.3.2",
|
||||
"execa": "^7.0.0",
|
||||
"ignore": "^5.2.4",
|
||||
"ini": "^3.0.1",
|
||||
"inquirer": "^9.1.4",
|
||||
"openai": "^3.2.1"
|
||||
|
||||
19
src/api.ts
19
src/api.ts
@@ -53,7 +53,10 @@ class OpenAi {
|
||||
} catch (error: unknown) {
|
||||
outro(`${chalk.red('✖')} ${error}`);
|
||||
|
||||
if (axios.isAxiosError<{ error?: { message: string } }>(error) && error.response?.status === 401) {
|
||||
if (
|
||||
axios.isAxiosError<{ error?: { message: string } }>(error) &&
|
||||
error.response?.status === 401
|
||||
) {
|
||||
const openAiError = error.response.data.error;
|
||||
|
||||
if (openAiError?.message) outro(openAiError.message);
|
||||
@@ -67,4 +70,18 @@ class OpenAi {
|
||||
};
|
||||
}
|
||||
|
||||
export const getOpenCommitLatestVersion = async (): Promise<
|
||||
string | undefined
|
||||
> => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
'https://unpkg.com/opencommit/package.json'
|
||||
);
|
||||
return data.version;
|
||||
} catch (_) {
|
||||
outro('Error while getting the latest version of opencommit');
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const api = new OpenAi();
|
||||
|
||||
10
src/cli.ts
10
src/cli.ts
@@ -7,8 +7,9 @@ import { configCommand } from './commands/config';
|
||||
import { hookCommand, isHookCalled } from './commands/githook.js';
|
||||
import { prepareCommitMessageHook } from './commands/prepare-commit-msg-hook';
|
||||
import { commit } from './commands/commit';
|
||||
// import { checkIsLatestVersion } from './utils/checkIsLatestVersion';
|
||||
|
||||
const rawArgv = process.argv.slice(2);
|
||||
const extraArgs = process.argv.slice(2);
|
||||
|
||||
cli(
|
||||
{
|
||||
@@ -19,12 +20,13 @@ cli(
|
||||
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
|
||||
help: { description: packageJSON.description }
|
||||
},
|
||||
() => {
|
||||
async () => {
|
||||
// await checkIsLatestVersion();
|
||||
if (isHookCalled) {
|
||||
prepareCommitMessageHook();
|
||||
} else {
|
||||
commit();
|
||||
commit(extraArgs);
|
||||
}
|
||||
},
|
||||
rawArgv
|
||||
extraArgs
|
||||
);
|
||||
|
||||
@@ -16,13 +16,21 @@ import {
|
||||
outro,
|
||||
isCancel,
|
||||
intro,
|
||||
multiselect
|
||||
multiselect,
|
||||
select
|
||||
} from '@clack/prompts';
|
||||
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() !== '');
|
||||
};
|
||||
|
||||
const generateCommitMessageFromGitDiff = async (
|
||||
diff: string
|
||||
diff: string,
|
||||
extraArgs: string[]
|
||||
): Promise<void> => {
|
||||
await assertGitRepo();
|
||||
|
||||
@@ -59,31 +67,61 @@ ${chalk.grey('——————————————————')}`
|
||||
});
|
||||
|
||||
if (isCommitConfirmedByUser && !isCancel(isCommitConfirmedByUser)) {
|
||||
const { stdout } = await execa('git', ['commit', '-m', commitMessage]);
|
||||
const { stdout } = await execa('git', [
|
||||
'commit',
|
||||
'-m',
|
||||
commitMessage,
|
||||
...extraArgs
|
||||
]);
|
||||
|
||||
outro(`${chalk.green('✔')} successfully committed`);
|
||||
|
||||
outro(stdout);
|
||||
const remotes = await getGitRemotes();
|
||||
|
||||
const isPushConfirmedByUser = await confirm({
|
||||
message: 'Do you want to run `git push`?'
|
||||
});
|
||||
if (remotes.length === 1) {
|
||||
const isPushConfirmedByUser = await confirm({
|
||||
message: 'Do you want to run `git push`?'
|
||||
});
|
||||
|
||||
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
|
||||
const pushSpinner = spinner();
|
||||
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
|
||||
const pushSpinner = spinner();
|
||||
pushSpinner.start(`Running \`git push ${remotes[0]}\``);
|
||||
const { stdout } = await execa('git', ['push', remotes[0]]);
|
||||
pushSpinner.stop(
|
||||
`${chalk.green('✔')} successfully pushed all commits to ${remotes[0]}`
|
||||
);
|
||||
if (stdout) outro(stdout);
|
||||
}
|
||||
} else {
|
||||
const selectedRemote = (await select({
|
||||
message: 'Choose a remote to push to',
|
||||
options: remotes.map((remote) => ({ value: remote, label: remote }))
|
||||
})) as string;
|
||||
|
||||
pushSpinner.start('Running `git push`');
|
||||
const { stdout } = await execa('git', ['push']);
|
||||
pushSpinner.stop(`${chalk.green('✔')} successfully pushed all commits`);
|
||||
if (!isCancel(selectedRemote)) {
|
||||
const pushSpinner = spinner();
|
||||
pushSpinner.start(`Running \`git push ${selectedRemote}\``);
|
||||
const { stdout } = await execa('git', ['push', selectedRemote]);
|
||||
pushSpinner.stop(
|
||||
`${chalk.green(
|
||||
'✔'
|
||||
)} successfully pushed all commits to ${selectedRemote}`
|
||||
);
|
||||
|
||||
if (stdout) outro(stdout);
|
||||
if (stdout) outro(stdout);
|
||||
} else outro(`${chalk.gray('✖')} process cancelled`);
|
||||
}
|
||||
} else outro(`${chalk.gray('✖')} process cancelled`);
|
||||
}
|
||||
};
|
||||
|
||||
export async function commit(isStageAllFlag = false) {
|
||||
export async function commit(
|
||||
extraArgs: string[] = [],
|
||||
isStageAllFlag: Boolean = false
|
||||
) {
|
||||
if (isStageAllFlag) {
|
||||
const changedFiles = await getChangedFiles();
|
||||
|
||||
if (changedFiles) await gitAdd({ files: changedFiles });
|
||||
else {
|
||||
outro('No changes detected, write some code and run `oc` again');
|
||||
@@ -106,6 +144,7 @@ export async function commit(isStageAllFlag = false) {
|
||||
}
|
||||
|
||||
const stagedFilesSpinner = spinner();
|
||||
|
||||
stagedFilesSpinner.start('Counting staged files');
|
||||
|
||||
if (!stagedFiles.length) {
|
||||
@@ -118,7 +157,7 @@ export async function commit(isStageAllFlag = false) {
|
||||
isStageAllAndCommitConfirmedByUser &&
|
||||
!isCancel(isStageAllAndCommitConfirmedByUser)
|
||||
) {
|
||||
await commit(true);
|
||||
await commit(extraArgs, true);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -136,7 +175,7 @@ export async function commit(isStageAllFlag = false) {
|
||||
await gitAdd({ files });
|
||||
}
|
||||
|
||||
await commit(false);
|
||||
await commit(extraArgs, false);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -147,7 +186,10 @@ export async function commit(isStageAllFlag = false) {
|
||||
);
|
||||
|
||||
const [, generateCommitError] = await trytm(
|
||||
generateCommitMessageFromGitDiff(await getDiff({ files: stagedFiles }))
|
||||
generateCommitMessageFromGitDiff(
|
||||
await getDiff({ files: stagedFiles }),
|
||||
extraArgs
|
||||
)
|
||||
);
|
||||
|
||||
if (generateCommitError) {
|
||||
|
||||
@@ -21,7 +21,7 @@ const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [
|
||||
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.}`
|
||||
} Use ${translation.localLanguage} to answer.`
|
||||
},
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.User,
|
||||
|
||||
@@ -6,6 +6,7 @@ import ko from '../i18n/ko.json' assert { type: 'json' };
|
||||
import zh_CN from '../i18n/zh_CN.json' assert { type: 'json' };
|
||||
import zh_TW from '../i18n/zh_TW.json' assert { type: 'json' };
|
||||
import ja from '../i18n/ja.json' assert { type: 'json' };
|
||||
import pt_br from '../i18n/pt_br.json' assert { type: 'json' };
|
||||
|
||||
export enum I18nLocals {
|
||||
'en' = 'en',
|
||||
@@ -15,7 +16,8 @@ export enum I18nLocals {
|
||||
'de' = 'de',
|
||||
'fr' = 'fr',
|
||||
'it' = 'it',
|
||||
'ko' = 'ko'
|
||||
'ko' = 'ko',
|
||||
'pt_br' = 'pt_br'
|
||||
};
|
||||
|
||||
export const i18n = {
|
||||
@@ -27,6 +29,7 @@ export const i18n = {
|
||||
fr,
|
||||
it,
|
||||
ko,
|
||||
pt_br
|
||||
};
|
||||
|
||||
export const I18N_CONFIG_ALIAS: { [key: string]: string[] } = {
|
||||
@@ -37,6 +40,8 @@ export const I18N_CONFIG_ALIAS: { [key: string]: string[] } = {
|
||||
de: ['de', 'German' ,'Deutsch'],
|
||||
fr: ['fr', 'French', 'française'],
|
||||
it: ['it', 'Italian', 'italiano'],
|
||||
pt_br: ['pt_br', 'Portuguese', 'português'],
|
||||
en: ['en', 'English', 'english'],
|
||||
};
|
||||
|
||||
export function getI18nLocal(value: string): string | boolean {
|
||||
|
||||
7
src/i18n/pt_br.json
Normal file
7
src/i18n/pt_br.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"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."
|
||||
}
|
||||
|
||||
23
src/utils/checkIsLatestVersion.ts
Normal file
23
src/utils/checkIsLatestVersion.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getOpenCommitLatestVersion } from '../api';
|
||||
import currentPackage from '../../package.json' assert { type: 'json' };
|
||||
import chalk from 'chalk';
|
||||
|
||||
export const checkIsLatestVersion = async () => {
|
||||
const latestVersion = await getOpenCommitLatestVersion();
|
||||
|
||||
if (latestVersion) {
|
||||
const currentVersion = currentPackage.version;
|
||||
|
||||
if (currentVersion !== latestVersion) {
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
`
|
||||
You are not using the latest stable version of OpenCommit with new features and bug fixes.
|
||||
Current version: ${currentVersion}. Latest version: ${latestVersion}.
|
||||
🚀 To update run: npm i -g opencommit@latest.
|
||||
`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { execa } from 'execa';
|
||||
import { outro, spinner } from '@clack/prompts';
|
||||
import { readFileSync } from 'fs';
|
||||
import ignore, { Ignore } from 'ignore';
|
||||
|
||||
export const assertGitRepo = async () => {
|
||||
try {
|
||||
@@ -13,16 +15,34 @@ export const assertGitRepo = async () => {
|
||||
// (file) => `:(exclude)${file}`
|
||||
// );
|
||||
|
||||
export const getOpenCommitIgnore = (): Ignore => {
|
||||
const ig = ignore();
|
||||
|
||||
try {
|
||||
ig.add(readFileSync('.opencommitignore').toString().split('\n'));
|
||||
} catch (e) {}
|
||||
|
||||
return ig;
|
||||
};
|
||||
|
||||
export const getStagedFiles = async (): Promise<string[]> => {
|
||||
const { stdout: files } = await execa('git', [
|
||||
'diff',
|
||||
'--name-only',
|
||||
'--cached'
|
||||
'--cached',
|
||||
'--relative'
|
||||
]);
|
||||
|
||||
if (!files) return [];
|
||||
|
||||
return files.split('\n').sort();
|
||||
const filesList = files.split('\n');
|
||||
|
||||
const ig = getOpenCommitIgnore();
|
||||
const allowedFiles = filesList.filter((file) => !ig.ignores(file));
|
||||
|
||||
if (!allowedFiles) return [];
|
||||
|
||||
return allowedFiles.sort();
|
||||
};
|
||||
|
||||
export const getChangedFiles = async (): Promise<string[]> => {
|
||||
@@ -67,6 +87,7 @@ export const getDiff = async ({ files }: { files: string[] }) => {
|
||||
const { stdout: diff } = await execa('git', [
|
||||
'diff',
|
||||
'--staged',
|
||||
'--',
|
||||
...filesWithoutLocks
|
||||
]);
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ export function mergeStrings(arr: string[], maxStringLength: number): string[] {
|
||||
currentItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
mergedArr.push(currentItem);
|
||||
|
||||
return mergedArr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user