Compare commits

...

15 Commits

Author SHA1 Message Date
di-sukharev
677b7ecad9 1.1.20 2023-03-21 15:36:20 +08:00
di-sukharev
6ab06f9db3 1.1.19 2023-03-21 15:36:16 +08:00
di-sukharev
38ac20612b feat(cli.ts): check if the latest version of opencommit is being used on startup
feat(api.ts): add function to get the latest version of opencommit from unpkg.com

feat(commit.ts): add support for pushing to multiple remotes and prompt user to choose which remote to push to

feat(i18n): add support for Portuguese (Brazil) language and translation for commit messages in pt_br.json

feat(utils): add checkIsLatestVersion function to check if the current version is the latest version
fix(git.ts): add --relative flag to getStagedFiles function to return relative paths
2023-03-21 15:36:05 +08:00
di-sukharev
54b8ba7419 chore(package.json): update version to 1.1.18
refactor(commit.ts): rename getStagedGitDiff to getChangedFiles and remove unused import of generateCommitMessageWithChatCompletion
refactor(commit.ts): reformat import statements for better readability

refactor(commit.ts): reformat code for better readability
feat(commit.ts): add support for selecting remote to push to when multiple remotes are available

refactor(commit.ts): remove unnecessary code and fix formatting
feat(commit.ts): add support for extraArgs parameter in generateCommitMessageFromGitDiff function

chore(checkIsLatestVersion.ts): update console message for clarity and readability
fix(git.ts): handle empty output from git command
fix(git.ts): add -- argument to git diff command to handle file names with leading hyphens
refactor(mergeStrings.ts): remove unnecessary whitespace and add missing semicolon
2023-03-21 15:35:04 +08:00
jessicakuijer
ff81d7e1ca feat(commit.ts) : Suggestion for the user to help how to choose where to push code on remotes (#20)
* 🔨 refactor(commit.ts): rename function to getGitRemotes and improve comments
This commit renames the function to getGitRemotes to better reflect its purpose. Additionally, the comments have been improved to better explain the function's behavior.

🚀 chore(commit.ts): add support for pushing to selected remote
This commit adds support for pushing to the selected remote. The user is prompted to choose a remote from the list of available remotes, and the selected remote is used to push the changes. The push command is run with the selected remote as an argument.

🐛 fix(commit.ts): fix formatting and add missing newline at end of file
This commit fixes the formatting of the code and adds a missing newline at the end of the file. The changes do not affect the functionality of the code.

* 🐛 fix(commit.ts): fix push command to include remote name
 feat(commit.ts): add support for multiple remotes
The push command was not including the remote name, which caused the push to fail. This has been fixed. Support for multiple remotes has been added, allowing the user to choose which remote to push to. If there is only one remote, the push command will automatically use that remote.
2023-03-21 15:14:13 +08:00
Nader Zouaoui
a6ccdb5f77 feature request: telling user when new version is available (#35)
*  feat(cli.ts): add checkIsLatestVersion function

This commit adds a new function that checks if the current version of OpenCommit is the latest version. The function uses the getOpenCommitLatestVersion function from the api module to get the latest version of OpenCommit. If the current version is not the latest version, a warning message is printed to the console, informing the user to update to the latest version to get the latest features and bug fixes.
2023-03-21 15:11:48 +08:00
Adriel Bento
fef8027959 feat(i18n): add support for Portuguese (Brazil) language (pt_br) (#34)
* feat(i18n): add support for Portuguese (Brazil) language (pt_br)
2023-03-21 15:09:48 +08:00
Moret84
0f48cc616e fix(git.ts): add relative flag to git diff command (#37)
The relative flag has been added to the git diff command in the
getStagedFiles function. This flag makes the output of the command
relative to the current working directory, which makes it easier to work
with the file paths and enables executing opencommit from anywhere in
the repository, not just from the root.
2023-03-21 15:08:46 +08:00
di-sukharev
b54ff02930 1.1.18 2023-03-21 15:06:47 +08:00
di-sukharev
7fb46de105 1.1.17 2023-03-21 15:06:43 +08:00
di-sukharev
2f6e98dc30 style(commit.ts): format code with prettier
refactor(commit.ts): add types to commit function parameters
fix(git.ts): handle empty string returned from getStagedFiles function

refactor(mergeStrings.ts): remove unnecessary blank line and add missing semicolon
2023-03-21 15:06:30 +08:00
di-sukharev
2aa6582c52 Merge remote-tracking branch 'origin/dev' 2023-03-21 14:51:44 +08:00
openefit
2acf833cd0 fix(generateCommitMessageFromGitDiff.ts): remove unnecessary character at the end of the line (#36) 2023-03-19 16:04:06 +08:00
Stuart van Beek
3f7025d50a feat: add support for .opencommitignore file (#22)
* feat: add support for .opencommitignore
2023-03-19 16:01:57 +08:00
Nader Zouaoui
d793bf1340 feat: Add support for extra args to be passed to the git commit command (#17)
* feat: Add support for extra args to be passed to the git commit command
2023-03-19 15:58:21 +08:00
13 changed files with 195 additions and 31 deletions

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ application.log
logfile.log
uncaughtExceptions.log
.vscode
src/*.json
src/*.json
.idea

View File

@@ -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
View File

@@ -1,12 +1,12 @@
{
"name": "opencommit",
"version": "1.1.16",
"version": "1.1.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "opencommit",
"version": "1.1.16",
"version": "1.1.20",
"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"
}

View File

@@ -1,6 +1,6 @@
{
"name": "opencommit",
"version": "1.1.16",
"version": "1.1.20",
"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"

View File

@@ -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();

View File

@@ -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
);

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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
View 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."
}

View File

@@ -0,0 +1,22 @@
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.
`
)
);
}
}
};

View File

@@ -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
]);

View File

@@ -9,6 +9,8 @@ export function mergeStrings(arr: string[], maxStringLength: number): string[] {
currentItem = item;
}
}
mergedArr.push(currentItem);
return mergedArr;
}