Compare commits

..

313 Commits

Author SHA1 Message Date
di-sukharev
2540c169dc 3.2.8 2025-06-08 11:17:08 +03:00
di-sukharev
75147e91e7 refactor(git.ts): improve git add completion message for clarity 2025-06-08 10:42:07 +03:00
di-sukharev
59b6edb49c format 2025-06-08 10:41:16 +03:00
di-sukharev
7683004464 build 2025-06-08 10:41:13 +03:00
di-sukharev
e1f657939f chore(deps): bump esbuild from 0.15.18 to 0.25.5 and @actions/github from 5.1.1 to 6.0.1 2025-06-08 10:40:42 +03:00
GPT8
55904155a8 Merge pull request #472 from kakakakakku/fgm
feat(cli.ts): enhance fgm flag to include description and default value for better usability
2025-05-30 10:15:00 +03:00
GPT8
c1be5138b6 Merge pull request #477 from jonsguez/fix/one-line-commit
fix(prompts.ts): edited contradictory assistant output
2025-05-30 10:13:35 +03:00
GPT8
063aa94576 Merge pull request #476 from benleibowitz/master
feat(config): add 'describe' mode to config command for detailed parameter info
2025-05-30 10:11:24 +03:00
jonsguez
668e149ae3 fix(prompts.ts): edited contradictory assistant output
When user wants one line commits the system prompt and the user/assistant one-shot example were contradicting each other, confusing the LLM. This fix modifies the assistant output so that prompt and one-shot are consistent.
2025-05-29 23:09:10 -04:00
Ben Leibowitz
b5fca3155f feat(config): add 'describe' mode to config command for detailed parameter info
This commit adds a new 'describe' mode to the config command, allowing users
to get detailed information about configuration parameters. It includes:

1. New CONFIG_MODES.describe enum value
2. Functions to generate and print help messages for config parameters
3. Updated configCommand to handle the new 'describe' mode
4. README updates to document the new 'describe' functionality
2025-05-29 15:46:48 -04:00
GPT8
bc514f8f4d Merge pull request #461 from kaovilai/dep-updates
fix tests
2025-05-29 21:56:45 +03:00
Tiger Kaovilai
3b868ce6df fix: enable git push functionality in e2e tests
- Added OCO_GITPUSH='true' environment variable to all e2e test commands
- Fixed ESM module import issues in commitlint tests by using process.cwd()
- Replaced import.meta.url usage with relative paths from project root
- All e2e tests now properly test the git push prompt functionality
2025-05-29 12:05:28 -05:00
Tiger Kaovilai
aad62d4fa1 fix: remove duplicate modulePathIgnorePatterns in Jest config
- Fixed Jest configuration error by removing duplicate modulePathIgnorePatterns property
- Consolidated the ignore patterns into a single declaration
- This resolves the TypeScript compilation error preventing tests from running
2025-05-29 12:05:13 -05:00
GPT8
21e92164e7 Merge branch 'master' into dep-updates 2025-05-29 12:37:48 +03:00
kakakakakku
f0381c8b12 feat(cli.ts): enhance fgm flag to include description and default value for better usability 2025-05-19 09:04:31 +09:00
GPT8
f6de2dc775 Merge pull request #467 from EmilienMottet/master
Add OCO_API_CUSTOM_HEADERS
2025-05-03 12:06:21 +03:00
EmilienMottet
6aae1c7bd7 ♻️(engine): extract custom header parsing and update OpenAiEngine
- export parseCustomHeaders from src/utils/engine.ts
- use parseCustomHeaders in OpenAiEngine for config.customHeaders
- remove try/catch and inline JSON.parse logic
- update config test to expect headers as object and drop JSON.parse

Centralize header parsing for reuse and simplify engine code
Update tests to match new header format for clarity
2025-04-30 21:43:44 +02:00
EmilienMottet
71a44fac28 ♻️ refactor OpenAI client options and unify custom headers parsing
Use OpenAI.ClientOptions for stronger typing and clarity
Extract custom headers parsing into parseCustomHeaders util
Simplify getEngine by delegating header parsing to helper
Improve maintainability and reduce code duplication
2025-04-30 14:46:54 +02:00
EmilienMottet
6c48c935e2 add custom HTTP headers support via OCO_API_CUSTOM_HEADERS
Add OCO_API_CUSTOM_HEADERS variable to README, config enum,
and env parsing to allow JSON string of custom headers.
Validate that custom headers are valid JSON in config validator.
Extend AiEngineConfig with customHeaders and pass headers to
OllamaEngine and OpenAiEngine clients when creating requests.
Parse custom headers in utils/engine and warn on invalid format.
Add unit tests to ensure OCO_API_CUSTOM_HEADERS is handled
correctly and merged from env over global config.

This enables users to send additional headers such as
Authorization or tracing headers with LLM API calls.
2025-04-29 20:51:24 +02:00
GPT8
25c6a0d5d4 Merge pull request #462 from jcppkkk/master
refactor(commitlint): update commitlint configuration and prompts
2025-04-25 16:21:42 +03:00
GPT8
b277bf3d50 Merge pull request #465 from jcppkkk/fix-tests
Fix tests
2025-04-25 16:19:59 +03:00
Jethro Yu
83b6e0bbaf fix(removeContentTags): keep newlines to preserve formatting
The space normalization logic is updated to replace only multiple spaces
and tabs with a single space, while preserving newlines. This change
ensures that the formatting of the content is maintained, especially
when dealing with empty line requirements and max line length.
2025-04-25 18:44:06 +08:00
Jethro Yu
2726e51c2a fix(test): Stabilize Jest ESM configuration for CI
Resolves persistent `ReferenceError: exports is not defined` errors
encountered during unit tests (`unit-test (20.x)` job) in the GitHub
Actions CI environment. These errors occurred specifically when
importing `cli-testing-library` in the global Jest setup file
(`test/jest-setup.ts`), despite tests passing locally with the same
Node.js version (v20.19.0).

After iterative testing, the following Jest configuration combination
was identified as necessary to ensure consistent ESM handling and test
success in both local and CI environments:

- Set preset to `ts-jest/presets/default-esm` for stricter ESM rules.

- Configured `transformIgnorePatterns` to ensure Jest transforms specific ESM dependencies (`cli-testing-library`, `@clack`, `cleye`) within `node_modules`.

- Expanded the `transform` pattern (`^.+\\.(ts|tsx|js|jsx|mjs)$`) to explicitly cover various script types handled by `ts-jest`.

- Included explicit `tsconfig` overrides (`module: 'ESNext'`, `target: 'ES2022'`) within the `ts-jest` transform options to resolve potential environment discrepancies.

- Retained `moduleNameMapper` for `.js` imports for reliable module resolution.

- Ensured `cli-testing-library` imports remain in the global setup (`test/jest-setup.ts`).

- Removed test cache clearing from the `test:unit` script in `package.json`.

This configuration now passes reliably across environments.
2025-04-25 18:44:05 +08:00
Jethro Yu
da2742edb1 chore(package.json): add rimraf as a development dependency for improved file management
Including rimraf in the project allows for easier and more efficient
removal of files and directories, enhancing the development workflow,
especially for tasks like cleaning up build artifacts.
2025-04-25 18:44:05 +08:00
Jethro Yu
0ebff3b974 fix(removeContentTags): keep newlines to preserve formatting
The space normalization logic is updated to replace only multiple spaces
and tabs with a single space, while preserving newlines. This change
ensures that the formatting of the content is maintained, especially
when dealing with empty line requirements and max line length.
2025-04-25 18:40:50 +08:00
Jethro Yu
a52589e9fe fix(test): Stabilize Jest ESM configuration for CI
Resolves persistent `ReferenceError: exports is not defined` errors
encountered during unit tests (`unit-test (20.x)` job) in the GitHub
Actions CI environment. These errors occurred specifically when
importing `cli-testing-library` in the global Jest setup file
(`test/jest-setup.ts`), despite tests passing locally with the same
Node.js version (v20.19.0).

After iterative testing, the following Jest configuration combination
was identified as necessary to ensure consistent ESM handling and test
success in both local and CI environments:

- Set preset to `ts-jest/presets/default-esm` for stricter ESM rules.

- Configured `transformIgnorePatterns` to ensure Jest transforms specific ESM dependencies (`cli-testing-library`, `@clack`, `cleye`) within `node_modules`.

- Expanded the `transform` pattern (`^.+\\.(ts|tsx|js|jsx|mjs)$`) to explicitly cover various script types handled by `ts-jest`.

- Included explicit `tsconfig` overrides (`module: 'ESNext'`, `target: 'ES2022'`) within the `ts-jest` transform options to resolve potential environment discrepancies.

- Retained `moduleNameMapper` for `.js` imports for reliable module resolution.

- Ensured `cli-testing-library` imports remain in the global setup (`test/jest-setup.ts`).

- Removed test cache clearing from the `test:unit` script in `package.json`.

This configuration now passes reliably across environments.
2025-04-15 15:58:53 +08:00
Jethro Yu
5381c5e18b chore(package.json): add rimraf as a development dependency for improved file management
Including rimraf in the project allows for easier and more efficient
removal of files and directories, enhancing the development workflow,
especially for tasks like cleaning up build artifacts.
2025-04-15 14:54:06 +08:00
Jethro Yu
9ffcdbdb3b refactor(commitlint): update commitlint configuration and prompts for improved clarity and consistency
The commitlint configuration and prompts have been refactored to enhance
clarity and maintain consistency throughout the codebase. The type
assertion for commitLintConfig is updated to use 'as any' for better
type handling. Additionally, formatting adjustments are made in the
prompts to ensure proper readability and alignment with the defined
conventions. These changes aim to streamline the commit message
generation process and improve overall code maintainability.
2025-04-15 14:00:09 +08:00
Tiger Kaovilai
6bc1d90469 npm run test:unit passing locally
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2025-04-14 05:06:45 -04:00
Tiger Kaovilai
5fb3d75412 fix tests
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2025-04-14 04:57:53 -04:00
GPT8
b3700ae685 Merge pull request #458 from kaovilai/thiknagain
Extends #445 to other providers which many provides deepseek
2025-04-14 11:19:20 +03:00
GPT8
1d81229931 Merge pull request #459 from kaovilai/imp1
chore: update GitHub Actions workflows to use latest action versions and improve configurations
2025-04-14 11:18:29 +03:00
GPT8
22f96b34a5 Merge pull request #460 from Heyian/fix/punycode
Fix the punycode deprecated message from the source (survives building from eslint.config.ts).
2025-04-14 11:17:23 +03:00
Marc-Antoine Favreau
beecedf6f3 chore(deps): update eslint and typescript-eslint dependencies
feat(package.json): add overrides for ajv and whatwg-url packages
fix(deepseek.ts): correct import path for OpenAiEngine and OpenAiConfig
2025-04-12 11:36:12 -04:00
Tiger Kaovilai
566a9b1a52 reuse function
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2025-04-12 04:51:11 -04:00
Tiger Kaovilai
aecc832529 chore: update GitHub Actions workflows to use latest action versions and improve configurations
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2025-04-12 04:33:35 -04:00
GPT8
9418f67636 Merge pull request #457 from jcppkkk/feat/commitlint-improve-consistency-handling
refactor(prompts): streamline commit message generation logic
2025-04-12 10:40:30 +03:00
Tiger Kaovilai
f5c6c313fc Extends #445 to other providers which many provides deepseek 2025-04-11 12:50:16 -04:00
Jethro Yu
fb533f838d refactor(prompts): streamline commit message generation logic
This update introduces a new structure for generating commit messages by defining a constant for commit types and consolidating the logic into a single function. The changes enhance readability and maintainability while ensuring consistent formatting based on the OCO_OMIT_SCOPE configuration.
2025-04-10 16:16:26 +08:00
GPT8
60a7650e1c Merge pull request #456 from kvokka/fix-config-default-settings 2025-04-08 11:19:53 +03:00
Mikhail Beliakov
beb623cdcd fix: config default settings 2025-03-28 19:17:02 +00:00
GPT8
cd5198a96f Merge pull request #454 from jcppkkk/master
feat(commitlint): add additional search path for @commitlint
2025-03-17 11:40:01 +03:00
Jethro Yu
44bd14d2c5 feat(commitlint): add additional search path for @commitlint
This change allows opencommit to locate commitlint installed globally
(with opencommit), which is useful for project that does not use node.js
environment or dose not have opencommit and @commitlint installed locally.
2025-03-17 11:42:54 +08:00
di-sukharev
7feb3ec00e build 2025-03-11 11:37:11 +03:00
di-sukharev
ff896fc225 3.2.7 2025-03-11 11:37:10 +03:00
di-sukharev
6485b8381b 3.2.6 2025-03-11 11:36:57 +03:00
GPT8
7945f44259 Merge pull request #453 from bbauti/omit-scope-feature
feat(config): Add OCO_OMIT_SCOPE option to control scope inclusion in commit messages
2025-03-11 11:30:43 +03:00
di-sukharev
44a35da245 3.2.6 2025-03-11 11:30:21 +03:00
Bautista Igarzabal
f8ce0d32d5 feat(scope): add OMIT_SCOPE config option 2025-03-09 00:41:30 -03:00
Satoru Hoshino
b55bcd5c0b feat(engine): add DeepSeekEngine (#446)
Add DeepSeekEngine to support DeepSeek API.  This
includes a new DeepSeekConfig interface and updates to
the engine selection logic.

feat(README.md, src/commands/config.ts): Add DeepSeek support

Adds support for the DeepSeek AI provider.  Updates the README,
config validation, and model list to include DeepSeek.  This
allows users to utilize DeepSeek models with the OpenCommit
tool.

fix(deepseek.ts): update DeepSeek API base URL to include version number v1

refactor(deepseek.ts): improve DeepSeekEngine constructor

The DeepSeekEngine constructor is refactored to use the
spread syntax for better readability and maintainability
when merging config parameters.  The baseURL is now
explicitly set within the constructor.

fix(README.md): remove Groq from the list of supported AI providers

refactor(deepseek.ts): rename interface DeepseekConfig to DeepSeekEngineeekConfig and fix typo

Revert "refactor(deepseek.ts): rename interface DeepseekConfig to DeepSeekEngineeekConfig and fix typo"

This reverts commit f492367d3885fa97cd685feca889f93d6c465b2f.

refactor(deepseek.ts): Rename DeepseekConfig to DeepSeekConfig for consistency

 feat(engine): add DeepSeekEngine to support DeepSeek API

♻️ refactor(engine): improve OpenAiEngine and create a
    new DeepSeekEngine class to handle DeepSeek API
    requests.  The DeepSeekEngine class inherits from
    OpenAiEngine and overrides the
    generateCommitMessage method to use the DeepSeek
    API.  This change improves code organization and
    maintainability.

🐛 Fix: Correct DeepSeekEngine import and class name

The import path and class name for DeepSeekEngine were
incorrect, causing a runtime error. This commit corrects
the import path and class name to `DeepseekEngine` to
resolve the issue.

Revert "🐛 Fix: Correct DeepSeekEngine import and class name"

This reverts commit 738fd36c434d9df9c3a458b1e8230c974bd2a76e.

🐛 Fix: Correct DeepSeekEngine import and class name

The import path and class name for DeepSeekEngine were corrected to match the actual file and class name.  This fixes a runtime error.

Restore ./out directory to master state
2025-02-10 13:21:39 +03:00
Badraxas
6816379119 feat(engine/ollama.ts): extract message from response data and clean content by removing unnecessary tags (#445)
Co-authored-by: Bastien Mottier <b.mottier@viously.com>
2025-01-30 10:43:05 +01:00
di-sukharev
9d80991805 build 2024-12-14 20:07:57 +01:00
di-sukharev
2e1a39fd2f 3.2.5 2024-12-14 20:07:56 +01:00
di-sukharev
30ddd05764 jump to 3.2.4 2024-12-14 20:07:52 +01:00
di-sukharev
5fd84937c5 build 2024-12-14 20:05:17 +01:00
di-sukharev
dc4fe43642 build 2024-12-14 20:03:35 +01:00
di-sukharev
98afbe21ea 3.2.4 2024-12-14 20:03:34 +01:00
di-sukharev
041465a81c 3.2.3 2024-12-14 20:03:11 +01:00
GPT8
40fa275b4f 3.2.3 (#431)
* 378: fix hook env (#402)

* fix(prepare-commit-msg-hook): update error handling to provide clearer instructions for setting API keys and improve user guidance

* Fix: a bug that causes an error when pushing without setting git remote (#396)

* update deploy commands

* feat(cli): add context flag for providing additional commit message input

* Fix [Bug]: punycode` module is deprecated #426 (#433)

Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>

* npm audit fix (#432)

Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>

* Feat: Add an option to `Don't push` when there are multiple git remotes (#434)

---------

Co-authored-by: GPT8 <57486732+di-sukharev@users.noreply.github.com>

* feat(engine): add support for MLX AI provider (#437)

---------

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>

* feat(config, engine): add support for Mistral AI provider and engine (#436)

* docs(CONTRIBUTING.md): update `TODO.md` reference (#435)

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>

* feat(config, engine): add support for Mistral AI provider and engine

* ```
feat(package): add mistralai and zod dependencies
```

* fix: recreate package-lock.json with node20

* fix: recreate package-lock.json with node v20.18.1 based on branch dev

---------

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: pedro-valentim <>

---------

Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: BILLY Maxime <ozeliurs@gmail.com>
Co-authored-by: Welington Sampaio <welington.sampaio@icloud.com>
Co-authored-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
Co-authored-by: albi ️ <sigismondi.alberto@gmail.com>
Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Pedro Valentim Silva Leite <18179935+pedro-valentim@users.noreply.github.com>
2024-12-14 20:02:25 +01:00
di-sukharev
6c9d89afea Merge branch 'master' into dev 2024-12-14 19:43:53 +01:00
Pedro Valentim Silva Leite
206887612a feat(config, engine): add support for Mistral AI provider and engine (#436)
* docs(CONTRIBUTING.md): update `TODO.md` reference (#435)

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>

* feat(config, engine): add support for Mistral AI provider and engine

* ```
feat(package): add mistralai and zod dependencies
```

* fix: recreate package-lock.json with node20

* fix: recreate package-lock.json with node v20.18.1 based on branch dev

---------

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: pedro-valentim <>
2024-12-11 14:01:50 +01:00
albi ⚡️
26ebfb416d feat(engine): add support for MLX AI provider (#437)
* docs(CONTRIBUTING.md): update `TODO.md` reference (#435)

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>

* feat(engine): add support for MLX AI provider
docs/engine: update documentation to include new engine providers

* fix(mlx.ts): add repetition_penalty option to generateCommitMessage method for improved model behavior

---------

Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
Co-authored-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-12-09 11:02:38 +01:00
Emmanuel Ferdman
6f16191af2 docs(CONTRIBUTING.md): update TODO.md reference (#435)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-11-29 21:33:39 +01:00
Takanori Matsumoto
dd65b9c3e3 Feat: Add an option to Don't push when there are multiple git remotes (#434)
* feat(commit.ts): add option to skip pushing commits to a remote repository to enhance user flexibility during commit process

* Update src/commands/commit.ts

---------

Co-authored-by: GPT8 <57486732+di-sukharev@users.noreply.github.com>
2024-11-28 13:56:01 +01:00
Tiger Kaovilai
6b822eb6d1 npm audit fix (#432)
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2024-11-20 11:49:57 +01:00
Tiger Kaovilai
809f5ff5f7 Fix [Bug]: punycode` module is deprecated #426 (#433)
Signed-off-by: Tiger Kaovilai <passawit.kaovilai@gmail.com>
2024-11-20 11:49:23 +01:00
Welington Sampaio
eca4083a04 Feat/add user input code context (#420)
* feat(cli): add context flag for providing additional commit message input
2024-11-20 11:47:58 +01:00
di-sukharev
0f315ae793 Merge remote-tracking branch 'origin/master' into dev 2024-11-19 18:59:27 +01:00
di-sukharev
25105e4c3a docs(CONTRIBUTING.md): update links to point to the correct repository name for consistency and clarity 2024-09-07 19:11:37 +03:00
GPT8
2769121842 3.2.2 (#413)
* feat(config): add support for groq AI provider, including config validation and engine implementation (#381)

* fix migrations (#414)

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: BILLY Maxime <ozeliurs@gmail.com>
2024-09-07 18:17:17 +03:00
GPT8
31200609b6 fix migrations (#414)
* refactor(config.ts): improve code readability by formatting array elements and conditions
* fix(migrations): handle undefined values correctly when setting default config values
* fix(migrations): ensure process exits with error code on migration failure
* fix(commit.ts): update error handling to provide clearer feedback when commit message generation fails
* feat(config.ts): add cleanUndefinedValues function to sanitize config values by converting 'undefined' and 'null' strings to actual values
* refactor(config.ts): return cleaned config from getConfig function to ensure consistent data types
* chore(migrations): log entriesToSet in migration to assist with debugging and tracking changes
2024-09-07 18:10:15 +03:00
di-sukharev
03b570c85c 3.2.2 2024-09-07 15:16:19 +03:00
di-sukharev
e3529e9ca7 Merge remote-tracking branch 'origin/master' into dev 2024-09-07 15:16:10 +03:00
BILLY Maxime
2d7e3842d6 feat(config): add support for groq AI provider, including config validation and engine implementation (#381) 2024-09-07 15:03:25 +03:00
di-sukharev
8ae927e2dc build 2024-09-06 13:59:28 +03:00
di-sukharev
2859d4ebe3 3.2.1 2024-09-06 13:59:25 +03:00
GPT8
306522e796 3.2.0 (#412)
* 378: fix hook env (#402)

* fix(prepare-commit-msg-hook): update error handling to provide clearer instructions for setting API keys and improve user guidance

* Fix: a bug that causes an error when pushing without setting git remote (#396)

* refactoring v2 (#408)

* 3.2.0

* update deploy commands

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
2024-09-06 13:58:54 +03:00
di-sukharev
a91aa3b4de update deploy commands 2024-09-06 12:17:37 +03:00
di-sukharev
f46336b86a 3.2.0 2024-09-06 12:15:18 +03:00
GPT8
f975e49760 refactoring v2 (#408) 2024-09-06 12:14:15 +03:00
Takanori Matsumoto
fa1cf46050 Fix: a bug that causes an error when pushing without setting git remote (#396) 2024-09-04 18:42:54 +03:00
GPT10
1d19ddd9e2 378: fix hook env (#402)
* fix(prepare-commit-msg-hook): update error handling to provide clearer instructions for setting API keys and improve user guidance

* build
2024-09-03 13:14:45 +03:00
di-sukharev
69b3c00a52 docs(README): update OCO_AI_PROVIDER and OCO_MODEL instructions for clarity and add valid model name options to OCO_MODEL description 2024-09-02 11:13:02 +03:00
di-sukharev
6f4afbfb52 build 2024-09-02 10:19:33 +03:00
di-sukharev
796de7b07e 3.1.2 2024-09-02 10:19:31 +03:00
unconstructive
9ad281a4ee 🔧 (ollama.ts): update client post request to use getUri method for endpoint URL construction (#404) 2024-09-02 10:18:01 +03:00
di-sukharev
1ce357b023 Merge branch 'dev' of github.com:di-sukharev/opencommit into dev 2024-09-01 18:26:33 +03:00
di-sukharev
45dd07d229 Merge branch 'master' into dev 2024-09-01 18:26:20 +03:00
di-sukharev
fa164377e4 build 2024-09-01 18:23:16 +03:00
di-sukharev
0b89767de0 3.1.1 2024-09-01 18:23:14 +03:00
GPT10
2dded4caa4 v 3.1.0 (#397) 2024-09-01 18:21:56 +03:00
GPT10
670f74ebc7 398: make why configurable (#403)
* feat(config): add OCO_WHY configuration option to enable output of change explanations in commit messages
docs(README): document the new OCO_WHY config option and its usage for outputting reasons for changes
2024-09-01 18:17:25 +03:00
Aloha
89d2aa603b feat: support pnpm (#394)
* feat: support pnpm

* fix(commitlint.ts): format commitlint config output as JSON for better readability

* test(commitlint.test.ts): update expected console output for commit message consistency
2024-09-01 16:11:26 +03:00
GPT10
8702c17758 390 add config set tests (#399)
* fix(commit.ts): improve user confirmation handling by exiting on cancel actions to prevent unintended behavior
refactor(commit.ts): streamline conditional checks for user confirmations to enhance code readability and maintainability

* refactor(commit.ts): rename spinner variables for clarity and consistency in commit message generation process
fix(commit.ts): ensure proper stopping of spinners in case of errors during commit message generation and committing process

* refactor(config.ts): extract default configuration to a constant for better maintainability and readability
refactor(config.ts): improve initGlobalConfig function to accept a configPath parameter for flexibility
feat(config.ts): enhance getConfig function to support separate paths for global and environment configurations
test(config.test.ts): update tests to reflect changes in config handling and ensure proper functionality
style(utils.ts): clean up code formatting for consistency and readability
style(tsconfig.json): adjust formatting in tsconfig.json for better clarity and maintainability

* fix(utils.ts): add existsSync check before removing temp directory to prevent errors if directory does not exist (#401)

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
2024-09-01 13:28:06 +03:00
di-sukharev
60597d23eb pump v to 3.1.0 2024-08-27 17:31:40 +03:00
di-sukharev
6f04927369 build 2024-08-27 17:31:23 +03:00
di-sukharev
0c0cf9c627 Merge remote-tracking branch 'origin/master' into dev 2024-08-27 17:31:13 +03:00
GPT10
8fe8e614ac refactoring_v1 (#391) 2024-08-27 17:04:36 +03:00
Takanori Matsumoto
68c9ed359c fix(dependencies): update tr46, webidl-conversions, and whatwg-url to latest versions for compatibility and security improvements (#395) 2024-08-26 10:28:34 +03:00
di-sukharev
1b29f3a9fd build 2024-08-19 12:37:38 +03:00
GPT10
596dcd7cea 3.0.20 (#389) 2024-08-19 12:37:17 +03:00
di-sukharev
eb3be62a4f 3.0.20 2024-08-19 12:34:35 +03:00
di-sukharev
58ad1880ef Merge remote-tracking branch 'origin/dev' into marcelwiegand-master 2024-08-19 12:29:55 +03:00
marcelwiegand
5ee17235cb 🐛(anthropic.ts): fix incorrect usage of 'typeof' operator for MODEL variable 2024-08-19 11:13:37 +02:00
marcelwiegand
f9c7316eb3 🐛(openAi.ts): fix incorrect usage of 'typeof' operator for MODEL variable 2024-08-19 11:13:09 +02:00
di-sukharev
dfe2730a45 build 2024-08-19 10:42:36 +03:00
GPT10
a979ba091a 3.0.19 (#384)
* fix(config.ts): move 'gpt-4o-mini' in MODEL_LIST
* docs(config.ts): update API key validation message with detailed instructions
* refactor(config.ts): simplify model validation logic to check for string type instead of MODEL_LIST

* 3.0.19
2024-08-19 10:41:26 +03:00
di-sukharev
6bbe07a9a1 build 2024-08-19 10:28:45 +03:00
di-sukharev
0156bb9dc9 3.0.19 2024-08-19 10:26:46 +03:00
di-sukharev
e27bbd0ac1 fix(config.ts): move 'gpt-4o-mini' in MODEL_LIST
docs(config.ts): update API key validation message with detailed instructions
refactor(config.ts): simplify model validation logic to check for string type instead of MODEL_LIST
2024-08-19 10:25:24 +03:00
GPT10
f3371ac1e3 3.0.18 (#382)
* 📝 (README.md): add support for custom AI models and update documentation to reflect new environment variable OCO_ AI_PROVIDER (#351)

* feat/add gemini (#349)

* fix: prompt-module/@commitlint (#336)

* docs: spelling fix (#325)

---------

Co-authored-by: tumf <y.takahara@gmail.com>
Co-authored-by: Drew Payment <drew.payment@gmail.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Kellan Stevens <kellan@kellanstevens.com>
Co-authored-by: JMN09 <jmn09@mail.aub.edu>
Co-authored-by: JMN09 <157629053+JMN09@users.noreply.github.com>
2024-08-18 14:55:33 +03:00
di-sukharev
9cc9f9d757 build 2024-08-18 14:47:25 +03:00
di-sukharev
c8d5c53db1 3.0.18 2024-08-18 14:47:23 +03:00
di-sukharev
fc4326233d Merge remote-tracking branch 'origin/master' into dev 2024-08-18 14:23:26 +03:00
XIAOTIAN LIU
07f7a05c4b support read OCO_OLLAMA_API_URL from env to OllamaAiEngine and fix bug in getting ollma model name when slash exists (#375) 2024-08-18 14:22:30 +03:00
di-sukharev
9b2e41d255 fix(package.json): add override for whatwg-url to version 13.0.0 to fix punnycode warning 2024-08-18 14:21:27 +03:00
di-sukharev
6fad862aa5 Merge remote-tracking branch 'origin/master' into dev 2024-08-18 14:04:27 +03:00
XiaomaiTX
c425878f21 feat: allow any string as model (#367)
* refactor(anthropic.ts, openAi.ts): remove model validation against predefined list to allow any string as model, simplifying configuration and improving flexibility
2024-08-18 13:47:18 +03:00
di-sukharev
1fb592afb2 Merge branch 'murex-feature/Flowise' into dev 2024-08-18 13:42:19 +03:00
di-sukharev
684f3dadfc build 2024-08-18 13:41:58 +03:00
di-sukharev
76e6070012 Merge branch 'feature/Flowise' of github.com:murex/opencommit into murex-feature/Flowise 2024-08-18 13:41:38 +03:00
di-sukharev
cb72837866 build 2024-08-18 13:36:02 +03:00
di-sukharev
2432ef9de3 Merge remote-tracking branch 'origin/master' into dev 2024-08-18 13:35:38 +03:00
JMN09
026fd1822a Merge branch 'dev' into feature/Flowise
# Conflicts:
#	out/cli.cjs
#	out/github-action.cjs
#	src/commands/config.ts
#	src/utils/engine.ts
2024-08-14 15:30:12 +03:00
JMN09
b42a52ef24 Merge branch 'master' of https://github.com/murex/opencommit into feature/Flowise 2024-08-13 16:26:25 +03:00
XYCode Kerman
10b031ab36 fix(i18n): zh_CN is wrong (#379) 2024-08-13 10:59:48 +03:00
Isaque Borges
d63b825ae5 fix(config.ts): invalid default config (#371)
* 📝 (README.md): add support for custom AI models and update documentation to reflect new environment variable OCO_ AI_PROVIDER (#351)

* Feat/add gemini (#349)

* 🐛Fix: prompt-module/@commitlint (#336)

* fix(commitlint/utils.ts): correct variable used in search for JSON block end tag

* ♻️ (commitlint/config.ts & pwd-commitlint.ts): Refactor commitlint config loading to support both CJS and ESM modules

💡 (pwd-commitlint.ts): Add detailed comments and error handling for better clarity and robustness in commitlint module loading process

*  (package.json): Add setup script for e2e tests to install dependencies for commitlint configurations
🔧 (setup.sh): Add shell script to set up commitlint configurations for e2e tests

*  (config.ts): Add support for OCO_TEST_MOCK_TYPE configuration key to define test mock type for testing purposes
📝 (config.ts): Update documentation for OCO_TEST_MOCK_TYPE configuration key in configValidators and getConfig functions
📝 (testAi.ts): Add TEST_MOCK_TYPES constant array to define supported test mock types
📝 (testAi.ts): Update generateCommitMessage function to use OCO_TEST_MOCK_TYPE from config for different test mock types
📝 (commitlint.test.ts): Add e2e test for running "oco commitlint force" with different @commitlint versions using CJS and ESM
📝 (utils.ts): Add wait function to introduce delay in milliseconds for testing purposes

*  (commitlint.test.ts): refactor setupCommitlint function to accept a version parameter for better code organization and readability
📝 (commitlint.test.ts): add test case for commitlint@9 using CJS to ensure proper functionality and compatibility
📝 (commitlint.test.ts): add test case for commitlint@18 using CJS to ensure proper functionality and compatibility
📝 (commitlint.test.ts): add test case for commitlint@19 using ESM to ensure proper functionality and compatibility

* 🔧 (commitlint.test.ts): remove unnecessary commands to create and add index.ts file before running tests

* refactor(test/e2e/prompt-module/commitlint.test.ts): remove unused import configure
style(test/e2e/prompt-module/commitlint.test.ts): add missing semicolon for consistency
test(test/e2e/prompt-module/commitlint.test.ts): add e2e tests for @commitlint prompt-module integration

*  (e2e tests): add package.json copying to setupCommitlint for version accuracy
♻️ (commitlint config): refactor commitlint.config.js to use ES module syntax
 (package.json): specify "type": "module" to support ES module syntax

* docs: spelling fix (#325)

* 3.0.17

* ♻️ (config.ts): refactor OCO_AZURE_ENDPOINT default value from empty string to undefined resolving #352 - creates invalid config file

---------

Co-authored-by: tumf <y.takahara@gmail.com>
Co-authored-by: Drew Payment <drew.payment@gmail.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Kellan Stevens <kellan@kellanstevens.com>
Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2024-08-13 10:25:26 +03:00
JMN09
b66da48106 build 2024-08-02 17:52:16 +03:00
JMN09
6f24afc600 feature(commands/configs.ts, engine/flowise.ts, utils/engine.ts): Incorporated flowise in configuration variables, added it as an AiEngine, and added it as a return value for getEngine() 2024-08-02 17:50:34 +03:00
di-sukharev
a80dcb03c4 build 2024-07-20 11:05:26 +03:00
GPT10
bebbed856f v 3.0.17 (#366) 2024-07-20 11:03:05 +03:00
di-sukharev
2d5882c257 Merge remote-tracking branch 'origin/master' into dev 2024-07-20 11:00:56 +03:00
Ying-Shan Lin
37fb140563 Update Claude Models (#362) 2024-07-20 11:00:10 +03:00
di-sukharev
aebe7e200f 3.0.17 2024-07-20 10:59:52 +03:00
di-sukharev
6f1e4bcec6 Merge remote-tracking branch 'origin/master' into dev 2024-07-20 10:58:16 +03:00
XiaomaiTX
2059549dce feat: Add all of OpenAI GPT models (#365) 2024-07-20 10:48:00 +03:00
Kellan Stevens
8361dc6838 docs: spelling fix (#325) 2024-07-04 11:08:44 +03:00
Takanori Matsumoto
73ccae9de3 🐛Fix: prompt-module/@commitlint (#336)
* fix(commitlint/utils.ts): correct variable used in search for JSON block end tag

* ♻️ (commitlint/config.ts & pwd-commitlint.ts): Refactor commitlint config loading to support both CJS and ESM modules

💡 (pwd-commitlint.ts): Add detailed comments and error handling for better clarity and robustness in commitlint module loading process

*  (package.json): Add setup script for e2e tests to install dependencies for commitlint configurations
🔧 (setup.sh): Add shell script to set up commitlint configurations for e2e tests

*  (config.ts): Add support for OCO_TEST_MOCK_TYPE configuration key to define test mock type for testing purposes
📝 (config.ts): Update documentation for OCO_TEST_MOCK_TYPE configuration key in configValidators and getConfig functions
📝 (testAi.ts): Add TEST_MOCK_TYPES constant array to define supported test mock types
📝 (testAi.ts): Update generateCommitMessage function to use OCO_TEST_MOCK_TYPE from config for different test mock types
📝 (commitlint.test.ts): Add e2e test for running "oco commitlint force" with different @commitlint versions using CJS and ESM
📝 (utils.ts): Add wait function to introduce delay in milliseconds for testing purposes

*  (commitlint.test.ts): refactor setupCommitlint function to accept a version parameter for better code organization and readability
📝 (commitlint.test.ts): add test case for commitlint@9 using CJS to ensure proper functionality and compatibility
📝 (commitlint.test.ts): add test case for commitlint@18 using CJS to ensure proper functionality and compatibility
📝 (commitlint.test.ts): add test case for commitlint@19 using ESM to ensure proper functionality and compatibility

* 🔧 (commitlint.test.ts): remove unnecessary commands to create and add index.ts file before running tests

* refactor(test/e2e/prompt-module/commitlint.test.ts): remove unused import configure
style(test/e2e/prompt-module/commitlint.test.ts): add missing semicolon for consistency
test(test/e2e/prompt-module/commitlint.test.ts): add e2e tests for @commitlint prompt-module integration

*  (e2e tests): add package.json copying to setupCommitlint for version accuracy
♻️ (commitlint config): refactor commitlint.config.js to use ES module syntax
 (package.json): specify "type": "module" to support ES module syntax
2024-07-04 11:08:08 +03:00
Drew Payment
c58e0c62a4 Feat/add gemini (#349) 2024-07-04 11:03:54 +03:00
tumf
a4b4e65011 📝 (README.md): add support for custom AI models and update documentation to reflect new environment variable OCO_ AI_PROVIDER (#351) 2024-07-04 11:03:17 +03:00
senovr
18f52772b3 Make endpoint url for Ollama configurable (#355) 2024-07-02 20:59:55 +03:00
di-sukharev
fef25a2d06 build 2024-05-25 19:23:06 +03:00
di-sukharev
bf578da16a 3.0.16 2024-05-25 19:23:04 +03:00
di-sukharev
41330d5517 Merge branch 'master' of github.com:di-sukharev/opencommit 2024-05-25 19:22:06 +03:00
GPT10
a703fde7b2 release candidate (#348)
* test:  add the first E2E test and configuration to CI (#316)

* add tests

* Add push config (#220)

* feat: add instructions and support for configuring gpt-4-turbo (#320)

* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

*  fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)

* test(oneFile.test.ts): update test expectations to match new push prompt text

* build

* Feat: Add Claude 3 support (#318)

* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)

* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build

* ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)

* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.

* new version

* improve OCO_AI_PROVIDER=ollama[/model name] (#327)

* 🐛 (config.ts, engine/ollama.ts, utils/engine.ts): improve Ollama AI configuration and usage
 (config.ts): add support for multiple Ollama models and allow users to specify the model in their config
 (engine/ollama.ts, utils/engine.ts): refactor code to use the specified Ollama model instead of hardcoding it

* add build results

* add support for Azure OpenAI API - continue MR 167 (#324)

*  feat(api.ts): add support for Azure OpenAI API

The Azure OpenAI API is now supported in addition to the OpenAI API. The API type can be specified in the configuration file using the OPENAI_API_TYPE key. If the key is not specified, the default value is 'openai'. The AzureOpenAIApi class is added to the utils folder to handle the Azure OpenAI API calls. The createChatCompletion method is implemented in the AzureOpenAIApi class to handle the chat completion requests. The method is called in the generateCommitMessage method in the OpenAi class if the apiType is set to 'azure'.

* 🐛 fix(AzureOpenAI.ts): fix import path for AxiosRequestConfig to avoid conflicts with openai's axios dependency

In AzureOpenAI.ts, the import path for AxiosRequestConfig was changed to avoid conflicts with openai's axios dependency, which was causing lint errors.

* 🔧 fix(AzureOpenAI.ts): import RequiredError to fix error handling and remove commented out debug code

The RequiredError class was not being imported from the openai/dist/base module, causing errors to be thrown incorrectly. This has been fixed by importing the RequiredError class. Debug code has been removed and comments have been updated to reflect the changes made.

* 🔇 chore(AzureOpenAI.ts): remove console.log statement and translate Japanese comment

The commented console.log statement was removed to improve code cleanliness.

* 🔥 refactoring(api.ts, AzureOpenAI.ts): Leverage openai npm package
🐛 fix(config.ts): API Key string validation

*  (README.md): update opencommit command to set OCO_AI_PROVIDER instead of OPENAI_API_TYPE to improve consistency and clarity in configuration
♻️ (config.ts): update OCO_AI_PROVIDER enum in configValidators to include 'azure' and remove unnecessary conditionals to improve maintainability and extensibility
⬆️ (config.ts): add OCO_AZURE_API_VERSION to ConfigType and getConfig() to support new azure api version configuration
♻️ (engine/ollama.ts): add space between temperature and top_p properties to improve readability
♻️ (engine/openAi.ts): refactor OpenAi class to improve readability and maintainability by extracting configuration logic into separate switch statement
🔧 (generateCommitMessageFromGitDiff.ts): refactor MAX_TOKENS_INPUT and MAX_TOKENS_OUTPUT lines to improve readability
🔧 (generateCommitMessageFromGitDiff.ts): refactor generateCommitMessageByDiff and getMessagesPromisesByChangesInFile functions to use destructuring and improve readability
♻️ (generateCommitMessageFromGitDiff.ts): refactor getCommitMsgsPromisesFromFileDiffs function to use destructuring and improve readability
📝 (modules/commitlint/config.ts): add missing types to function parameters and improve readability by removing unnecessary comments and whitespace
📝 (modules/commitlint/utils.ts): fix indentation and add missing types to function parameters
📝 (prompts.ts): update INIT_MAIN_PROMPT description to include clarification on the use of present tense and line length
📝 (version.ts): fix import statements and add missing types to function parameters

*  (package.json): add @azure/openai dependency to support integration with Azure AI services
🔧 (config.ts): change CONFIG_KEYS.OCO_AZURE_API_VERSION to CONFIG_KEYS.OCO_AZURE_ENDPOINT to improve semantics and allow configuration of Azure endpoint URL
♻️ (config.ts): refactor configValidators to use OCO_AZURE_ENDPOINT instead of OCO_AZURE_API_VERSION and update validation message for OCO_AZURE_ENDPOINT
 (config.ts): add OCO_AZURE_ENDPOINT to getConfig function to retrieve Azure endpoint configuration from environment variables
 (azure.ts): introduce a new file azure.ts to implement Azure AI engine
 (azure.ts): implement generateCommitMessage function in Azure AI engine
 (prompts.ts): add a new line to INIT_MAIN_PROMPT to mention that changes within a single file should be described with a single commit message
♻️ (engine.ts): refactor getEngine function to add support for 'azure' as the AI provider and return the azure engine

* 📝 (prompts.ts): remove unnecessary information about crafting a concise commit message with a one single message for OCO_ONE_LINE_COMMIT configuration

* 3.0.14 (#333)

* test:  add the first E2E test and configuration to CI (#316)

* add tests

* Add push config (#220)

* feat: add instructions and support for configuring gpt-4-turbo (#320)

* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

*  fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)

* test(oneFile.test.ts): update test expectations to match new push prompt text

* build

* Feat: Add Claude 3 support (#318)

* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)

* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build

* ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)

* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.

* new version

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>

* 3.0.15

* build

* 🐛 (prepare-commit-msg-hook.ts): improve error message to cover missing OCO_ANTHROPIC_API_KEY and OCO_AZURE_API_KEY in addition to OCO_OPENAI_API_KEY

* 🐛 (azure.ts): fix check for OCO_AI_PROVIDER to properly assign provider variable
🐛 (azure.ts): initialize OpenAIClient only if provider is 'azure'
🔧 (Dockerfile): rearrange instructions to optimize caching by copying package.json and package-lock.json first before running npm ci and copying the rest of the files
🔧 (e2e/noChanges.test.ts): remove unnecessary line break
🔧 (e2e/oneFile.test.ts): remove unnecessary line break

---------

Co-authored-by: Takuya Ono <takuya-o@users.osdn.me>
Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>
Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* feat(cli.ts, commit.ts): add `--yes` flag to skip commit confirmation prompt (#341)

docs(README.md): document the `--yes` flag usage in README for user guidance

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>
Co-authored-by: tumf <y.takahara@gmail.com>
Co-authored-by: Jakub Rosa <jakub.rosa.95@gmail.com>
Co-authored-by: Takuya Ono <takuya-o@users.osdn.me>
Co-authored-by: Ignacio Lago <ignacio@ignaciolago.com>
2024-05-25 19:21:50 +03:00
di-sukharev
c5ee5cd8df Merge remote-tracking branch 'origin/dev' 2024-05-25 19:14:41 +03:00
Ignacio Lago
312540456a feat(cli.ts, commit.ts): add --yes flag to skip commit confirmation prompt (#341)
docs(README.md): document the `--yes` flag usage in README for user guidance
2024-05-23 10:56:35 +03:00
yowatari
7ddbaf477a feat: add instructions and support for configuring gpt-4o (#340)
* feat(config.ts): add 'gpt-4o' to supported model list for enhanced model options
docs(config.ts): update error message to include 'gpt-4o' in the list of supported models

* docs(README.md): update OCO_MODEL options to include 'gpt-4o' model
2024-05-14 14:16:21 +03:00
Kai Xu
9a0f412fff fix(config.ts): add optional config parameter to [CONFIG_KEYS.OCO_ MODEL] validator to allow for dynamic model selection based on configuration (#337)
feat(engine/ollama.ts): integrate with config command to load OCO_MODEL from configuration and use it as the default AI engine model
2024-05-12 15:56:04 +03:00
Kotaro Nishigori
7cd3ef09cb Update README.md (#335) 2024-05-09 11:23:54 +03:00
Jakub Rosa
f814c6b89d add support for Azure OpenAI API - continue MR 167 (#324)
*  feat(api.ts): add support for Azure OpenAI API

The Azure OpenAI API is now supported in addition to the OpenAI API. The API type can be specified in the configuration file using the OPENAI_API_TYPE key. If the key is not specified, the default value is 'openai'. The AzureOpenAIApi class is added to the utils folder to handle the Azure OpenAI API calls. The createChatCompletion method is implemented in the AzureOpenAIApi class to handle the chat completion requests. The method is called in the generateCommitMessage method in the OpenAi class if the apiType is set to 'azure'.

* 🐛 fix(AzureOpenAI.ts): fix import path for AxiosRequestConfig to avoid conflicts with openai's axios dependency

In AzureOpenAI.ts, the import path for AxiosRequestConfig was changed to avoid conflicts with openai's axios dependency, which was causing lint errors.

* 🔧 fix(AzureOpenAI.ts): import RequiredError to fix error handling and remove commented out debug code

The RequiredError class was not being imported from the openai/dist/base module, causing errors to be thrown incorrectly. This has been fixed by importing the RequiredError class. Debug code has been removed and comments have been updated to reflect the changes made.

* 🔇 chore(AzureOpenAI.ts): remove console.log statement and translate Japanese comment

The commented console.log statement was removed to improve code cleanliness.

* 🔥 refactoring(api.ts, AzureOpenAI.ts): Leverage openai npm package
🐛 fix(config.ts): API Key string validation

*  (README.md): update opencommit command to set OCO_AI_PROVIDER instead of OPENAI_API_TYPE to improve consistency and clarity in configuration
♻️ (config.ts): update OCO_AI_PROVIDER enum in configValidators to include 'azure' and remove unnecessary conditionals to improve maintainability and extensibility
⬆️ (config.ts): add OCO_AZURE_API_VERSION to ConfigType and getConfig() to support new azure api version configuration
♻️ (engine/ollama.ts): add space between temperature and top_p properties to improve readability
♻️ (engine/openAi.ts): refactor OpenAi class to improve readability and maintainability by extracting configuration logic into separate switch statement
🔧 (generateCommitMessageFromGitDiff.ts): refactor MAX_TOKENS_INPUT and MAX_TOKENS_OUTPUT lines to improve readability
🔧 (generateCommitMessageFromGitDiff.ts): refactor generateCommitMessageByDiff and getMessagesPromisesByChangesInFile functions to use destructuring and improve readability
♻️ (generateCommitMessageFromGitDiff.ts): refactor getCommitMsgsPromisesFromFileDiffs function to use destructuring and improve readability
📝 (modules/commitlint/config.ts): add missing types to function parameters and improve readability by removing unnecessary comments and whitespace
📝 (modules/commitlint/utils.ts): fix indentation and add missing types to function parameters
📝 (prompts.ts): update INIT_MAIN_PROMPT description to include clarification on the use of present tense and line length
📝 (version.ts): fix import statements and add missing types to function parameters

*  (package.json): add @azure/openai dependency to support integration with Azure AI services
🔧 (config.ts): change CONFIG_KEYS.OCO_AZURE_API_VERSION to CONFIG_KEYS.OCO_AZURE_ENDPOINT to improve semantics and allow configuration of Azure endpoint URL
♻️ (config.ts): refactor configValidators to use OCO_AZURE_ENDPOINT instead of OCO_AZURE_API_VERSION and update validation message for OCO_AZURE_ENDPOINT
 (config.ts): add OCO_AZURE_ENDPOINT to getConfig function to retrieve Azure endpoint configuration from environment variables
 (azure.ts): introduce a new file azure.ts to implement Azure AI engine
 (azure.ts): implement generateCommitMessage function in Azure AI engine
 (prompts.ts): add a new line to INIT_MAIN_PROMPT to mention that changes within a single file should be described with a single commit message
♻️ (engine.ts): refactor getEngine function to add support for 'azure' as the AI provider and return the azure engine

* 📝 (prompts.ts): remove unnecessary information about crafting a concise commit message with a one single message for OCO_ONE_LINE_COMMIT configuration

* 3.0.14 (#333)

* test:  add the first E2E test and configuration to CI (#316)

* add tests

* Add push config (#220)

* feat: add instructions and support for configuring gpt-4-turbo (#320)

* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

*  fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)

* test(oneFile.test.ts): update test expectations to match new push prompt text

* build

* Feat: Add Claude 3 support (#318)

* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)

* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build

* ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)

* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.

* new version

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>

* 3.0.15

* build

* 🐛 (prepare-commit-msg-hook.ts): improve error message to cover missing OCO_ANTHROPIC_API_KEY and OCO_AZURE_API_KEY in addition to OCO_OPENAI_API_KEY

* 🐛 (azure.ts): fix check for OCO_AI_PROVIDER to properly assign provider variable
🐛 (azure.ts): initialize OpenAIClient only if provider is 'azure'
🔧 (Dockerfile): rearrange instructions to optimize caching by copying package.json and package-lock.json first before running npm ci and copying the rest of the files
🔧 (e2e/noChanges.test.ts): remove unnecessary line break
🔧 (e2e/oneFile.test.ts): remove unnecessary line break

---------

Co-authored-by: Takuya Ono <takuya-o@users.osdn.me>
Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>
Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2024-05-09 11:23:00 +03:00
tumf
74024a4997 improve OCO_AI_PROVIDER=ollama[/model name] (#327)
* 🐛 (config.ts, engine/ollama.ts, utils/engine.ts): improve Ollama AI configuration and usage
 (config.ts): add support for multiple Ollama models and allow users to specify the model in their config
 (engine/ollama.ts, utils/engine.ts): refactor code to use the specified Ollama model instead of hardcoding it

* add build results
2024-05-07 10:51:24 +03:00
di-sukharev
cb7f5dd44d build 2024-05-05 19:12:25 +03:00
di-sukharev
9cf2db84a9 3.0.15 2024-05-05 19:12:23 +03:00
GPT10
ec307d561f 3.0.14 (#333)
* test:  add the first E2E test and configuration to CI (#316)

* add tests

* Add push config (#220)

* feat: add instructions and support for configuring gpt-4-turbo (#320)

* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

*  fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)

* test(oneFile.test.ts): update test expectations to match new push prompt text

* build

* Feat: Add Claude 3 support (#318)

* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>

* 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)

* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build

* ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)

* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.

* new version

---------

Co-authored-by: Takanori Matsumoto <matscube@gmail.com>
Co-authored-by: Moret84 <aurelienrivet@hotmail.fr>
Co-authored-by: yowatari <4982161+YOwatari@users.noreply.github.com>
Co-authored-by: metavind <94786679+metavind@users.noreply.github.com>
2024-05-05 19:07:55 +03:00
di-sukharev
058bad95cd new version 2024-05-05 19:04:19 +03:00
di-sukharev
7469633e3d Merge remote-tracking branch 'origin/master' into dev 2024-05-05 19:03:02 +03:00
Takanori Matsumoto
278e4cb4c2 ♻️ refactor(config.ts): Addition of UnitTest environment and unittest for commands/config.ts#getConfig (#330)
* feat(jest.config.ts): update jest preset for TS ESM support and ignore patterns
feat(package.json): add test:unit script with NODE_OPTIONS for ESM
refactor(src/commands/config.ts): improve dotenv usage with dynamic paths
feat(src/commands/config.ts): allow custom config and env paths in getConfig
refactor(src/commands/config.ts): streamline environment variable access

feat(test/unit): add unit tests for config handling and utility functions

- Implement unit tests for `getConfig` function to ensure correct behavior
  in various scenarios including default values, global config, and local
  env file precedence.
- Add utility function `prepareFile` for creating temporary files during
  tests, facilitating testing of file-based configurations.

* feat(e2e.yml): add unit-test job to GitHub Actions for running unit tests on pull requests

* ci(test.yml): add GitHub Actions workflow for unit and e2e tests on pull requests

* refactor(config.ts): streamline environment variable access using process.env directly
test(config.test.ts): add setup and teardown for environment variables in tests to ensure test isolation

* feat(package.json): add `test:all` script to run all tests in Docker
refactor(package.json): consolidate Docker build steps into `test:docker-build` script for DRY principle
fix(package.json): ensure `test:unit:docker` and `test:e2e:docker` scripts use the same Docker image and remove container after run
chore(test/Dockerfile): remove default CMD to allow dynamic test script execution in Docker

* refactor(config.test.ts): anonymize API keys in tests for better security practices

* feat(config.test.ts): add tests for OCO_ANTHROPIC_API_KEY configuration

* refactor(config.ts): streamline path imports and remove unused DotenvParseOutput

- Simplify path module imports by removing default import and using named imports for `pathJoin` and `pathResolve`.
- Remove unused `DotenvParseOutput` import to clean up the code.

* refactor(config.test.ts): simplify API key mock values for clarity in tests

* test(config.test.ts): remove tests for default config values and redundant cases

- Removed tests that checked for default config values when no config or env files are present, as these scenarios are now handled differently.
- Eliminated tests for empty global config and local env files to streamline testing focus on actual config loading logic.
- Removed test for prioritizing local env over global config due to changes in config loading strategy, simplifying the configuration management.
2024-05-05 18:46:15 +03:00
Takanori Matsumoto
e19305dee2 🐛bug fix: enable to use the new format of OpenAI's project API Key (#328)
* fix(config.ts): remove validation for OCO_OPENAI_API_KEY length to accommodate variable key lengths

* build
2024-05-02 12:07:21 +03:00
metavind
673eee209d Feat: Add Claude 3 support (#318)
* 3.0.12

* build

* feat: anthropic claude 3 support

* fix: add system prompt

* fix: type check

* fix: package version

* fix: update anthropic for dependency bug fix

* feat: update build files

* feat: update version number

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2024-04-14 13:20:12 +08:00
Takanori Matsumoto
91399a0c68 fix the broken E2E tests due to the addition of OCO_GITPUSH (#321)
* test(oneFile.test.ts): update test expectations to match new push prompt text

* build
2024-04-13 20:42:32 +08:00
yowatari
a4480893cb feat: add instructions and support for configuring gpt-4-turbo (#320)
* 3.0.12

* build

* feat: add 'gpt-4-turbo' to supported models in README and config validation

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2024-04-12 11:55:41 +08:00
Moret84
c410486e30 Add push config (#220) 2024-03-29 10:57:20 +08:00
Takanori Matsumoto
5cda8b1b03 test: add the first E2E test and configuration to CI (#316)
* add tests
2024-03-25 12:01:05 +08:00
di-sukharev
0ac7211ff7 build 2024-03-18 15:22:14 +08:00
di-sukharev
670a758bee 3.0.12 2024-03-18 15:22:13 +08:00
di-sukharev
bdc98c6fa8 docs(README.md): update description text for better readability
style(git.ts): add line breaks for better code readability
2024-03-18 15:21:38 +08:00
di-sukharev
f0251d14bb build 2024-03-18 15:19:59 +08:00
di-sukharev
61f1a27377 Merge remote-tracking branch 'origin/master' into dev 2024-03-18 15:19:45 +08:00
Takanori Matsumoto
c39181e5bd fix(cli.ts): fix the commit function call by passing the correct arguments to the commit function (#313) 2024-03-18 12:29:35 +08:00
Mouadh HSOUMI
45dc2c4535 feat: add OCO_ONE_LINE_COMMIT config for enabling one line commit message (#307) 2024-03-09 12:44:12 +08:00
digitalstudium
a192441f68 Switch ollama api endpoint from /api/generate to /api/chat (#304)
* 3.0.11

* build

* docs: update ollama usage readme (#301)

Signed-off-by: Albert Simon <albert.simon.sge@mango.com>
Co-authored-by: Albert Simon <albert.simon.sge@mango.com>

* 🚨 BREAKING CHANGES 🚨

- feat(engine/ollama): add support for local models and change prompt format to improve AI performance
+ fix(engine/ollama): fix issue with local model not responding correctly to requests

The commit message is now more concise, clear, and informative. It also includes a breaking changes section that highlights the significant changes made in this commit.

---------

Signed-off-by: Albert Simon <albert.simon.sge@mango.com>
Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
Co-authored-by: Albert Simon <47634918+willyw0nka@users.noreply.github.com>
Co-authored-by: Albert Simon <albert.simon.sge@mango.com>
Co-authored-by: Константин Шуткин <shutkin-kn@mosmetro.ru>
2024-03-08 10:08:52 +08:00
Kuma Taro
744bb9b11d Feat: Update OCO_MODEL to Latest Version and Remove Legacy GPT-3.5 Turbo Models (#299)
* fix(config.ts): improve code formatting for better readability
feat(config.ts): add additional supported models for OCO_MODEL configuration
feat(config.ts): update default OCO_MODEL value to 'gpt-3.5-turbo' for better compatibility

* docs(README.md): update OCO_MODEL options to include new GPT-4 models and remove
mention of 'gpt-3.5-turbo-16k' model
docs(README.md): update default OCO_MODEL to 'gpt-3.5-turbo' and provide
instructions for switching to GPT-4 models, specifically 'gpt-4-0125-preview'
and 'gpt-4-turbo-preview'
docs(README.md): update OCO_MODEL configuration in CI/CD pipeline to 'gpt-3.5-turbo'
docs(README.md): update cost information related to default and GPT-4 models in
OpenCommit
2024-03-06 22:00:35 +08:00
Albert Simon
f3adc86693 docs: update ollama usage readme (#301)
Signed-off-by: Albert Simon <albert.simon.sge@mango.com>
Co-authored-by: Albert Simon <albert.simon.sge@mango.com>
2024-03-06 21:56:53 +08:00
di-sukharev
714fac0637 build 2024-03-03 20:02:23 +08:00
di-sukharev
eaf6600299 3.0.11 2024-03-03 20:02:21 +08:00
di-sukharev
401be04b4d fix(githook.ts): update error message to include command example for better user guidance 2024-03-03 19:58:23 +08:00
di-sukharev
a9a2131ebf build 2024-03-03 19:57:37 +08:00
Maxence d'Espeuilles
7dd8094760 🔧 refactor(commit.ts): remove messageTemplate from extraArgs to avoid duplication in commit message (#279) 2024-03-03 19:56:53 +08:00
di-sukharev
a3d3363a01 build 2024-03-03 19:44:31 +08:00
di-sukharev
75d0f57f09 3.0.10 2024-03-03 19:44:30 +08:00
di-sukharev
8c92b92868 Merge branch 'dev' 2024-03-03 19:44:07 +08:00
di-sukharev
a33027b4db 🔀 refactor(commit.ts): rearrange function parameters for better readability
🎨 style(commit.ts): improve code formatting for generateCommitMessageByDiff function call
2024-03-03 19:43:40 +08:00
di-sukharev
c1797de3da build 2024-03-03 19:43:15 +08:00
di-sukharev
3d49081f6d build 2024-03-03 19:39:00 +08:00
di-sukharev
8c318d96f4 Merge branch 'dev' of github.com:di-sukharev/opencommit into dev 2024-03-03 19:37:21 +08:00
Malthe Poulsen
9b7337f67f Support Gitmoji Format in Commit Messages (#249)
* 📝 docs(prompts.ts): update prompt message to include information about GitMoji convention and descriptions of changes

📝 docs(prompts.ts): update prompt message to include information about GitMoji convention and descriptions of changes

* 🎨 (prompts.ts): import `removeConventionalCommitWord` function to remove conventional commit word from commit prompts
🐛 (prompts.ts): remove conventional commit word from `commitFix` and `commitFeat` prompts to improve clarity
📝 (removeConventionalCommitWord.ts): add `removeConventionalCommitWord` function to remove conventional commit word from commit message

* 📝 (package.json): update version from 3.0.3 to 3.0.0 to align with the latest release

* 🔧 (cli.ts): add a new flag 'fgm' to the 'flags' object to support the '--fgm' flag in the CLI command
🔧 (commit.ts): pass the value of the 'fgm' flag to the 'commit' function to enable or disable full GitMoji specification
♻️ (commit.ts): refactor the 'commit' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'commit' function recursively
♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'generateCommitMessageByDiff' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'generateCommitMessageChatCompletionPrompt' function
♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'generateCommitMessageChatCompletionPrompt' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'getMainCommitPrompt' function
♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'getCommitMsgsPromisesFromFileDiffs' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'getMessagesPromisesByChangesInFile' function
♻️ (generateCommitMessageFromGitDiff.ts): refactor the 'getMessagesPromisesByChangesInFile' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'generateCommitMessageChatCompletionPrompt' function
♻️ (prompts.ts): refactor the 'getMainCommitPrompt' function to accept the 'fullGitMojiSpec' parameter and pass it to the 'INIT_MAIN_PROMPT' function

* 📝 (README.md): add documentation for the `--fgm` flag in the `oco` command to enable the use of the full GitMoji specification

* 📝 (README.md): update flag description for using full GitMoji specification
📝 (README.md): add link to the GitMoji specification for reference

* 🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag
🔧 (api.ts): update the default value of the `apiKey` variable to a placeholder value for testing purposes

* Revert "🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag"

This reverts commit 230a4aa449.

* 🔧 (README.md): fix a typo in the description of the `Use Full GitMoji Specification` flag

* 📝 (prompts.ts): update INIT_MAIN_PROMPT content to include information about the fullGitMojiSpec flag and provide instructions on how to choose the right emoji for the commit message

---------

Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
2024-03-03 19:36:55 +08:00
di-sukharev
0b5adf104a build 2024-03-03 19:36:23 +08:00
Jack Lukic
ec699c48bf Bug (commitling) Parse JSON blocks from return response (#297) 2024-03-03 19:23:44 +08:00
mattsalt123
c9b45492a5 feat: Add Input Token Limit Config Option (#281) 2024-02-29 23:06:23 +08:00
di-sukharev
b0b90679a4 build 2024-02-28 19:29:20 +08:00
di-sukharev
02cef105a6 3.0.9 2024-02-28 19:29:17 +08:00
di-sukharev
407ca4b244 build 2024-02-28 19:19:42 +08:00
di-sukharev
62e44e5e35 3.0.8 2024-02-28 19:19:41 +08:00
di-sukharev
03fce6f5cf build 2024-02-28 19:19:11 +08:00
di-sukharev
6155fca4b1 3.0.7 2024-02-28 19:19:09 +08:00
di-sukharev
22d4af48c7 build 2024-02-28 19:19:04 +08:00
di-sukharev
17d5a7143f Merge branch 'dev' 2024-02-28 19:18:11 +08:00
Isaac Johnston
011db5ad5e feat: add instructions and support for configuring GPT-4 Turbo (Preview) (#274)
* docs(README.md): add instructions for configuring GPT-4 Turbo (Preview)
feat(config.ts): include 'gpt-4-1106-preview' in the list of supported models

* fix(config.ts): update error message to include 'gpt-4-1106-preview' as a supported model
2024-02-28 19:17:10 +08:00
di-sukharev
89682f0397 remove stale issue action 2024-02-28 14:01:07 +08:00
di-sukharev
9ed9174b6f build 2024-02-27 22:17:08 +08:00
di-sukharev
53ae8926fa 3.0.6 2024-02-27 22:17:06 +08:00
di-sukharev
6c743ba230 chore(README.md): update link to GitHub 2023 hackathon in README
The link to the GitHub 2023 hackathon in the README has been updated to point to the correct URL. This ensures that users can easily access the relevant information about the hackathon.
2024-02-27 22:16:23 +08:00
di-sukharev
9852c36a98 build 2024-02-27 22:07:18 +08:00
di-sukharev
5f85cafc7e 3.0.5 2024-02-27 22:07:16 +08:00
di-sukharev
0591e6e81e docs(README.md): add alternative ways to run OpenCommit via npx and bunx
The README.md file has been updated to include alternative ways to run OpenCommit via the `npx opencommit` and `bunx opencommit` commands. This provides users with more options to execute the tool without the need for a global installation.
2024-02-27 22:06:08 +08:00
di-sukharev
a296892aaf build 2024-02-27 22:04:39 +08:00
di-sukharev
45958284c9 Merge branch 'master' into dev 2024-02-27 22:04:13 +08:00
Jaroslaw Weber
1d6980faf3 add ollama support (#269)
* add ollama support

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
Co-authored-by: GPT10 <57486732+di-sukharev@users.noreply.github.com>
Co-authored-by: Jaroslaw Weber <jaroslaw.weber@adriel.com>
2024-02-27 22:01:13 +08:00
di-sukharev
f793f01059 Merge branch 'master' of github.com:di-sukharev/opencommit 2023-11-27 12:14:13 +08:00
di-sukharev
7deffa8ee2 update twitter 2023-11-27 12:00:28 +08:00
GPT10
84dfc85328 Update README.md
fix(README.md): GitHub hackathon link
2023-10-05 22:05:45 +08:00
di-sukharev
b79aef5fad build 2023-09-18 14:23:43 +08:00
di-sukharev
57d9cc59b5 fix(config.ts): add default value for config parameter in OCO_OPENAI_API_KEY validator function
The OCO_OPENAI_API_KEY validator function now accepts an optional config parameter with a default value of an empty object {}. This change ensures that the function can be called without providing the config parameter, improving flexibility and avoiding potential errors when the parameter is not provided.
2023-09-18 14:21:55 +08:00
di-sukharev
e599700d72 build 2023-09-09 15:52:29 +08:00
di-sukharev
2761403735 Merge remote-tracking branch 'origin/master' into dev 2023-09-09 15:52:13 +08:00
di-sukharev
e57033c4a1 chore(README.md): update information about default model and cost
The default model used by OpenCommit is ChatGPT (3.5-turbo-16k) official model. It is mentioned that the cost should not exceed $0.10 per casual working day. Additionally, it is now mentioned that users have the option to switch to gpt-4, which is better but more expensive. This update provides users with more information about the available models and their associated costs.
2023-09-05 20:30:39 +08:00
di-sukharev
ca049e4b5d chore(package.json): update deploy script to push tags before publishing to npm
The deploy script in package.json has been updated to push tags before publishing to npm. This ensures that the latest version tag is pushed to the remote repository before publishing the package.
2023-09-05 13:57:20 +08:00
di-sukharev
2d48648f52 chore(package.json): update version to 3.0.3
The version in package.json has been updated from 3.0.2 to 3.0.3. This change is made to reflect the latest version of the project.
2023-09-05 13:51:21 +08:00
di-sukharev
40297e0c6a chore(package.json): update version to 3.0.2
The version in package.json has been updated from 3.0.1 to 3.0.2. This change is made to reflect the latest version of the project.
2023-09-05 13:49:17 +08:00
di-sukharev
75f0cd47b8 chore(package.json): update version to 3.0.1
The version in package.json has been updated from 3.0.0 to 3.0.1. This change is made to reflect the new version of the project.
2023-09-05 13:48:09 +08:00
Sukharev
c76313737d #247: fix (#248) 2023-09-05 13:45:34 +08:00
di-sukharev
a2b1890e7e build 2023-09-05 13:42:30 +08:00
di-sukharev
df705b97b7 Merge remote-tracking branch 'origin/master' into dev 2023-09-05 13:42:22 +08:00
di-sukharev
df280b7db7 fix(commitlint): use process.cwd() as fallback for process.env.PWD to ensure compatibility across platforms
The commitlint module now uses process.cwd() as a fallback when process.env.PWD is not available. This ensures compatibility across different platforms and environments when determining the path to the node_modules directory.
2023-09-05 13:39:49 +08:00
Sukharev
67dff60a7d chore(prompts.ts): update INIT_MAIN_PROMPT content to provide clearer instructions and improve readability (#245) 2023-09-03 15:39:52 +08:00
di-sukharev
08fb4d801f chore: add .npmignore file and update package.json to include specific files in the "files" field
The .npmignore file is added to the project. It currently includes the file "out/github-action.cjs" to be ignored when publishing the package to npm.

In the package.json file, the "files" field is updated to include the files "out/cli.cjs" and "out/tiktoken_bg.wasm". This ensures that these files are included when publishing the package to npm.

The "deploy" script in the package.json file is also updated to include pushing tags to the remote repository using "git push --tags". This ensures that the newly created version tags are pushed along with the code changes.
2023-09-03 15:29:29 +08:00
di-sukharev
ac8c87be9e docs(README.md): add note about MacOS potentially requiring 'sudo' when installing package globally
The README.md file now includes a note that MacOS may prompt the user to run the 'npm install -g opencommit' command with 'sudo' when installing the package globally. This note is added to provide clarity and help users who encounter this situation.
2023-09-03 15:22:47 +08:00
di-sukharev
a9050fda39 build 2023-09-03 15:17:12 +08:00
Sukharev
b98b892ba1 v3.0.0 (#243)
* update major version

---------

Co-authored-by: E Einowski <5124085+EwiththeBowtie@users.noreply.github.com>
Co-authored-by: Max Zavodniuk <lovemaxonly@gmail.com>
Co-authored-by: Malthe Poulsen <30603252+malpou@users.noreply.github.com>
Co-authored-by: Sébastien Fichot <fichot.sebastien@gmail.com>
2023-09-03 15:16:02 +08:00
di-sukharev
0460a252e2 update major version 2023-09-03 15:11:14 +08:00
di-sukharev
ebeb68fd9b chore(package.json): update deploy script to use 'git push --follow-tags' instead of 'git push --tags'
The deploy script in package.json has been updated to use 'git push --follow-tags' instead of 'git push --tags'. This change ensures that tags are pushed along with the commit when deploying the application.
2023-09-03 15:07:43 +08:00
di-sukharev
a9f550fb79 Merge remote-tracking branch 'origin/master' into dev 2023-09-03 15:04:09 +08:00
Sukharev
4181c0b20d Refactoring and cleaning for v3 (#244)
* chore(README.md): update setup instructions for OpenCommit as a GitHub Action
fix(commit.ts): check if OCO_MESSAGE_TEMPLATE_PLACEHOLDER exists before replacing it in the commit message template
The README.md file has been updated to reflect the changes in the setup instructions for using OpenCommit as a GitHub Action. The new instructions provide a file template for the GitHub Action workflow and include the latest version of the OpenCommit GitHub Action.

In the commit.ts file, a fix has been made to check if the OCO_MESSAGE_TEMPLATE_PLACEHOLDER exists before replacing it in the commit message template. This ensures that the replacement is only performed when the placeholder is present, preventing any potential errors.
2023-09-03 14:59:37 +08:00
di-sukharev
765e9884dd chore(package.json): reorder scripts to improve readability and maintainability 2023-09-03 14:10:56 +08:00
di-sukharev
ea9411fa69 build 2023-09-03 14:09:45 +08:00
Sébastien Fichot
12956d7633 feat: Integrate @commitlint for Enhanced Commit Message Generation and Configuration Support (#209)
* add commitlint support
* refactor code
* improve readme text
2023-09-03 14:00:18 +08:00
Malthe Poulsen
c1627bb98c 🔧 chore(github-action.ts): add check for changes in commit messages before rebasing to improve efficiency (#222)
🐛 fix(github-action.ts): fix logic to check if there are any changes in commit messages before rebasing to ensure correct behavior
2023-09-03 12:22:05 +08:00
Max Zavodniuk
4e374aa9db fix(config.ts): pass config object to configValidators[CONFIG_KEYS.OCO_OPENAI_API_KEY] to allow access to other config values (#225)
* any length `OCO_OPENAI_API_KEY` if `OCO_OPENAI_BASE_PATH` is set
2023-09-03 12:21:13 +08:00
E Einowski
ef003bdad6 Add Detailed Explanation of OCO_MESSAGE_TEMPLATE_PLACEHOLDER Feature to README.md (#242)
* 📝 docs(README.md): add detailed explanation of OCO_MESSAGE_TEMPLATE_PLACEHOLDER feature
2023-09-03 12:18:10 +08:00
Sukharev
ba5dd848b8 Update README.md 2023-08-24 12:00:43 +08:00
di-sukharev
916ddf02d1 2.4.2 2023-07-05 15:20:11 +08:00
di-sukharev
fa85ec0351 build 2023-07-05 15:20:03 +08:00
di-sukharev
3a7f2d69e7 2.4.1 2023-07-05 15:19:29 +08:00
di-sukharev
f240d35231 build 2023-07-05 15:19:20 +08:00
di-sukharev
04ee73ca9d 2.4.0 2023-07-05 15:18:19 +08:00
di-sukharev
91885cdd1c 2.3.0 2023-07-05 15:18:02 +08:00
di-sukharev
b7a2cd46b3 build 2023-07-05 15:13:58 +08:00
di-sukharev
028c0bc518 format files 2023-07-05 15:13:30 +08:00
di-sukharev
897eb73cd7 feat(README.md): add GitHub Hackathon winner badge to project description
docs(README.md): restructure README, move CLI setup instructions to top for better visibility
feat(cli.cjs): extend file exclusion list in 'getDiff' to include image files for better performance
refactor(config.ts): add more model options in configValidators for flexibility
refactor(config.ts): set default model to 'gpt-3.5-turbo-16k' for better performance
refactor(git.ts): simplify 'getCoreHooksPath' function for readability
feat(git.ts): extend file exclusion list in 'getDiff' to include image files for better performance
2023-07-05 15:11:17 +08:00
di-sukharev
e7ce40a8d1 build 2023-07-05 14:57:03 +08:00
di-sukharev
18d25672d7 docs(CONTRIBUTING.md): clarify pull request target branch in contribution guidelines
This change emphasizes that pull requests should be made to the `dev` branch, not the `master` branch, to ensure proper workflow.
2023-07-05 14:52:09 +08:00
di-sukharev
79b1008e02 Merge branch 'dev' 2023-07-05 14:50:19 +08:00
seho
3c0a271bf8 feat(config.ts): add OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item to allow users to customize the message template placeholder (#208)
feat(commit.ts): add check for message templates in extraArgs and replace OCO_MESSAGE_TEMPLATE_PLACEHOLDER with generated commit message if found
docs(README.md): add documentation for OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item and how to use it in the command line (#205)
2023-07-05 14:27:43 +08:00
Sam Rudloff
ccfd24a9e5 Adding gpt-3.5-turbo-16k Modal (#215) 2023-07-05 14:25:21 +08:00
Avighna
0c8bf5562d Fixed lots of changes (#195) 2023-06-19 18:34:58 +08:00
Sukharev
96c7676a13 Revert "Fixing grammatical errors and enhancing overall grammar in README.md (#190)" (#210)
This reverts commit 90f64d5475.
2023-06-19 18:33:39 +08:00
Avighna
90f64d5475 Fixing grammatical errors and enhancing overall grammar in README.md (#190)
* Fixed grammatical errors and enhanced overall grammar in README.md
2023-06-19 18:32:12 +08:00
di-sukharev
a70b831f9d build 2023-05-26 13:14:47 +08:00
di-sukharev
cad179953a 2.2.10 2023-05-26 13:14:42 +08:00
di-sukharev
8979841010 refactor(api.ts): reformat imports to be grouped and sorted alphabetically
refactor(api.ts): replace axios with execa to get the latest version of opencommit from npm registry
2023-05-26 13:14:33 +08:00
di-sukharev
1e974086d3 build 2023-05-26 13:12:07 +08:00
di-sukharev
792ab67ef1 2.2.9 2023-05-26 13:12:00 +08:00
di-sukharev
a7af55df37 feat(checkIsLatestVersion.ts): add outro message from @clack/prompts to warn user about not using the latest stable version of OpenCommit 2023-05-26 13:11:51 +08:00
di-sukharev
5c540abae9 build 2023-05-26 13:10:41 +08:00
di-sukharev
f69e716dcc 2.2.8 2023-05-26 13:10:29 +08:00
di-sukharev
1d8d8e57c2 style(.prettierrc): reorder properties to follow alphabetical order 2023-05-26 13:09:53 +08:00
di-sukharev
fdc638cd86 build 2023-05-26 13:08:19 +08:00
di-sukharev
56e02f2604 2.2.7 2023-05-26 13:08:08 +08:00
di-sukharev
b926a627a8 Merge remote-tracking branch 'origin/dev' 2023-05-26 13:07:42 +08:00
Gabriel Moreli
32f3e176f0 feat(api.ts): solving bad request issue (#187)
* 2.0.18

* patch

* 2.0.19

* style(.prettierrc): reorder properties to follow alphabetical order and improve readability

* feat(generateCommitMessageFromGitDiff.ts): changing logic of MAX_REQ_TOKENS

fix(api.ts): add missing import for GenerateCommitMessageErrorEnum
The token count validation is added to prevent the request from exceeding the default model token limit. The validation is done by counting the tokens in each message and adding 4 to each count to account for the additional tokens added by the API. If the total token count exceeds the limit, an error is thrown. The missing import for GenerateCommitMessageErrorEnum is also added.

feat: add support for splitting long line-diffs into smaller pieces
This change adds support for splitting long line-diffs into smaller pieces to avoid exceeding the maximum commit message length. The `splitDiff` function splits a single line into multiple lines if it exceeds the maximum length. It also splits the diff into smaller pieces if adding the next line would exceed the maximum length. This change improves the readability of commit messages and makes them more consistent.

refactor: improve code readability by adding whitespace and reformatting code
This commit improves the readability of the code by adding whitespace and reformatting the code. The changes do not affect the functionality of the code. Additionally, a new function `delay` has been added to the file.

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2023-05-26 13:07:09 +08:00
di-sukharev
cf4212016f build 2023-05-26 12:57:44 +08:00
di-sukharev
c491fa4bad 2.2.6 2023-05-26 12:57:38 +08:00
di-sukharev
f10fc37fe7 refactor(CommandsEnum.ts): reorder COMMANDS enum values to improve readability and maintainability 2023-05-26 12:57:20 +08:00
di-sukharev
f7b1a6358f build 2023-05-26 12:55:27 +08:00
di-sukharev
5f9f29c467 2.2.5 2023-05-26 12:55:14 +08:00
di-sukharev
0ec5dab80a 2.2.4 2023-05-26 12:54:52 +08:00
di-sukharev
8d47a1bb0f chore(package.json): revert version to 2.2.3 and remove postversion script as it is no longer needed
feat(package.json): add deploy script to automate versioning, building, pushing and publishing the package to npm registry
2023-05-26 12:54:42 +08:00
di-sukharev
fad05e0757 2.2.4 2023-05-26 12:52:00 +08:00
di-sukharev
12f7e7eaf9 build 2023-05-26 12:51:55 +08:00
di-sukharev
6490532818 2.2.3 2023-05-26 12:43:53 +08:00
di-sukharev
e0e953dab8 build 2023-05-26 12:43:49 +08:00
di-sukharev
75fa04efd4 2.2.2 2023-05-26 12:42:47 +08:00
di-sukharev
93019139fb build 2023-05-26 12:42:43 +08:00
di-sukharev
ce47c1fe81 2.2.1 2023-05-26 12:37:20 +08:00
di-sukharev
a880dd6bd2 build 2023-05-26 12:37:16 +08:00
di-sukharev
f193bb1d96 chore(package.json): update version from 2.1.4 to 2.2.0 to reflect new features and improvements 2023-05-26 12:35:11 +08:00
di-sukharev
4cc34dfa5f chore(package.json): update version from 2.1.3 to 2.1.4 to reflect recent changes and improvements 2023-05-26 12:33:18 +08:00
di-sukharev
d034238505 chore(package.json): update version from 2.1.2 to 2.1.3 to reflect recent changes and improvements 2023-05-26 12:27:48 +08:00
di-sukharev
25acb1c219 chore(package.json): update version from 2.1.1 to 2.1.2 to reflect recent changes and improvements 2023-05-26 12:27:28 +08:00
di-sukharev
44c9d48eae 2.1.1 2023-05-26 12:22:15 +08:00
di-sukharev
702eab9ec0 build 2023-05-26 12:22:11 +08:00
di-sukharev
e8be3858ab update v to 2.1.0 2023-05-26 12:22:07 +08:00
di-sukharev
50e7cd3576 chore(package.json): update package version from 2.0.19 to 2.1.0
refactor(cli.ts): uncomment checkIsLatestVersion function call to ensure the latest version of the package is being used
2023-05-26 12:16:56 +08:00
di-sukharev
4f57201e98 build 2023-05-26 12:09:25 +08:00
di-sukharev
009462f92c Merge remote-tracking branch 'origin/master' into dev 2023-05-26 12:06:00 +08:00
Moulick Aggarwal
34775e9e69 Remove oc alias (#178)
oc is pretty wellknow alias for OpenShift CLI

Signed-off-by: Moulick Aggarwal <moulickaggarwal@gmail.com>
2023-05-26 12:05:18 +08:00
Hiro
3f3043c48e doc: add timeout-minutes to workflow (#186)
* 2.0.18

* patch

* 2.0.19

* style(.prettierrc): reorder properties to follow alphabetical order and improve readability

* doc: add timeout-minutes to workflow

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2023-05-26 11:55:31 +08:00
di-sukharev
820422760a style(.prettierrc): reorder properties to follow alphabetical order and improve readability 2023-05-24 13:42:42 +08:00
di-sukharev
b40657c1a7 2.0.19 2023-05-22 12:32:23 +08:00
di-sukharev
dcd8f52be3 patch 2023-05-22 12:32:12 +08:00
di-sukharev
0eff198eb9 2.0.18 2023-05-22 12:31:43 +08:00
di-sukharev
b345eee815 build 2023-05-22 12:31:19 +08:00
henrycunh
cba599337d refactor(config.ts): handle undefined values and improve validation for OCO_OPENAI_MAX_TOKENS and OCO_MODEL (#176)
fix(config.ts): set default values for OCO_MODEL and OCO_LANGUAGE when reading from environment variables
refactor(generateCommitMessageFromGitDiff.ts): remove unused GenerateCommitMessageError interface
2023-05-22 12:30:41 +08:00
di-sukharev
4e25f1460a chore(README.md): update opencommit action version to v1.0.4 to fix bugs and improve functionality 2023-05-22 12:27:58 +08:00
di-sukharev
61b145455d Merge remote-tracking branch 'origin/master' into dev 2023-05-22 12:26:59 +08:00
di-sukharev
0b8dc12047 chore: update .eslintrc.json to disable import/order rule and enable simple-import-sort rules
chore: update .prettierignore to include /out directory
docs: update LICENSE file to include link to author's GitHub profile
chore: update tsconfig.json to move resolveJsonModule above moduleResolution for consistency
2023-05-22 12:22:37 +08:00
di-sukharev
1a1134a010 docs(README.md): remove outdated setup instructions for OpenCommit GitHub Action and update action name 2023-05-21 17:11:59 +08:00
di-sukharev
d40e2ca4af feat(package.json): add build:push script to build and push changes to remote repository in one command 2023-05-21 16:55:11 +08:00
di-sukharev
540b4b3bf1 Merge branch 'master' of github.com:di-sukharev/opencommit 2023-05-21 16:54:09 +08:00
di-sukharev
0429b92120 chore(README.md): update title to better reflect the purpose of the project
docs(README.md): update GitHub Action setup instructions with correct link to the Marketplace and version number of the action used
2023-05-21 16:53:47 +08:00
Sukharev
c6036f0570 Update action.yml 2023-05-21 16:46:16 +08:00
Sukharev
3eb319a919 GitHub action (#173)
* add option to run OpenCommit as a Github Action
2023-05-21 16:15:45 +08:00
107 changed files with 141147 additions and 21127 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
.env

View File

@@ -21,8 +21,8 @@
"rules": {
"prettier/prettier": "error",
"no-console": "error",
"sort-imports": "off",
"import/order": "off",
"sort-imports": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"import/first": "error",

View File

@@ -9,7 +9,7 @@ Thanks for considering contributing to the project.
3. Create a new branch for your changes.
4. Make your changes and commit them with descriptive commit messages.
5. Push your changes to your forked repository.
6. Create a pull request from your branch to the `dev` branch.
6. Create a pull request from your branch to the `dev` branch. Not `master` branch, PR to `dev` branch, please.
## Getting started
@@ -18,7 +18,7 @@ To get started, follow these steps:
1. Clone the project repository locally.
2. Install dependencies with `npm install`.
3. Run the project with `npm run dev`.
4. See [issues](https://github.com/di-sukharev/open-commit/issues) or [TODO.md](../TODO.md) to help the project.
4. See [issues](https://github.com/di-sukharev/opencommit/issues) or [TODO.md](TODO.md) to help the project.
## Commit message guidelines
@@ -30,7 +30,7 @@ If you encounter any issues while using the project, please report them on the G
## Contacts
If you have any questions about contributing to the project, please contact by [creating an issue](https://github.com/di-sukharev/open-commit/issues) on the GitHub issue tracker.
If you have any questions about contributing to the project, please contact by [creating an issue](https://github.com/di-sukharev/opencommit/issues) on the GitHub issue tracker.
## License

BIN
.github/github-mark-white.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -40,11 +40,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -71,6 +71,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v2
uses: actions/dependency-review-action@v3

View File

@@ -1,28 +0,0 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '27 21 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 40
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

53
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Testing
on:
pull_request:
push:
branches:
- master
- main
jobs:
unit-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Run Unit Tests
run: npm run test:unit
e2e-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install git
run: |
sudo apt-get update
sudo apt-get install -y git
git --version
- name: Setup git
run: |
git config --global user.email "test@example.com"
git config --global user.name "Test User"
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Run E2E Tests
run: npm run test:e2e

4
.gitignore vendored
View File

@@ -10,4 +10,6 @@ uncaughtExceptions.log
.vscode
src/*.json
.idea
test.ts
test.ts
notes.md
.nvmrc

1
.npmignore Normal file
View File

@@ -0,0 +1 @@
out/github-action.cjs

1
.opencommitignore Normal file
View File

@@ -0,0 +1 @@
out

View File

@@ -1,2 +1,3 @@
/build
/dist
/dist
/out

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) Dima Sukharev
Copyright (c) Dima Sukharev, https://github.com/di-sukharev
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

411
README.md
View File

@@ -2,12 +2,12 @@
<div>
<img src=".github/logo-grad.svg" alt="OpenCommit logo"/>
<h1 align="center">OpenCommit</h1>
<h4 align="center">Follow the bird <a href="https://twitter.com/io_Y_oi"><img src="https://img.shields.io/twitter/follow/io_Y_oi?style=flat&label=io_Y_oi&logo=twitter&color=0bf&logoColor=fff" align="center"></a>
</h4>
<h4 align="center">Author <a href="https://twitter.com/_sukharev_"><img src="https://img.shields.io/twitter/follow/_sukharev_?style=flat&label=_sukharev_&logo=twitter&color=0bf&logoColor=fff" align="center"></a>
</div>
<h2>GPT CLI to auto-generate impressive commits in 1 second</h2>
<h2>Auto-generate meaningful commits in a second</h2>
<p>Killing lame commits with AI 🤯🔫</p>
<a href="https://www.npmjs.com/package/opencommit"><img src="https://img.shields.io/npm/v/opencommit" alt="Current version"></a>
<h4 align="center">🪩 Winner of <a href="https://twitter.com/_sukharev_/status/1683448136973582336">GitHub 2023 hackathon</a> 🪩</h4>
</div>
---
@@ -16,72 +16,11 @@
<img src=".github/opencommit-example.png" alt="OpenCommit example"/>
</div>
All the commits in this repo are done with OpenCommit — look into [the commits](https://github.com/di-sukharev/opencommit/commit/eae7618d575ee8d2e9fff5de56da79d40c4bc5fc) to see how OpenCommit works. Emoji and long commit description text is configurable.
All the commits in this repo are authored by OpenCommit — look at [the commits](https://github.com/di-sukharev/opencommit/commit/eae7618d575ee8d2e9fff5de56da79d40c4bc5fc) to see how OpenCommit works. Emojis and long commit descriptions are configurable, basically everything is.
## Setup OpenCommit as a Github Action
## Setup OpenCommit as a CLI tool
OpenCommit is now available as a GitHub Action which automatically improves all new commits messages when you push to remote!
This is great if you want to make sure all of the commits in all of repository branches are meaningful and not lame like `fix1` or `done2`.
### Automatic 1 click setup
You can simply [setup the action automatically via the GitHub Marketplace](TODO).
### Manual 3 clicks setup
Create a file `.github/workflows/opencommit.yml` with contents below:
```yml
name: 'OpenCommit'
on:
push:
# this list of branches is often enough,
# but you may still ignore other public branches
branches-ignore: [main master dev development release]
jobs:
opencommit:
name: OpenCommit
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Setup Node.js Environment
uses: actions/setup-node@v2
with:
node-version: '16'
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: di-sukharev/opencommit@github-action
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
env:
# set openAI api key in repo actions secrets,
# for openAI keys go to: https://platform.openai.com/account/api-keys
# for repo secret go to: <your_repo_url>/settings/secrets/actions
OCO_OPENAI_API_KEY: ${{ secrets.OCO_OPENAI_API_KEY }}
# customization
OCO_OPENAI_MAX_TOKENS: 500
OCO_OPENAI_BASE_PATH: ''
OCO_DESCRIPTION: false
OCO_EMOJI: false
OCO_MODEL: gpt-3.5-turbo
OCO_LANGUAGE: en
```
That is it. Now when you push to any branch in your repo — all NEW commits are being improved by never-tired-AI.
Make sure you exclude public collaboration branches (`main`, `dev`, `etc`) in `branches-ignore`, so OpenCommit does not rebase commits there when improving the messages.
Interactive rebase (`rebase -i`) changes commit SHA, so commit history in remote becomes different with your local branch history. It's ok when you work on the branch alone, but may be inconvenient for other collaborators.
## Setup OpenCommit as a CLI
You can use OpenCommit by simply running it via CLI like this `oc`. 2 seconds and your staged changes are committed with a meaningful message.
You can use OpenCommit by simply running it via the CLI like this `oco`. 2 seconds and your staged changes are committed with a meaningful message.
1. Install OpenCommit globally to use in any repository:
@@ -89,107 +28,260 @@ You can use OpenCommit by simply running it via CLI like this `oc`. 2 seconds an
npm install -g opencommit
```
2. Get your API key from [OpenAI](https://platform.openai.com/account/api-keys). Make sure you add payment details, so API works.
2. Get your API key from [OpenAI](https://platform.openai.com/account/api-keys) or other supported LLM providers (we support them all). Make sure that you add your OpenAI payment details to your account, so the API works.
3. Set the key to OpenCommit config:
```sh
opencommit config set OCO_OPENAI_API_KEY=<your_api_key>
oco config set OCO_API_KEY=<your_api_key>
```
Your api key is stored locally in `~/.opencommit` config file.
Your API key is stored locally in the `~/.opencommit` config file.
## Usage
You can call OpenCommit directly to generate a commit message for your staged changes:
You can call OpenCommit with `oco` command to generate a commit message for your staged changes:
```sh
git add <files...>
opencommit
oco
```
You can also use the `oc` shortcut:
Running `git add` is optional, `oco` will do it for you.
### Running locally with Ollama
You can also run it with local model through ollama:
- install and start ollama
- run `ollama run mistral` (do this only once, to pull model)
- run (in your project directory):
```sh
git add <files...>
oc
oco config set OCO_AI_PROVIDER='ollama' OCO_MODEL='llama3:8b'
```
Default model is `mistral`.
If you have ollama that is set up in docker/ on another machine with GPUs (not locally), you can change the default endpoint url.
You can do so by setting the `OCO_API_URL` environment variable as follows:
```sh
oco config set OCO_API_URL='http://192.168.1.10:11434/api/chat'
```
where 192.168.1.10 is example of endpoint URL, where you have ollama set up.
### Flags
There are multiple optional flags that can be used with the `oco` command:
#### Use Full GitMoji Specification
Link to the GitMoji specification: https://gitmoji.dev/
This flag can only be used if the `OCO_EMOJI` configuration item is set to `true`. This flag allows users to use all emojis in the GitMoji specification, By default, the GitMoji full specification is set to `false`, which only includes 10 emojis (🐛✨📝🚀✅♻️⬆️🔧🌐💡).
This is due to limit the number of tokens sent in each request. However, if you would like to use the full GitMoji specification, you can use the `--fgm` flag.
```
oco --fgm
```
#### Skip Commit Confirmation
This flag allows users to automatically commit the changes without having to manually confirm the commit message. This is useful for users who want to streamline the commit process and avoid additional steps. To use this flag, you can run the following command:
```
oco --yes
```
## Configuration
### Local per repo configuration
Create an `.env` file and add OpenCommit config variables there like this:
Create a `.env` file and add OpenCommit config variables there like this:
```env
OCO_OPENAI_API_KEY=<your openAI API token>
OCO_OPENAI_MAX_TOKENS=<max response tokens from openAI API>
OCO_OPENAI_BASE_PATH=<may be used to set proxy path to openAI api>
OCO_DESCRIPTION=<postface a message with ~3 sentences description>
OCO_EMOJI=<add GitMoji>
OCO_MODEL=<either gpt-3.5-turbo or gpt-4>
...
OCO_AI_PROVIDER=<openai (default), anthropic, azure, ollama, gemini, flowise, deepseek>
OCO_API_KEY=<your OpenAI API token> // or other LLM provider API token
OCO_API_URL=<may be used to set proxy path to OpenAI api>
OCO_API_CUSTOM_HEADERS=<JSON string of custom HTTP headers to include in API requests>
OCO_TOKENS_MAX_INPUT=<max model token limit (default: 4096)>
OCO_TOKENS_MAX_OUTPUT=<max response tokens (default: 500)>
OCO_DESCRIPTION=<postface a message with ~3 sentences description of the changes>
OCO_EMOJI=<boolean, add GitMoji>
OCO_MODEL=<either 'gpt-4o-mini' (default), 'gpt-4o', 'gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-4-1106-preview', 'gpt-4-turbo-preview' or 'gpt-4-0125-preview' or any Anthropic or Ollama model or any string basically, but it should be a valid model name>
OCO_LANGUAGE=<locale, scroll to the bottom to see options>
OCO_MESSAGE_TEMPLATE_PLACEHOLDER=<message template placeholder, default: '$msg'>
OCO_PROMPT_MODULE=<either conventional-commit or @commitlint, default: conventional-commit>
OCO_ONE_LINE_COMMIT=<one line commit message, default: false>
```
Global configs are same as local configs, but they are stored in the global `~/.opencommit` config file and set with `oco config set` command, e.g. `oco config set OCO_MODEL=gpt-4o`.
### Global config for all repos
Local config still has more priority as Global config, but you may set `OCO_MODEL` and `OCO_LOCALE` globally and set local configs for `OCO_EMOJI` and `OCO_DESCRIPTION` per repo which is more convenient.
Local config still has more priority than Global config, but you may set `OCO_MODEL` and `OCO_LOCALE` globally and set local configs for `OCO_EMOJI` and `OCO_DESCRIPTION` per repo which is more convenient.
Simply run any of the variable above like this:
Simply set any of the variables above like this:
```sh
oc config set OCO_OPENAI_API_KEY=gpt-4
oco config set OCO_MODEL=gpt-4o-mini
```
To see all available configuration parameters and their accepted values:
```sh
oco config describe
```
To see details for a specific parameter:
```sh
oco config describe OCO_MODEL
```
Configure [GitMoji](https://gitmoji.dev/) to preface a message.
```sh
oc config set OCO_EMOJI=true
oco config set OCO_EMOJI=true
```
To remove preface emoji:
To remove preface emojis:
```sh
oc config set OCO_EMOJI=false
oco config set OCO_EMOJI=false
```
### Switch to GPT-4
Other config options are behaving the same.
By default OpenCommit uses GPT-3.5-turbo (ChatGPT).
### Output WHY the changes were done (WIP)
You may switch to GPT-4 which performs better, but costs ~x15 times more 🤠
You can set the `OCO_WHY` config to `true` to have OpenCommit output a short description of WHY the changes were done after the commit message. Default is `false`.
To make this perform accurate we must store 'what files do' in some kind of an index or embedding and perform a lookup (kinda RAG) for the accurate git commit message. If you feel like building this comment on this ticket https://github.com/di-sukharev/opencommit/issues/398 and let's go from there together.
```sh
oc config set OCO_MODEL=gpt-4
oco config set OCO_WHY=true
```
Make sure you do lowercase `gpt-4` and you have API access to the 4th model. Even if you have ChatGPT+ it doesn't necessarily mean that you have API access to GPT-4.
### Switch to GPT-4 or other models
## Locale configuration
By default, OpenCommit uses `gpt-4o-mini` model.
You may switch to gpt-4o which performs better, but costs more 🤠
```sh
oco config set OCO_MODEL=gpt-4o
```
or for as a cheaper option:
```sh
oco config set OCO_MODEL=gpt-3.5-turbo
```
### Switch to other LLM providers with a custom URL
By default OpenCommit uses [OpenAI](https://openai.com).
You could switch to [Azure OpenAI Service](https://learn.microsoft.com/azure/cognitive-services/openai/) or Flowise or Ollama.
```sh
oco config set OCO_AI_PROVIDER=azure OCO_API_KEY=<your_azure_api_key> OCO_API_URL=<your_azure_endpoint>
oco config set OCO_AI_PROVIDER=flowise OCO_API_KEY=<your_flowise_api_key> OCO_API_URL=<your_flowise_endpoint>
oco config set OCO_AI_PROVIDER=ollama OCO_API_KEY=<your_ollama_api_key> OCO_API_URL=<your_ollama_endpoint>
```
### Locale configuration
To globally specify the language used to generate commit messages:
```sh
# de, German ,Deutsch
oc config set OCO_LANGUAGE=de
oc config set OCO_LANGUAGE=German
oc config set OCO_LANGUAGE=Deutsch
# de, German, Deutsch
oco config set OCO_LANGUAGE=de
oco config set OCO_LANGUAGE=German
oco config set OCO_LANGUAGE=Deutsch
# fr, French, française
oc config set OCO_LANGUAGE=fr
oc config set OCO_LANGUAGE=French
oc config set OCO_LANGUAGE=française
oco config set OCO_LANGUAGE=fr
oco config set OCO_LANGUAGE=French
oco config set OCO_LANGUAGE=française
```
The default language set is **English**
The default language setting is **English**
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder
### Git flags
### Push to git (gonna be deprecated)
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`.
A prompt for pushing to git is on by default but if you would like to turn it off just use:
```sh
oc --no-verify
oco config set OCO_GITPUSH=false
```
and it will exit right after commit is confirmed without asking if you would like to push to remote.
### Switch to `@commitlint`
OpenCommit allows you to choose the prompt module used to generate commit messages. By default, OpenCommit uses its conventional-commit message generator. However, you can switch to using the `@commitlint` prompt module if you prefer. This option lets you generate commit messages in respect with the local config.
You can set this option by running the following command:
```sh
oco config set OCO_PROMPT_MODULE=<module>
```
Replace `<module>` with either `conventional-commit` or `@commitlint`.
#### Example:
To switch to using the `'@commitlint` prompt module, run:
```sh
oco config set OCO_PROMPT_MODULE=@commitlint
```
To switch back to the default conventional-commit message generator, run:
```sh
oco config set OCO_PROMPT_MODULE=conventional-commit
```
#### Integrating with `@commitlint`
The integration between `@commitlint` and OpenCommit is done automatically the first time OpenCommit is run with `OCO_PROMPT_MODULE` set to `@commitlint`. However, if you need to force set or reset the configuration for `@commitlint`, you can run the following command:
```sh
oco commitlint force
```
To view the generated configuration for `@commitlint`, you can use this command:
```sh
oco commitlint get
```
This allows you to ensure that the configuration is set up as desired.
Additionally, the integration creates a file named `.opencommit-commitlint` which contains the prompts used for the local `@commitlint` configuration. You can modify this file to fine-tune the example commit message generated by OpenAI. This gives you the flexibility to make adjustments based on your preferences or project guidelines.
OpenCommit generates a file named `.opencommit-commitlint` in your project directory which contains the prompts used for the local `@commitlint` configuration. You can modify this file to fine-tune the example commit message generated by OpenAI. If the local `@commitlint` configuration changes, this file will be updated the next time OpenCommit is run.
This offers you greater control over the generated commit messages, allowing for customization that aligns with your project's conventions.
## Git flags
The `opencommit` or `oco` 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 `oco`.
```sh
oco --no-verify
```
is translated to :
@@ -198,33 +290,68 @@ is translated to :
git commit -m "${generatedMessage}" --no-verify
```
To include a message in the generated message, you can utilize the template function, for instance:
```sh
oco '#205: $msg
```
> opencommit examines placeholders in the parameters, allowing you to append additional information before and after the placeholders, such as the relevant Issue or Pull Request. Similarly, you have the option to customize the OCO_MESSAGE_TEMPLATE_PLACEHOLDER configuration item, for example, simplifying it to $m!"
### Message Template Placeholder Config
#### Overview
The `OCO_MESSAGE_TEMPLATE_PLACEHOLDER` feature in the `opencommit` tool allows users to embed a custom message within the generated commit message using a template function. This configuration is designed to enhance the flexibility and customizability of commit messages, making it easier for users to include relevant information directly within their commits.
#### Implementation Details
In our codebase, the implementation of this feature can be found in the following segment:
```javascript
commitMessage = messageTemplate.replace(
config.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
commitMessage
);
```
This line is responsible for replacing the placeholder in the `messageTemplate` with the actual `commitMessage`.
#### Usage
For instance, using the command `oco '$msg #205`, users can leverage this feature. The provided code represents the backend mechanics of such commands, ensuring that the placeholder is replaced with the appropriate commit message.
#### Committing with the Message
Once users have generated their desired commit message, they can proceed to commit using the generated message. By understanding the feature's full potential and its implementation details, users can confidently use the generated messages for their commits.
### Ignore files
You can ignore files from submission to OpenAI by creating a `.opencommitignore` file. For example:
You can remove files from being sent 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.
This helps prevent opencommit from uploading artifacts and large files.
By default, opencommit ignores files matching: `*-lock.*` and `*.lock`
## Git hook (KILLER FEATURE)
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.
You can set OpenCommit as Git [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) hook. Hook integrates with your IDE Source Control and allows you to edit the message before committing.
To set the hook:
```sh
oc hook set
oco hook set
```
To unset the hook:
```sh
oc hook unset
oco hook unset
```
To use the hook:
@@ -236,6 +363,70 @@ git commit
Or follow the process of your IDE Source Control feature, when it calls `git commit` command — OpenCommit will integrate into the flow.
## Setup OpenCommit as a GitHub Action (BETA) 🔥
OpenCommit is now available as a GitHub Action which automatically improves all new commits messages when you push to remote!
This is great if you want to make sure all commits in all of your repository branches are meaningful and not lame like `fix1` or `done2`.
Create a file `.github/workflows/opencommit.yml` with the contents below:
```yml
name: 'OpenCommit Action'
on:
push:
# this list of branches is often enough,
# but you may still ignore other public branches
branches-ignore: [main master dev development release]
jobs:
opencommit:
timeout-minutes: 10
name: OpenCommit
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Setup Node.js Environment
uses: actions/setup-node@v2
with:
node-version: '16'
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: di-sukharev/opencommit@github-action-v1.0.4
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
env:
# set openAI api key in repo actions secrets,
# for openAI keys go to: https://platform.openai.com/account/api-keys
# for repo secret go to: <your_repo_url>/settings/secrets/actions
OCO_API_KEY: ${{ secrets.OCO_API_KEY }}
# customization
OCO_TOKENS_MAX_INPUT: 4096
OCO_TOKENS_MAX_OUTPUT: 500
OCO_OPENAI_BASE_PATH: ''
OCO_DESCRIPTION: false
OCO_EMOJI: false
OCO_MODEL: gpt-4o
OCO_LANGUAGE: en
OCO_PROMPT_MODULE: conventional-commit
```
That is it. Now when you push to any branch in your repo — all NEW commits are being improved by your never-tired AI.
Make sure you exclude public collaboration branches (`main`, `dev`, `etc`) in `branches-ignore`, so OpenCommit does not rebase commits there while improving the messages.
Interactive rebase (`rebase -i`) changes commits' SHA, so the commit history in remote becomes different from your local branch history. This is okay if you work on the branch alone, but may be inconvenient for other collaborators.
## Payments
You pay for your own requests to OpenAI API. OpenCommit uses ChatGPT (3.5-turbo) official model, that is ~15x times cheaper than GPT-4.
You pay for your requests to OpenAI API on your own.
OpenCommit stores your key locally.
OpenCommit by default uses 3.5-turbo model, it should not exceed $0.10 per casual working day.
You may switch to gpt-4, it's better, but more expensive.

42
jest.config.ts Normal file
View File

@@ -0,0 +1,42 @@
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from 'jest';
const config: Config = {
testTimeout: 100_000,
coverageProvider: 'v8',
moduleDirectories: ['node_modules', 'src'],
preset: 'ts-jest/presets/default-esm',
setupFilesAfterEnv: ['<rootDir>/test/jest-setup.ts'],
testEnvironment: 'node',
testRegex: ['.*\\.test\\.ts$'],
// Tell Jest to ignore the specific duplicate package.json files
// that are causing Haste module naming collisions
modulePathIgnorePatterns: [
'<rootDir>/test/e2e/prompt-module/data/'
],
transformIgnorePatterns: [
'node_modules/(?!(cli-testing-library|@clack|cleye)/.*)'
],
transform: {
'^.+\\.(ts|tsx|js|jsx|mjs)$': [
'ts-jest',
{
diagnostics: false,
useESM: true,
tsconfig: {
module: 'ESNext',
target: 'ES2022'
}
}
]
},
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
}
};
export default config;

60412
out/cli.cjs

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

8244
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "opencommit",
"version": "2.0.17",
"version": "3.2.8",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [
"git",
@@ -12,22 +12,23 @@
"aicommit",
"aicommits",
"gptcommit",
"commit"
"commit",
"ollama"
],
"main": "cli.js",
"bin": {
"opencommit": "./out/cli.cjs",
"oc": "./out/cli.cjs",
"oco": "./out/cli.cjs"
"opencommit": "out/cli.cjs",
"oco": "out/cli.cjs"
},
"repository": {
"url": "https://github.com/di-sukharev/opencommit"
"url": "git+https://github.com/di-sukharev/opencommit.git"
},
"type": "module",
"author": "https://github.com/di-sukharev",
"license": "MIT",
"files": [
"out/**/*"
"out/cli.cjs",
"out/tiktoken_bg.wasm"
],
"release": {
"branches": [
@@ -40,40 +41,71 @@
"scripts": {
"watch": "npm run -S build -- --sourcemap --watch",
"start": "node ./out/cli.cjs",
"ollama:start": "OCO_AI_PROVIDER='ollama' node ./out/cli.cjs",
"dev": "ts-node ./src/cli.ts",
"build": "rimraf out && node esbuild.config.js",
"deploy": "npm run build && npm version patch && npm publish --tag latest",
"dev:gemini": "OCO_AI_PROVIDER='gemini' ts-node ./src/cli.ts",
"build": "npx rimraf out && node esbuild.config.js",
"build:push": "npm run build && git add . && git commit -m 'build' && git push",
"deploy": "npm publish --tag latest",
"deploy:build": "npm run build:push && git push --tags && npm run deploy",
"deploy:patch": "npm version patch && npm run deploy:build",
"lint": "eslint src --ext ts && tsc --noEmit",
"format": "prettier --write src"
"format": "prettier --write src",
"test": "node --no-warnings --experimental-vm-modules $( [ -f ./node_modules/.bin/jest ] && echo ./node_modules/.bin/jest || which jest ) test/unit",
"test:all": "npm run test:unit:docker && npm run test:e2e:docker",
"test:docker-build": "docker build -t oco-test -f test/Dockerfile .",
"test:unit": "NODE_OPTIONS=--experimental-vm-modules jest test/unit",
"test:unit:docker": "npm run test:docker-build && DOCKER_CONTENT_TRUST=0 docker run --rm oco-test npm run test:unit",
"test:e2e": "npm run test:e2e:setup && jest test/e2e",
"test:e2e:setup": "sh test/e2e/setup.sh",
"test:e2e:docker": "npm run test:docker-build && DOCKER_CONTENT_TRUST=0 docker run --rm oco-test npm run test:e2e",
"mlx:start": "OCO_AI_PROVIDER='mlx' node ./out/cli.cjs"
},
"devDependencies": {
"@commitlint/types": "^17.4.4",
"@types/ini": "^1.3.31",
"@types/inquirer": "^9.0.3",
"@types/jest": "^29.5.12",
"@types/node": "^16.18.14",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"@typescript-eslint/eslint-plugin": "^8.29.0",
"@typescript-eslint/parser": "^8.29.0",
"cli-testing-library": "^2.0.2",
"dotenv": "^16.0.3",
"esbuild": "^0.15.18",
"eslint": "^8.28.0",
"esbuild": "^0.25.5",
"eslint": "^9.24.0",
"jest": "^29.7.0",
"prettier": "^2.8.4",
"rimraf": "^6.0.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.1",
"typescript": "^4.9.3"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@actions/github": "^6.0.1",
"@anthropic-ai/sdk": "^0.19.2",
"@azure/openai": "^1.0.0-beta.12",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
"@google/generative-ai": "^0.11.4",
"@mistralai/mistralai": "^1.3.5",
"@octokit/webhooks-schemas": "^6.11.0",
"@octokit/webhooks-types": "^6.11.0",
"axios": "^1.3.4",
"chalk": "^5.2.0",
"cleye": "^1.3.2",
"crypto": "^1.0.1",
"execa": "^7.0.0",
"ignore": "^5.2.4",
"ini": "^3.0.1",
"inquirer": "^9.1.4",
"openai": "^3.2.1"
"openai": "^4.57.0",
"punycode": "^2.3.1",
"zod": "^3.23.8"
},
"overrides": {
"ajv": "^8.17.1",
"whatwg-url": "^14.0.0"
}
}

View File

@@ -1,4 +1,5 @@
export enum COMMANDS {
config = 'config',
hook = 'hook',
config = 'config'
commitlint = 'commitlint'
}

View File

@@ -1,101 +0,0 @@
import { intro, outro } from '@clack/prompts';
import axios from 'axios';
import chalk from 'chalk';
import {
ChatCompletionRequestMessage,
Configuration as OpenAiApiConfiguration,
OpenAIApi
} from 'openai';
import { CONFIG_MODES, getConfig } from './commands/config';
const config = getConfig();
let maxTokens = config?.OCO_OPENAI_MAX_TOKENS;
let basePath = config?.OCO_OPENAI_BASE_PATH;
let apiKey = config?.OCO_OPENAI_API_KEY;
const [command, mode] = process.argv.slice(2);
if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set) {
intro('opencommit');
outro(
'OCO_OPENAI_API_KEY is not set, please run `oc config set OCO_OPENAI_API_KEY=<your token>. Make sure you add payment details, so API works.`'
);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
process.exit(1);
}
const MODEL = config?.OCO_MODEL || 'gpt-3.5-turbo';
class OpenAi {
private openAiApiConfiguration = new OpenAiApiConfiguration({
apiKey: apiKey
});
private openAI!: OpenAIApi;
constructor() {
if (basePath) {
this.openAiApiConfiguration.basePath = basePath;
}
this.openAI = new OpenAIApi(this.openAiApiConfiguration);
}
public generateCommitMessage = async (
messages: Array<ChatCompletionRequestMessage>
): Promise<string | undefined> => {
const params = {
model: MODEL,
messages,
temperature: 0,
top_p: 0.1,
max_tokens: maxTokens || 500
};
try {
const { data } = await this.openAI.createChatCompletion(params);
const message = data.choices[0].message;
return message?.content;
} catch (error) {
outro(`${chalk.red('✖')} ${JSON.stringify(params)}`);
const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const openAiError = error.response.data.error;
if (openAiError?.message) outro(openAiError.message);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
}
throw err;
}
};
}
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

@@ -1,13 +1,15 @@
#!/usr/bin/env node
import { cli } from 'cleye';
import packageJSON from '../package.json' assert { type: 'json' };
import packageJSON from '../package.json';
import { commit } from './commands/commit';
import { commitlintConfigCommand } from './commands/commitlint';
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';
import { checkIsLatestVersion } from './utils/checkIsLatestVersion';
import { runMigrations } from './migrations/_run.js';
const extraArgs = process.argv.slice(2);
@@ -15,18 +17,37 @@ cli(
{
version: packageJSON.version,
name: 'opencommit',
commands: [configCommand, hookCommand],
flags: {},
commands: [configCommand, hookCommand, commitlintConfigCommand],
flags: {
fgm: {
type: Boolean,
description: 'Use full GitMoji specification',
default: false
},
context: {
type: String,
alias: 'c',
description: 'Additional user input context for the commit message',
default: ''
},
yes: {
type: Boolean,
alias: 'y',
description: 'Skip commit confirmation prompt',
default: false
}
},
ignoreArgv: (type) => type === 'unknown-flag' || type === 'argument',
help: { description: packageJSON.description }
},
async () => {
// await checkIsLatestVersion();
async ({ flags }) => {
await runMigrations();
await checkIsLatestVersion();
if (await isHookCalled()) {
prepareCommitMessageHook();
} else {
commit(extraArgs);
commit(extraArgs, flags.context, false, flags.fgm, flags.yes);
}
},
extraArgs

5
src/commands/ENUMS.ts Normal file
View File

@@ -0,0 +1,5 @@
export enum COMMANDS {
config = 'config',
hook = 'hook',
commitlint = 'commitlint'
}

9
src/commands/README.md Normal file
View File

@@ -0,0 +1,9 @@
# @commitlint Module for opencommit
1. Load commitlint configuration within tree.
2. Generate a commit with commitlint prompt:
- Will not run if hash is the same.
- Infer a prompt for each commitlint rule.
- Ask OpenAI to generate consistency with embedded commitlint rules.
- Store configuration close to commitlint configuration.
3. Replace conventional-commit prompt with commitlint prompt.

View File

@@ -1,8 +1,15 @@
import { execa } from 'execa';
import {
GenerateCommitMessageErrorEnum,
generateCommitMessageByDiff
} from '../generateCommitMessageFromGitDiff';
confirm,
intro,
isCancel,
multiselect,
outro,
select,
spinner
} from '@clack/prompts';
import chalk from 'chalk';
import { execa } from 'execa';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
import {
assertGitRepo,
getChangedFiles,
@@ -10,61 +17,102 @@ import {
getStagedFiles,
gitAdd
} from '../utils/git';
import {
spinner,
confirm,
outro,
isCancel,
intro,
multiselect,
select
} from '@clack/prompts';
import chalk from 'chalk';
import { trytm } from '../utils/trytm';
import { getConfig } from './config';
const config = getConfig();
const getGitRemotes = async () => {
const { stdout } = await execa('git', ['remote']);
return stdout.split('\n').filter((remote) => Boolean(remote.trim()));
};
const generateCommitMessageFromGitDiff = async (
diff: string,
extraArgs: string[]
): Promise<void> => {
// Check for the presence of message templates
const checkMessageTemplate = (extraArgs: string[]): string | false => {
for (const key in extraArgs) {
if (extraArgs[key].includes(config.OCO_MESSAGE_TEMPLATE_PLACEHOLDER))
return extraArgs[key];
}
return false;
};
interface GenerateCommitMessageFromGitDiffParams {
diff: string;
extraArgs: string[];
context?: string;
fullGitMojiSpec?: boolean;
skipCommitConfirmation?: boolean;
}
const generateCommitMessageFromGitDiff = async ({
diff,
extraArgs,
context = '',
fullGitMojiSpec = false,
skipCommitConfirmation = false
}: GenerateCommitMessageFromGitDiffParams): Promise<void> => {
await assertGitRepo();
const commitGenerationSpinner = spinner();
commitGenerationSpinner.start('Generating the commit message');
const commitSpinner = spinner();
commitSpinner.start('Generating the commit message');
try {
const commitMessage = await generateCommitMessageByDiff(diff);
let commitMessage = await generateCommitMessageByDiff(
diff,
fullGitMojiSpec,
context
);
commitSpinner.stop('📝 Commit message generated');
const messageTemplate = checkMessageTemplate(extraArgs);
if (
config.OCO_MESSAGE_TEMPLATE_PLACEHOLDER &&
typeof messageTemplate === 'string'
) {
const messageTemplateIndex = extraArgs.indexOf(messageTemplate);
extraArgs.splice(messageTemplateIndex, 1);
commitMessage = messageTemplate.replace(
config.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
commitMessage
);
}
commitGenerationSpinner.stop('📝 Commit message generated');
outro(
`Commit message:
`Generated commit message:
${chalk.grey('——————————————————')}
${commitMessage}
${chalk.grey('——————————————————')}`
);
const isCommitConfirmedByUser = await confirm({
message: 'Confirm the commit message?'
});
const isCommitConfirmedByUser =
skipCommitConfirmation ||
(await confirm({
message: 'Confirm the commit message?'
}));
if (isCommitConfirmedByUser && !isCancel(isCommitConfirmedByUser)) {
if (isCancel(isCommitConfirmedByUser)) process.exit(1);
if (isCommitConfirmedByUser) {
const committingChangesSpinner = spinner();
committingChangesSpinner.start('Committing the changes');
const { stdout } = await execa('git', [
'commit',
'-m',
commitMessage,
...extraArgs
]);
outro(`${chalk.green('✔')} Successfully committed`);
committingChangesSpinner.stop(
`${chalk.green('✔')} Successfully committed`
);
outro(stdout);
const remotes = await getGitRemotes();
// user isn't pushing, return early
if (config.OCO_GITPUSH === false) return;
if (!remotes.length) {
const { stdout } = await execa('git', ['push']);
if (stdout) outro(stdout);
@@ -76,10 +124,12 @@ ${chalk.grey('——————————————————')}`
message: 'Do you want to run `git push`?'
});
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
if (isCancel(isPushConfirmedByUser)) process.exit(1);
if (isPushConfirmedByUser) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${remotes[0]}\``);
pushSpinner.start(`Running 'git push ${remotes[0]}'`);
const { stdout } = await execa('git', [
'push',
@@ -99,30 +149,54 @@ ${chalk.grey('——————————————————')}`
process.exit(0);
}
} else {
const skipOption = `don't push`;
const selectedRemote = (await select({
message: 'Choose a remote to push to',
options: remotes.map((remote) => ({ value: remote, label: remote }))
options: [...remotes, skipOption].map((remote) => ({
value: remote,
label: remote
}))
})) as string;
if (!isCancel(selectedRemote)) {
if (isCancel(selectedRemote)) process.exit(1);
if (selectedRemote !== skipOption) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${selectedRemote}\``);
pushSpinner.start(`Running 'git push ${selectedRemote}'`);
const { stdout } = await execa('git', ['push', selectedRemote]);
if (stdout) outro(stdout);
pushSpinner.stop(
`${chalk.green(
'✔'
)} Successfully pushed all commits to ${selectedRemote}`
)} successfully pushed all commits to ${selectedRemote}`
);
}
}
} else {
const regenerateMessage = await confirm({
message: 'Do you want to regenerate the message?'
});
if (stdout) outro(stdout);
} else outro(`${chalk.gray('✖')} process cancelled`);
if (isCancel(regenerateMessage)) process.exit(1);
if (regenerateMessage) {
await generateCommitMessageFromGitDiff({
diff,
extraArgs,
fullGitMojiSpec
});
}
}
} catch (error) {
commitSpinner.stop('📝 Commit message generated');
commitGenerationSpinner.stop(
`${chalk.red('✖')} Failed to generate the commit message`
);
console.log(error);
const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);
@@ -132,14 +206,17 @@ ${chalk.grey('——————————————————')}`
export async function commit(
extraArgs: string[] = [],
isStageAllFlag: Boolean = false
context: string = '',
isStageAllFlag: Boolean = false,
fullGitMojiSpec: boolean = false,
skipCommitConfirmation: 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');
outro('No changes detected, write some code and run `oco` again');
process.exit(1);
}
}
@@ -162,18 +239,18 @@ export async function commit(
stagedFilesSpinner.start('Counting staged files');
if (!stagedFiles.length) {
if (stagedFiles.length === 0) {
stagedFilesSpinner.stop('No files are staged');
const isStageAllAndCommitConfirmedByUser = await confirm({
message: 'Do you want to stage all files and generate commit message?'
});
if (
isStageAllAndCommitConfirmedByUser &&
!isCancel(isStageAllAndCommitConfirmedByUser)
) {
await commit(extraArgs, true);
process.exit(1);
if (isCancel(isStageAllAndCommitConfirmedByUser)) process.exit(1);
if (isStageAllAndCommitConfirmedByUser) {
await commit(extraArgs, context, true, fullGitMojiSpec);
process.exit(0);
}
if (stagedFiles.length === 0 && changedFiles.length > 0) {
@@ -185,13 +262,13 @@ export async function commit(
}))
})) as string[];
if (isCancel(files)) process.exit(1);
if (isCancel(files)) process.exit(0);
await gitAdd({ files });
}
await commit(extraArgs, false);
process.exit(1);
await commit(extraArgs, context, false, fullGitMojiSpec);
process.exit(0);
}
stagedFilesSpinner.stop(
@@ -201,10 +278,13 @@ export async function commit(
);
const [, generateCommitError] = await trytm(
generateCommitMessageFromGitDiff(
await getDiff({ files: stagedFiles }),
extraArgs
)
generateCommitMessageFromGitDiff({
diff: await getDiff({ files: stagedFiles }),
extraArgs,
context,
fullGitMojiSpec,
skipCommitConfirmation
})
);
if (generateCommitError) {

View File

@@ -0,0 +1,44 @@
import { intro, outro } from '@clack/prompts';
import chalk from 'chalk';
import { command } from 'cleye';
import { configureCommitlintIntegration } from '../modules/commitlint/config';
import { getCommitlintLLMConfig } from '../modules/commitlint/utils';
import { COMMANDS } from './ENUMS';
export enum CONFIG_MODES {
get = 'get',
force = 'force'
}
export const commitlintConfigCommand = command(
{
name: COMMANDS.commitlint,
parameters: ['<mode>']
},
async (argv) => {
intro('opencommit — configure @commitlint');
try {
const { mode } = argv._;
if (mode === CONFIG_MODES.get) {
const commitLintConfig = await getCommitlintLLMConfig();
outro(JSON.stringify(commitLintConfig, null, 2));
return;
}
if (mode === CONFIG_MODES.force) {
await configureCommitlintIntegration(true);
return;
}
throw new Error(
`Unsupported mode: ${mode}. Valid modes are: "force" and "get"`
);
} catch (error) {
outro(`${chalk.red('✖')} ${error}`);
process.exit(1);
}
}
);

View File

@@ -1,30 +1,164 @@
import { command } from 'cleye';
import { join as pathJoin } from 'path';
import { parse as iniParse, stringify as iniStringify } from 'ini';
import { existsSync, writeFileSync, readFileSync } from 'fs';
import { homedir } from 'os';
import { intro, outro } from '@clack/prompts';
import chalk from 'chalk';
import { COMMANDS } from '../CommandsEnum';
import { getI18nLocal } from '../i18n';
import { command } from 'cleye';
import * as dotenv from 'dotenv';
dotenv.config();
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { parse as iniParse, stringify as iniStringify } from 'ini';
import { homedir } from 'os';
import { join as pathJoin, resolve as pathResolve } from 'path';
import { COMMANDS } from './ENUMS';
import { TEST_MOCK_TYPES } from '../engine/testAi';
import { getI18nLocal, i18n } from '../i18n';
export enum CONFIG_KEYS {
OCO_OPENAI_API_KEY = 'OCO_OPENAI_API_KEY',
OCO_OPENAI_MAX_TOKENS = 'OCO_OPENAI_MAX_TOKENS',
OCO_OPENAI_BASE_PATH = 'OCO_OPENAI_BASE_PATH',
OCO_API_KEY = 'OCO_API_KEY',
OCO_TOKENS_MAX_INPUT = 'OCO_TOKENS_MAX_INPUT',
OCO_TOKENS_MAX_OUTPUT = 'OCO_TOKENS_MAX_OUTPUT',
OCO_DESCRIPTION = 'OCO_DESCRIPTION',
OCO_EMOJI = 'OCO_EMOJI',
OCO_MODEL = 'OCO_MODEL',
OCO_LANGUAGE = 'OCO_LANGUAGE'
OCO_LANGUAGE = 'OCO_LANGUAGE',
OCO_WHY = 'OCO_WHY',
OCO_MESSAGE_TEMPLATE_PLACEHOLDER = 'OCO_MESSAGE_TEMPLATE_PLACEHOLDER',
OCO_PROMPT_MODULE = 'OCO_PROMPT_MODULE',
OCO_AI_PROVIDER = 'OCO_AI_PROVIDER',
OCO_ONE_LINE_COMMIT = 'OCO_ONE_LINE_COMMIT',
OCO_TEST_MOCK_TYPE = 'OCO_TEST_MOCK_TYPE',
OCO_API_URL = 'OCO_API_URL',
OCO_API_CUSTOM_HEADERS = 'OCO_API_CUSTOM_HEADERS',
OCO_OMIT_SCOPE = 'OCO_OMIT_SCOPE',
OCO_GITPUSH = 'OCO_GITPUSH' // todo: deprecate
}
export enum CONFIG_MODES {
get = 'get',
set = 'set'
set = 'set',
describe = 'describe'
}
export const MODEL_LIST = {
openai: [
'gpt-4o-mini',
'gpt-3.5-turbo',
'gpt-3.5-turbo-instruct',
'gpt-3.5-turbo-0613',
'gpt-3.5-turbo-0301',
'gpt-3.5-turbo-1106',
'gpt-3.5-turbo-0125',
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-16k-0613',
'gpt-3.5-turbo-16k-0301',
'gpt-4',
'gpt-4-0314',
'gpt-4-0613',
'gpt-4-1106-preview',
'gpt-4-0125-preview',
'gpt-4-turbo-preview',
'gpt-4-vision-preview',
'gpt-4-1106-vision-preview',
'gpt-4-turbo',
'gpt-4-turbo-2024-04-09',
'gpt-4-32k',
'gpt-4-32k-0314',
'gpt-4-32k-0613',
'gpt-4o',
'gpt-4o-2024-05-13',
'gpt-4o-mini-2024-07-18'
],
anthropic: [
'claude-3-5-sonnet-20240620',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-3-haiku-20240307'
],
gemini: [
'gemini-1.5-flash',
'gemini-1.5-pro',
'gemini-1.0-pro',
'gemini-pro-vision',
'text-embedding-004'
],
groq: [
'llama3-70b-8192', // Meta Llama 3 70B (default one, no daily token limit and 14 400 reqs/day)
'llama3-8b-8192', // Meta Llama 3 8B
'llama-guard-3-8b', // Llama Guard 3 8B
'llama-3.1-8b-instant', // Llama 3.1 8B (Preview)
'llama-3.1-70b-versatile', // Llama 3.1 70B (Preview)
'gemma-7b-it', // Gemma 7B
'gemma2-9b-it' // Gemma 2 9B
],
mistral: [
'ministral-3b-2410',
'ministral-3b-latest',
'ministral-8b-2410',
'ministral-8b-latest',
'open-mistral-7b',
'mistral-tiny',
'mistral-tiny-2312',
'open-mistral-nemo',
'open-mistral-nemo-2407',
'mistral-tiny-2407',
'mistral-tiny-latest',
'open-mixtral-8x7b',
'mistral-small',
'mistral-small-2312',
'open-mixtral-8x22b',
'open-mixtral-8x22b-2404',
'mistral-small-2402',
'mistral-small-2409',
'mistral-small-latest',
'mistral-medium-2312',
'mistral-medium',
'mistral-medium-latest',
'mistral-large-2402',
'mistral-large-2407',
'mistral-large-2411',
'mistral-large-latest',
'pixtral-large-2411',
'pixtral-large-latest',
'codestral-2405',
'codestral-latest',
'codestral-mamba-2407',
'open-codestral-mamba',
'codestral-mamba-latest',
'pixtral-12b-2409',
'pixtral-12b',
'pixtral-12b-latest',
'mistral-embed',
'mistral-moderation-2411',
'mistral-moderation-latest'
],
deepseek: ['deepseek-chat', 'deepseek-reasoner']
};
const getDefaultModel = (provider: string | undefined): string => {
switch (provider) {
case 'ollama':
return '';
case 'mlx':
return '';
case 'anthropic':
return MODEL_LIST.anthropic[0];
case 'gemini':
return MODEL_LIST.gemini[0];
case 'groq':
return MODEL_LIST.groq[0];
case 'mistral':
return MODEL_LIST.mistral[0];
case 'deepseek':
return MODEL_LIST.deepseek[0];
default:
return MODEL_LIST.openai[0];
}
};
export enum DEFAULT_TOKEN_LIMITS {
DEFAULT_MAX_TOKENS_INPUT = 4096,
DEFAULT_MAX_TOKENS_OUTPUT = 500
}
const validateConfig = (
@@ -33,8 +167,10 @@ const validateConfig = (
validationMessage: string
) => {
if (!condition) {
outro(`${chalk.red('✖')} wrong value for ${key}: ${validationMessage}.`);
outro(
`${chalk.red('✖')} Unsupported config key ${key}: ${validationMessage}`
'For more help refer to docs https://github.com/di-sukharev/opencommit'
);
process.exit(1);
@@ -42,17 +178,19 @@ const validateConfig = (
};
export const configValidators = {
[CONFIG_KEYS.OCO_OPENAI_API_KEY](value: any) {
validateConfig(CONFIG_KEYS.OCO_OPENAI_API_KEY, value, 'Cannot be empty');
[CONFIG_KEYS.OCO_API_KEY](value: any, config: any = {}) {
if (config.OCO_AI_PROVIDER !== 'openai') return value;
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.startsWith('sk-'),
'Must start with "sk-"'
'OCO_API_KEY',
typeof value === 'string' && value.length > 0,
'Empty value is not allowed'
);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.length === 51,
'Must be 51 characters long'
'OCO_API_KEY',
value,
'You need to provide the OCO_API_KEY when OCO_AI_PROVIDER set to "openai" (default) or "ollama" or "mlx" or "azure" or "gemini" or "flowise" or "anthropic" or "deepseek". Run `oco config set OCO_API_KEY=your_key OCO_AI_PROVIDER=openai`'
);
return value;
@@ -62,25 +200,44 @@ export const configValidators = {
validateConfig(
CONFIG_KEYS.OCO_DESCRIPTION,
typeof value === 'boolean',
'Must be true or false'
'Must be boolean: true or false'
);
return value;
},
[CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS](value: any) {
// If the value is a string, convert it to a number.
if (typeof value === 'string') {
value = parseInt(value);
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS](value) {
try {
// Custom headers must be a valid JSON string
if (typeof value === 'string') {
JSON.parse(value);
}
return value;
} catch (error) {
validateConfig(
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
!isNaN(value),
'Must be a number'
CONFIG_KEYS.OCO_API_CUSTOM_HEADERS,
false,
'Must be a valid JSON string of headers'
);
}
},
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT](value: any) {
value = parseInt(value);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
typeof value === 'number',
CONFIG_KEYS.OCO_TOKENS_MAX_INPUT,
!isNaN(value),
'Must be a number'
);
return value;
},
[CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT](value: any) {
value = parseInt(value);
validateConfig(
CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT,
!isNaN(value),
'Must be a number'
);
@@ -91,133 +248,583 @@ export const configValidators = {
validateConfig(
CONFIG_KEYS.OCO_EMOJI,
typeof value === 'boolean',
'Must be true or false'
'Must be boolean: true or false'
);
return value;
},
[CONFIG_KEYS.OCO_OMIT_SCOPE](value: any) {
validateConfig(
CONFIG_KEYS.OCO_OMIT_SCOPE,
typeof value === 'boolean',
'Must be boolean: true or false'
);
return value;
},
[CONFIG_KEYS.OCO_LANGUAGE](value: any) {
const supportedLanguages = Object.keys(i18n);
validateConfig(
CONFIG_KEYS.OCO_LANGUAGE,
getI18nLocal(value),
`${value} is not supported yet`
`${value} is not supported yet. Supported languages: ${supportedLanguages}`
);
return getI18nLocal(value);
},
[CONFIG_KEYS.OCO_OPENAI_BASE_PATH](value: any) {
[CONFIG_KEYS.OCO_API_URL](value: any) {
validateConfig(
CONFIG_KEYS.OCO_OPENAI_BASE_PATH,
CONFIG_KEYS.OCO_API_URL,
typeof value === 'string',
'Must be string'
`${value} is not a valid URL. It should start with 'http://' or 'https://'.`
);
return value;
},
[CONFIG_KEYS.OCO_MODEL](value: any) {
[CONFIG_KEYS.OCO_MODEL](value: any, config: any = {}) {
validateConfig(
CONFIG_KEYS.OCO_OPENAI_BASE_PATH,
value === 'gpt-3.5-turbo' || value === 'gpt-4',
`${value} is not supported yet, use 'gpt-4' or 'gpt-3.5-turbo' (default)`
CONFIG_KEYS.OCO_MODEL,
typeof value === 'string',
`${value} is not supported yet, use:\n\n ${[
...MODEL_LIST.openai,
...MODEL_LIST.anthropic,
...MODEL_LIST.gemini
].join('\n')}`
);
return value;
},
[CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER](value: any) {
validateConfig(
CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
value.startsWith('$'),
`${value} must start with $, for example: '$msg'`
);
return value;
},
[CONFIG_KEYS.OCO_PROMPT_MODULE](value: any) {
validateConfig(
CONFIG_KEYS.OCO_PROMPT_MODULE,
['conventional-commit', '@commitlint'].includes(value),
`${value} is not supported yet, use '@commitlint' or 'conventional-commit' (default)`
);
return value;
},
// todo: deprecate
[CONFIG_KEYS.OCO_GITPUSH](value: any) {
validateConfig(
CONFIG_KEYS.OCO_GITPUSH,
typeof value === 'boolean',
'Must be true or false'
);
return value;
},
[CONFIG_KEYS.OCO_AI_PROVIDER](value: any) {
if (!value) value = 'openai';
validateConfig(
CONFIG_KEYS.OCO_AI_PROVIDER,
[
'openai',
'mistral',
'anthropic',
'gemini',
'azure',
'test',
'flowise',
'groq',
'deepseek'
].includes(value) || value.startsWith('ollama'),
`${value} is not supported yet, use 'ollama', 'mlx', 'anthropic', 'azure', 'gemini', 'flowise', 'mistral', 'deepseek' or 'openai' (default)`
);
return value;
},
[CONFIG_KEYS.OCO_ONE_LINE_COMMIT](value: any) {
validateConfig(
CONFIG_KEYS.OCO_ONE_LINE_COMMIT,
typeof value === 'boolean',
'Must be true or false'
);
return value;
},
[CONFIG_KEYS.OCO_TEST_MOCK_TYPE](value: any) {
validateConfig(
CONFIG_KEYS.OCO_TEST_MOCK_TYPE,
TEST_MOCK_TYPES.includes(value),
`${value} is not supported yet, use ${TEST_MOCK_TYPES.map(
(t) => `'${t}'`
).join(', ')}`
);
return value;
},
[CONFIG_KEYS.OCO_WHY](value: any) {
validateConfig(
CONFIG_KEYS.OCO_WHY,
typeof value === 'boolean',
'Must be true or false'
);
return value;
}
};
export enum OCO_AI_PROVIDER_ENUM {
OLLAMA = 'ollama',
OPENAI = 'openai',
ANTHROPIC = 'anthropic',
GEMINI = 'gemini',
AZURE = 'azure',
TEST = 'test',
FLOWISE = 'flowise',
GROQ = 'groq',
MISTRAL = 'mistral',
MLX = 'mlx',
DEEPSEEK = 'deepseek'
}
export type ConfigType = {
[key in CONFIG_KEYS]?: any;
[CONFIG_KEYS.OCO_API_KEY]?: string;
[CONFIG_KEYS.OCO_TOKENS_MAX_INPUT]: number;
[CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT]: number;
[CONFIG_KEYS.OCO_API_URL]?: string;
[CONFIG_KEYS.OCO_API_CUSTOM_HEADERS]?: string;
[CONFIG_KEYS.OCO_DESCRIPTION]: boolean;
[CONFIG_KEYS.OCO_EMOJI]: boolean;
[CONFIG_KEYS.OCO_WHY]: boolean;
[CONFIG_KEYS.OCO_MODEL]: string;
[CONFIG_KEYS.OCO_LANGUAGE]: string;
[CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER]: string;
[CONFIG_KEYS.OCO_PROMPT_MODULE]: OCO_PROMPT_MODULE_ENUM;
[CONFIG_KEYS.OCO_AI_PROVIDER]: OCO_AI_PROVIDER_ENUM;
[CONFIG_KEYS.OCO_GITPUSH]: boolean;
[CONFIG_KEYS.OCO_ONE_LINE_COMMIT]: boolean;
[CONFIG_KEYS.OCO_OMIT_SCOPE]: boolean;
[CONFIG_KEYS.OCO_TEST_MOCK_TYPE]: string;
};
const configPath = pathJoin(homedir(), '.opencommit');
export const defaultConfigPath = pathJoin(homedir(), '.opencommit');
export const defaultEnvPath = pathResolve(process.cwd(), '.env');
export const getConfig = (): ConfigType | null => {
const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_OPENAI_MAX_TOKENS: Number(process.env.OCO_OPENAI_MAX_TOKENS),
OCO_OPENAI_BASE_PATH: process.env.OCO_OPENAI_BASE_PATH,
OCO_DESCRIPTION: process.env.OCO_DESCRIPTION === 'true' ? true : false,
OCO_EMOJI: process.env.OCO_EMOJI === 'true' ? true : false,
OCO_MODEL: process.env.OCO_MODEL,
OCO_LANGUAGE: process.env.OCO_LANGUAGE
};
const assertConfigsAreValid = (config: Record<string, any>) => {
for (const [key, value] of Object.entries(config)) {
if (!value) continue;
const configExists = existsSync(configPath);
if (!configExists) return configFromEnv;
if (typeof value === 'string' && ['null', 'undefined'].includes(value)) {
config[key] = undefined;
continue;
}
const configFile = readFileSync(configPath, 'utf8');
const config = iniParse(configFile);
for (const configKey of Object.keys(config)) {
try {
const validator = configValidators[configKey as CONFIG_KEYS];
const validValue = validator(
config[configKey] ?? configFromEnv[configKey as CONFIG_KEYS]
);
config[configKey] = validValue;
const validate = configValidators[key as CONFIG_KEYS];
validate(value, config);
} catch (error) {
outro(
`'${configKey}' name is invalid, it should be either 'OCO_${configKey.toUpperCase()}' or it doesn't exist.`
);
outro(`Unknown '${key}' config option or missing validator.`);
outro(
`Manually fix the '.env' file or global '~/.opencommit' config file.`
);
process.exit(1);
}
}
return config;
};
export const setConfig = (keyValues: [key: string, value: string][]) => {
const config = getConfig() || {};
enum OCO_PROMPT_MODULE_ENUM {
CONVENTIONAL_COMMIT = 'conventional-commit',
COMMITLINT = '@commitlint'
}
for (const [configKey, configValue] of keyValues) {
if (!configValidators.hasOwnProperty(configKey)) {
throw new Error(`Unsupported config key: ${configKey}`);
export const DEFAULT_CONFIG = {
OCO_TOKENS_MAX_INPUT: DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_INPUT,
OCO_TOKENS_MAX_OUTPUT: DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT,
OCO_DESCRIPTION: false,
OCO_EMOJI: false,
OCO_MODEL: getDefaultModel('openai'),
OCO_LANGUAGE: 'en',
OCO_MESSAGE_TEMPLATE_PLACEHOLDER: '$msg',
OCO_PROMPT_MODULE: OCO_PROMPT_MODULE_ENUM.CONVENTIONAL_COMMIT,
OCO_AI_PROVIDER: OCO_AI_PROVIDER_ENUM.OPENAI,
OCO_ONE_LINE_COMMIT: false,
OCO_TEST_MOCK_TYPE: 'commit-message',
OCO_WHY: false,
OCO_OMIT_SCOPE: false,
OCO_GITPUSH: true // todo: deprecate
};
const initGlobalConfig = (configPath: string = defaultConfigPath) => {
writeFileSync(configPath, iniStringify(DEFAULT_CONFIG), 'utf8');
return DEFAULT_CONFIG;
};
const parseConfigVarValue = (value?: any) => {
try {
return JSON.parse(value);
} catch (error) {
return value;
}
};
const getEnvConfig = (envPath: string) => {
dotenv.config({ path: envPath });
return {
OCO_MODEL: process.env.OCO_MODEL,
OCO_API_URL: process.env.OCO_API_URL,
OCO_API_KEY: process.env.OCO_API_KEY,
OCO_API_CUSTOM_HEADERS: process.env.OCO_API_CUSTOM_HEADERS,
OCO_AI_PROVIDER: process.env.OCO_AI_PROVIDER as OCO_AI_PROVIDER_ENUM,
OCO_TOKENS_MAX_INPUT: parseConfigVarValue(process.env.OCO_TOKENS_MAX_INPUT),
OCO_TOKENS_MAX_OUTPUT: parseConfigVarValue(
process.env.OCO_TOKENS_MAX_OUTPUT
),
OCO_DESCRIPTION: parseConfigVarValue(process.env.OCO_DESCRIPTION),
OCO_EMOJI: parseConfigVarValue(process.env.OCO_EMOJI),
OCO_LANGUAGE: process.env.OCO_LANGUAGE,
OCO_MESSAGE_TEMPLATE_PLACEHOLDER:
process.env.OCO_MESSAGE_TEMPLATE_PLACEHOLDER,
OCO_PROMPT_MODULE: process.env.OCO_PROMPT_MODULE as OCO_PROMPT_MODULE_ENUM,
OCO_ONE_LINE_COMMIT: parseConfigVarValue(process.env.OCO_ONE_LINE_COMMIT),
OCO_TEST_MOCK_TYPE: process.env.OCO_TEST_MOCK_TYPE,
OCO_OMIT_SCOPE: parseConfigVarValue(process.env.OCO_OMIT_SCOPE),
OCO_GITPUSH: parseConfigVarValue(process.env.OCO_GITPUSH) // todo: deprecate
};
};
export const setGlobalConfig = (
config: ConfigType,
configPath: string = defaultConfigPath
) => {
writeFileSync(configPath, iniStringify(config), 'utf8');
};
export const getIsGlobalConfigFileExist = (
configPath: string = defaultConfigPath
) => {
return existsSync(configPath);
};
export const getGlobalConfig = (configPath: string = defaultConfigPath) => {
let globalConfig: ConfigType;
const isGlobalConfigFileExist = getIsGlobalConfigFileExist(configPath);
if (!isGlobalConfigFileExist) globalConfig = initGlobalConfig(configPath);
else {
const configFile = readFileSync(configPath, 'utf8');
globalConfig = iniParse(configFile) as ConfigType;
}
return globalConfig;
};
/**
* Merges two configs.
* Env config takes precedence over global ~/.opencommit config file
* @param main - env config
* @param fallback - global ~/.opencommit config file
* @returns merged config
*/
const mergeConfigs = (main: Partial<ConfigType>, fallback: ConfigType) => {
const allKeys = new Set([...Object.keys(main), ...Object.keys(fallback)]);
return Array.from(allKeys).reduce((acc, key) => {
acc[key] = parseConfigVarValue(main[key] ?? fallback[key]);
return acc;
}, {} as ConfigType);
};
interface GetConfigOptions {
globalPath?: string;
envPath?: string;
setDefaultValues?: boolean;
}
const cleanUndefinedValues = (config: ConfigType) => {
return Object.fromEntries(
Object.entries(config).map(([_, v]) => {
try {
if (typeof v === 'string') {
if (v === 'undefined') return [_, undefined];
if (v === 'null') return [_, null];
const parsedValue = JSON.parse(v);
return [_, parsedValue];
}
return [_, v];
} catch (error) {
return [_, v];
}
})
);
};
export const getConfig = ({
envPath = defaultEnvPath,
globalPath = defaultConfigPath
}: GetConfigOptions = {}): ConfigType => {
const envConfig = getEnvConfig(envPath);
const globalConfig = getGlobalConfig(globalPath);
const config = mergeConfigs(envConfig, globalConfig);
const cleanConfig = cleanUndefinedValues(config);
return cleanConfig as ConfigType;
};
export const setConfig = (
keyValues: [key: string, value: string | boolean | number | null][],
globalConfigPath: string = defaultConfigPath
) => {
const config = getConfig({
globalPath: globalConfigPath
});
const configToSet = {};
for (let [key, value] of keyValues) {
if (!configValidators.hasOwnProperty(key)) {
const supportedKeys = Object.keys(configValidators).join('\n');
throw new Error(
`Unsupported config key: ${key}. Expected keys are:\n\n${supportedKeys}.\n\nFor more help refer to our docs: https://github.com/di-sukharev/opencommit`
);
}
let parsedConfigValue;
try {
parsedConfigValue = JSON.parse(configValue);
if (typeof value === 'string') parsedConfigValue = JSON.parse(value);
else parsedConfigValue = value;
} catch (error) {
parsedConfigValue = configValue;
parsedConfigValue = value;
}
const validValue =
configValidators[configKey as CONFIG_KEYS](parsedConfigValue);
config[configKey as CONFIG_KEYS] = validValue;
const validValue = configValidators[key as CONFIG_KEYS](
parsedConfigValue,
config
);
configToSet[key] = validValue;
}
writeFileSync(configPath, iniStringify(config), 'utf8');
setGlobalConfig(mergeConfigs(configToSet, config), globalConfigPath);
outro(`${chalk.green('✔')} Config successfully set`);
outro(`${chalk.green('✔')} config successfully set`);
};
// --- HELP MESSAGE GENERATION ---
function getConfigKeyDetails(key) {
switch (key) {
case CONFIG_KEYS.OCO_MODEL:
return {
description: 'The AI model to use for generating commit messages',
values: MODEL_LIST
};
case CONFIG_KEYS.OCO_AI_PROVIDER:
return {
description: 'The AI provider to use',
values: Object.values(OCO_AI_PROVIDER_ENUM)
};
case CONFIG_KEYS.OCO_PROMPT_MODULE:
return {
description: 'The prompt module to use for commit message generation',
values: Object.values(OCO_PROMPT_MODULE_ENUM)
};
case CONFIG_KEYS.OCO_LANGUAGE:
return {
description: 'The locale to use for commit messages',
values: Object.keys(i18n)
};
case CONFIG_KEYS.OCO_TEST_MOCK_TYPE:
return {
description: 'The type of test mock to use',
values: ['commit-message', 'prompt-module-commitlint-config']
};
case CONFIG_KEYS.OCO_ONE_LINE_COMMIT:
return {
description: 'One line commit message',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_DESCRIPTION:
return {
description: 'Postface a message with ~3 sentences description of the changes',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_EMOJI:
return {
description: 'Preface a message with GitMoji',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_WHY:
return {
description: 'Output a short description of why the changes were done after the commit message (default: false)',
values: ['true', 'false']
}
case CONFIG_KEYS.OCO_OMIT_SCOPE:
return {
description: 'Do not include a scope in the commit message',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_GITPUSH:
return {
description: 'Push to git after commit (deprecated). If false, oco will exit after committing',
values: ['true', 'false']
};
case CONFIG_KEYS.OCO_TOKENS_MAX_INPUT:
return {
description: 'Max model token limit',
values: ['Any positive integer']
};
case CONFIG_KEYS.OCO_TOKENS_MAX_OUTPUT:
return {
description: 'Max response tokens',
values: ['Any positive integer']
};
case CONFIG_KEYS.OCO_API_KEY:
return {
description: 'API key for the selected provider',
values: ['String (required for most providers)']
};
case CONFIG_KEYS.OCO_API_URL:
return {
description: 'Custom API URL - may be used to set proxy path to OpenAI API',
values: ["URL string (must start with 'http://' or 'https://')"]
};
case CONFIG_KEYS.OCO_MESSAGE_TEMPLATE_PLACEHOLDER:
return {
description: 'Message template placeholder',
values: ["String (must start with $)"]
};
default:
return {
description: 'String value',
values: ['Any string']
};
}
}
function printConfigKeyHelp(param) {
if (!Object.values(CONFIG_KEYS).includes(param)) {
console.log(chalk.red(`Unknown config parameter: ${param}`));
return;
}
const details = getConfigKeyDetails(param as CONFIG_KEYS);
let desc = details.description;
let defaultValue = undefined;
if (param in DEFAULT_CONFIG) {
defaultValue = DEFAULT_CONFIG[param];
}
console.log(chalk.bold(`\n${param}:`));
console.log(chalk.gray(` Description: ${desc}`));
if (defaultValue !== undefined) {
// Print booleans and numbers as-is, strings without quotes
if (typeof defaultValue === 'string') {
console.log(chalk.gray(` Default: ${defaultValue}`));
} else {
console.log(chalk.gray(` Default: ${defaultValue}`));
}
}
if (Array.isArray(details.values)) {
console.log(chalk.gray(' Accepted values:'));
details.values.forEach(value => {
console.log(chalk.gray(` - ${value}`));
});
} else {
console.log(chalk.gray(' Accepted values by provider:'));
Object.entries(details.values).forEach(([provider, values]) => {
console.log(chalk.gray(` ${provider}:`));
(values as string[]).forEach(value => {
console.log(chalk.gray(` - ${value}`));
});
});
}
}
function printAllConfigHelp() {
console.log(chalk.bold('Available config parameters:'));
for (const key of Object.values(CONFIG_KEYS).sort()) {
const details = getConfigKeyDetails(key);
// Try to get the default value from DEFAULT_CONFIG
let defaultValue = undefined;
if (key in DEFAULT_CONFIG) {
defaultValue = DEFAULT_CONFIG[key];
}
console.log(chalk.bold(`\n${key}:`));
console.log(chalk.gray(` Description: ${details.description}`));
if (defaultValue !== undefined) {
if (typeof defaultValue === 'string') {
console.log(chalk.gray(` Default: ${defaultValue}`));
} else {
console.log(chalk.gray(` Default: ${defaultValue}`));
}
}
}
console.log(chalk.yellow('\nUse "oco config describe [PARAMETER]" to see accepted values and more details for a specific config parameter.'));
}
export const configCommand = command(
{
name: COMMANDS.config,
parameters: ['<mode>', '<key=values...>']
parameters: ['<mode>', '[key=values...]'],
help: {
description: 'Configure opencommit settings',
examples: [
'Describe all config parameters: oco config describe',
'Describe a specific parameter: oco config describe OCO_MODEL',
'Get a config value: oco config get OCO_MODEL',
'Set a config value: oco config set OCO_MODEL=gpt-4'
]
}
},
async (argv) => {
intro('opencommit — config');
try {
const { mode, keyValues } = argv._;
intro(`COMMAND: config ${mode} ${keyValues}`);
if (mode === CONFIG_MODES.get) {
if (mode === CONFIG_MODES.describe) {
if (!keyValues || keyValues.length === 0) {
printAllConfigHelp();
} else {
for (const key of keyValues) {
printConfigKeyHelp(key);
}
}
process.exit(0);
} else if (mode === CONFIG_MODES.get) {
if (!keyValues || keyValues.length === 0) {
throw new Error('No config keys specified for get mode');
}
const config = getConfig() || {};
for (const key of keyValues) {
outro(`${key}=${config[key as keyof typeof config]}`);
}
} else if (mode === CONFIG_MODES.set) {
if (!keyValues || keyValues.length === 0) {
throw new Error('No config keys specified for set mode');
}
await setConfig(
keyValues.map((keyValue) => keyValue.split('=') as [string, string])
);
} else {
throw new Error(
`Unsupported mode: ${mode}. Valid modes are: "set" and "get"`
`Unsupported mode: ${mode}. Valid modes are: "set", "get", and "describe"`
);
}
} catch (error) {

View File

@@ -1,11 +1,11 @@
import { intro, outro } from '@clack/prompts';
import chalk from 'chalk';
import { command } from 'cleye';
import { existsSync } from 'fs';
import fs from 'fs/promises';
import path from 'path';
import { command } from 'cleye';
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';
import { COMMANDS } from './ENUMS';
const HOOK_NAME = 'prepare-commit-msg';
const DEFAULT_SYMLINK_URL = path.join('.git', 'hooks', HOOK_NAME);
@@ -92,7 +92,7 @@ export const hookCommand = command(
}
throw new Error(
`Unsupported mode: ${mode}. Supported modes are: 'set' or 'unset'`
`Unsupported mode: ${mode}. Supported modes are: 'set' or 'unset'. Run: \`oco hook set\``
);
} catch (error) {
outro(`${chalk.red('✖')} ${error}`);

View File

@@ -1,9 +1,11 @@
import fs from 'fs/promises';
import chalk from 'chalk';
import fs from 'fs/promises';
import { intro, outro, spinner } from '@clack/prompts';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
import { getChangedFiles, getDiff, getStagedFiles, gitAdd } from '../utils/git';
import { getConfig } from './config';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
const [messageFilePath, commitSource] = process.argv.slice(2);
@@ -24,7 +26,7 @@ export const prepareCommitMessageHook = async (
if (changedFiles) await gitAdd({ files: changedFiles });
else {
outro('No changes detected, write some code and run `oc` again');
outro('No changes detected, write some code and run `oco` again');
process.exit(1);
}
}
@@ -37,10 +39,11 @@ export const prepareCommitMessageHook = async (
const config = getConfig();
if (!config?.OCO_OPENAI_API_KEY) {
throw new Error(
'No OPEN_AI_API exists. Set your OPEN_AI_API=<key> in ~/.opencommit'
if (!config.OCO_API_KEY) {
outro(
'No OCO_API_KEY is set. Set your key via `oco config set OCO_API_KEY=<value>. For more info see https://github.com/di-sukharev/opencommit'
);
return;
}
const spin = spinner();

31
src/engine/Engine.ts Normal file
View File

@@ -0,0 +1,31 @@
import AnthropicClient from '@anthropic-ai/sdk';
import { OpenAIClient as AzureOpenAIClient } from '@azure/openai';
import { GoogleGenerativeAI as GeminiClient } from '@google/generative-ai';
import { AxiosInstance as RawAxiosClient } from 'axios';
import { OpenAI as OpenAIClient } from 'openai';
import { Mistral as MistralClient } from '@mistralai/mistralai';
export interface AiEngineConfig {
apiKey: string;
model: string;
maxTokensOutput: number;
maxTokensInput: number;
baseURL?: string;
customHeaders?: Record<string, string>;
}
type Client =
| OpenAIClient
| AzureOpenAIClient
| AnthropicClient
| RawAxiosClient
| GeminiClient
| MistralClient;
export interface AiEngine {
config: AiEngineConfig;
client: Client;
generateCommitMessage(
messages: Array<OpenAIClient.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | null | undefined>;
}

79
src/engine/anthropic.ts Normal file
View File

@@ -0,0 +1,79 @@
import AnthropicClient from '@anthropic-ai/sdk';
import {
MessageCreateParamsNonStreaming,
MessageParam
} from '@anthropic-ai/sdk/resources/messages.mjs';
import { outro } from '@clack/prompts';
import axios from 'axios';
import chalk from 'chalk';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
interface AnthropicConfig extends AiEngineConfig {}
export class AnthropicEngine implements AiEngine {
config: AnthropicConfig;
client: AnthropicClient;
constructor(config) {
this.config = config;
this.client = new AnthropicClient({ apiKey: this.config.apiKey });
}
public generateCommitMessage = async (
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> => {
const systemMessage = messages.find((msg) => msg.role === 'system')
?.content as string;
const restMessages = messages.filter(
(msg) => msg.role !== 'system'
) as MessageParam[];
const params: MessageCreateParamsNonStreaming = {
model: this.config.model,
system: systemMessage,
messages: restMessages,
temperature: 0,
top_p: 0.1,
max_tokens: this.config.maxTokensOutput
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 4)
.reduce((a, b) => a + b, 0);
if (
REQUEST_TOKENS >
this.config.maxTokensInput - this.config.maxTokensOutput
) {
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
}
const data = await this.client.messages.create(params);
const message = data?.content[0].text;
let content = message;
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const anthropicAiError = error.response.data.error;
if (anthropicAiError?.message) outro(anthropicAiError.message);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
}
throw err;
}
};
}

80
src/engine/azure.ts Normal file
View File

@@ -0,0 +1,80 @@
import {
AzureKeyCredential,
OpenAIClient as AzureOpenAIClient
} from '@azure/openai';
import { outro } from '@clack/prompts';
import axios from 'axios';
import chalk from 'chalk';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
interface AzureAiEngineConfig extends AiEngineConfig {
baseURL: string;
apiKey: string;
}
export class AzureEngine implements AiEngine {
config: AzureAiEngineConfig;
client: AzureOpenAIClient;
constructor(config: AzureAiEngineConfig) {
this.config = config;
this.client = new AzureOpenAIClient(
this.config.baseURL,
new AzureKeyCredential(this.config.apiKey)
);
}
generateCommitMessage = async (
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> => {
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 4)
.reduce((a, b) => a + b, 0);
if (
REQUEST_TOKENS >
this.config.maxTokensInput - this.config.maxTokensOutput
) {
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
}
const data = await this.client.getChatCompletions(
this.config.model,
messages
);
const message = data.choices[0].message;
if (message?.content === null) {
return undefined;
}
let content = message?.content;
return removeContentTags(content, 'think');
} catch (error) {
outro(`${chalk.red('✖')} ${this.config.model}`);
const err = error as Error;
outro(`${chalk.red('✖')} ${JSON.stringify(error)}`);
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const openAiError = error.response.data.error;
if (openAiError?.message) outro(openAiError.message);
outro(
'For help look into README https://github.com/di-sukharev/opencommit#setup'
);
}
throw err;
}
};
}

61
src/engine/deepseek.ts Normal file
View File

@@ -0,0 +1,61 @@
import axios from 'axios';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { OpenAiEngine, OpenAiConfig } from './openAi';
export interface DeepseekConfig extends OpenAiConfig {}
export class DeepseekEngine extends OpenAiEngine {
constructor(config: DeepseekConfig) {
// Call OpenAIEngine constructor with forced Deepseek baseURL
super({
...config,
baseURL: 'https://api.deepseek.com/v1'
});
}
// Identical method from OpenAiEngine, re-implemented here
public generateCommitMessage = async (
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | null> => {
const params = {
model: this.config.model,
messages,
temperature: 0,
top_p: 0.1,
max_tokens: this.config.maxTokensOutput
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 4)
.reduce((a, b) => a + b, 0);
if (
REQUEST_TOKENS >
this.config.maxTokensInput - this.config.maxTokensOutput
)
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
const completion = await this.client.chat.completions.create(params);
const message = completion.choices[0].message;
let content = message?.content;
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const openAiError = error.response.data.error;
if (openAiError) throw new Error(openAiError.message);
}
throw err;
}
};
}

47
src/engine/flowise.ts Normal file
View File

@@ -0,0 +1,47 @@
import axios, { AxiosInstance } from 'axios';
import { OpenAI } from 'openai';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
interface FlowiseAiConfig extends AiEngineConfig {}
export class FlowiseEngine implements AiEngine {
config: FlowiseAiConfig;
client: AxiosInstance;
constructor(config) {
this.config = config;
this.client = axios.create({
url: `${config.baseURL}/${config.apiKey}`,
headers: { 'Content-Type': 'application/json' }
});
}
async generateCommitMessage(
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> {
const gitDiff = (messages[messages.length - 1]?.content as string)
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
const payload = {
question: gitDiff,
overrideConfig: {
systemMessagePrompt: messages[0]?.content
},
history: messages.slice(1, -1)
};
try {
const response = await this.client.post('', payload);
const message = response.data;
let content = message?.text;
return removeContentTags(content, 'think');
} catch (err: any) {
const message = err.response?.data?.error ?? err.message;
throw new Error('local model issues. details: ' + message);
}
}
}

90
src/engine/gemini.ts Normal file
View File

@@ -0,0 +1,90 @@
import {
Content,
GoogleGenerativeAI,
HarmBlockThreshold,
HarmCategory,
Part
} from '@google/generative-ai';
import axios from 'axios';
import { OpenAI } from 'openai';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
interface GeminiConfig extends AiEngineConfig {}
export class GeminiEngine implements AiEngine {
config: GeminiConfig;
client: GoogleGenerativeAI;
constructor(config) {
this.client = new GoogleGenerativeAI(config.apiKey);
this.config = config;
}
async generateCommitMessage(
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> {
const systemInstruction = messages
.filter((m) => m.role === 'system')
.map((m) => m.content)
.join('\n');
const gemini = this.client.getGenerativeModel({
model: this.config.model,
systemInstruction
});
const contents = messages
.filter((m) => m.role !== 'system')
.map(
(m) =>
({
parts: [{ text: m.content } as Part],
role: m.role === 'user' ? m.role : 'model'
} as Content)
);
try {
const result = await gemini.generateContent({
contents,
safetySettings: [
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
},
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
},
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE
}
],
generationConfig: {
maxOutputTokens: this.config.maxTokensOutput,
temperature: 0,
topP: 0.1
}
});
const content = result.response.text();
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const geminiError = error.response.data.error;
if (geminiError) throw new Error(geminiError?.message);
}
throw err;
}
}
}

10
src/engine/groq.ts Normal file
View File

@@ -0,0 +1,10 @@
import { OpenAiConfig, OpenAiEngine } from './openAi';
interface GroqConfig extends OpenAiConfig {}
export class GroqEngine extends OpenAiEngine {
constructor(config: GroqConfig) {
config.baseURL = 'https://api.groq.com/openai/v1';
super(config);
}
}

77
src/engine/mistral.ts Normal file
View File

@@ -0,0 +1,77 @@
import axios from 'axios';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
// Using any for Mistral types to avoid type declaration issues
export interface MistralAiConfig extends AiEngineConfig {}
export type MistralCompletionMessageParam = Array<any>;
// Import Mistral dynamically to avoid TS errors
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Mistral = require('@mistralai/mistralai').Mistral;
export class MistralAiEngine implements AiEngine {
config: MistralAiConfig;
client: any; // Using any type for Mistral client to avoid TS errors
constructor(config: MistralAiConfig) {
this.config = config;
if (!config.baseURL) {
this.client = new Mistral({ apiKey: config.apiKey });
} else {
this.client = new Mistral({ apiKey: config.apiKey, serverURL: config.baseURL });
}
}
public generateCommitMessage = async (
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | null> => {
const params = {
model: this.config.model,
messages: messages as MistralCompletionMessageParam,
topP: 0.1,
maxTokens: this.config.maxTokensOutput
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 4)
.reduce((a, b) => a + b, 0);
if (
REQUEST_TOKENS >
this.config.maxTokensInput - this.config.maxTokensOutput
)
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
const completion = await this.client.chat.complete(params);
if (!completion.choices)
throw Error('No completion choice available.')
const message = completion.choices[0].message;
if (!message || !message.content)
throw Error('No completion choice available.')
let content = message.content as string;
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const mistralError = error.response.data.error;
if (mistralError) throw new Error(mistralError.message);
}
throw err;
}
};
}

47
src/engine/mlx.ts Normal file
View File

@@ -0,0 +1,47 @@
import axios, { AxiosInstance } from 'axios';
import { OpenAI } from 'openai';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
interface MLXConfig extends AiEngineConfig {}
export class MLXEngine implements AiEngine {
config: MLXConfig;
client: AxiosInstance;
constructor(config) {
this.config = config;
this.client = axios.create({
url: config.baseURL
? `${config.baseURL}/${config.apiKey}`
: 'http://localhost:8080/v1/chat/completions',
headers: { 'Content-Type': 'application/json' }
});
}
async generateCommitMessage(
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>):
Promise<string | undefined> {
const params = {
messages,
temperature: 0,
top_p: 0.1,
repetition_penalty: 1.5,
stream: false
};
try {
const response = await this.client.post(
this.client.getUri(this.config),
params
);
const choices = response.data.choices;
const message = choices[0].message;
let content = message?.content;
return removeContentTags(content, 'think');
} catch (err: any) {
const message = err.response?.data?.error ?? err.message;
throw new Error(`MLX provider error: ${message}`);
}
}
}

52
src/engine/ollama.ts Normal file
View File

@@ -0,0 +1,52 @@
import axios, { AxiosInstance } from 'axios';
import { OpenAI } from 'openai';
import { removeContentTags } from '../utils/removeContentTags';
import { AiEngine, AiEngineConfig } from './Engine';
interface OllamaConfig extends AiEngineConfig {}
export class OllamaEngine implements AiEngine {
config: OllamaConfig;
client: AxiosInstance;
constructor(config) {
this.config = config;
// Combine base headers with custom headers
const headers = {
'Content-Type': 'application/json',
...config.customHeaders
};
this.client = axios.create({
url: config.baseURL
? `${config.baseURL}/${config.apiKey}`
: 'http://localhost:11434/api/chat',
headers
});
}
async generateCommitMessage(
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> {
const params = {
model: this.config.model ?? 'mistral',
messages,
options: { temperature: 0, top_p: 0.1 },
stream: false
};
try {
const response = await this.client.post(
this.client.getUri(this.config),
params
);
const { message } = response.data;
let content = message?.content;
return removeContentTags(content, 'think');
} catch (err: any) {
const message = err.response?.data?.error ?? err.message;
throw new Error(`Ollama provider error: ${message}`);
}
}
}

77
src/engine/openAi.ts Normal file
View File

@@ -0,0 +1,77 @@
import axios from 'axios';
import { OpenAI } from 'openai';
import { GenerateCommitMessageErrorEnum } from '../generateCommitMessageFromGitDiff';
import { parseCustomHeaders } from '../utils/engine';
import { removeContentTags } from '../utils/removeContentTags';
import { tokenCount } from '../utils/tokenCount';
import { AiEngine, AiEngineConfig } from './Engine';
export interface OpenAiConfig extends AiEngineConfig {}
export class OpenAiEngine implements AiEngine {
config: OpenAiConfig;
client: OpenAI;
constructor(config: OpenAiConfig) {
this.config = config;
const clientOptions: OpenAI.ClientOptions = {
apiKey: config.apiKey
};
if (config.baseURL) {
clientOptions.baseURL = config.baseURL;
}
if (config.customHeaders) {
const headers = parseCustomHeaders(config.customHeaders);
if (Object.keys(headers).length > 0) {
clientOptions.defaultHeaders = headers;
}
}
this.client = new OpenAI(clientOptions);
}
public generateCommitMessage = async (
messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | null> => {
const params = {
model: this.config.model,
messages,
temperature: 0,
top_p: 0.1,
max_tokens: this.config.maxTokensOutput
};
try {
const REQUEST_TOKENS = messages
.map((msg) => tokenCount(msg.content as string) + 4)
.reduce((a, b) => a + b, 0);
if (
REQUEST_TOKENS >
this.config.maxTokensInput - this.config.maxTokensOutput
)
throw new Error(GenerateCommitMessageErrorEnum.tooMuchTokens);
const completion = await this.client.chat.completions.create(params);
const message = completion.choices[0].message;
let content = message?.content;
return removeContentTags(content, 'think');
} catch (error) {
const err = error as Error;
if (
axios.isAxiosError<{ error?: { message: string } }>(error) &&
error.response?.status === 401
) {
const openAiError = error.response.data.error;
if (openAiError) throw new Error(openAiError.message);
}
throw err;
}
};
}

47
src/engine/testAi.ts Normal file
View File

@@ -0,0 +1,47 @@
import { OpenAI } from 'openai';
import { AiEngine } from './Engine';
export const TEST_MOCK_TYPES = [
'commit-message',
'prompt-module-commitlint-config'
] as const;
export type TestMockType = (typeof TEST_MOCK_TYPES)[number];
type TestAiEngine = Partial<AiEngine> & {
mockType: TestMockType;
};
export class TestAi implements TestAiEngine {
mockType: TestMockType;
// those are not used in the test engine
config: any;
client: any;
// ---
constructor(mockType: TestMockType) {
this.mockType = mockType;
}
async generateCommitMessage(
_messages: Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>
): Promise<string | undefined> {
switch (this.mockType) {
case 'commit-message':
return 'fix(testAi.ts): test commit message';
case 'prompt-module-commitlint-config':
return (
`{\n` +
` "localLanguage": "english",\n` +
` "commitFix": "fix(server): Change 'port' variable to uppercase 'PORT'",\n` +
` "commitFeat": "feat(server): Allow server to listen on a port specified through environment variable",\n` +
` "commitDescription": "Change 'port' variable to uppercase 'PORT'. Allow server to listen on a port specified through environment variable."\n` +
`}`
);
default:
throw Error('unsupported test mock type');
}
}
}

View File

@@ -1,67 +1,25 @@
import {
ChatCompletionRequestMessage,
ChatCompletionRequestMessageRoleEnum
} from 'openai';
import { api } from './api';
import { getConfig } from './commands/config';
import { OpenAI } from 'openai';
import { DEFAULT_TOKEN_LIMITS, getConfig } from './commands/config';
import { getMainCommitPrompt } from './prompts';
import { getEngine } from './utils/engine';
import { mergeDiffs } from './utils/mergeDiffs';
import { i18n, I18nLocals } from './i18n';
import { tokenCount } from './utils/tokenCount';
const config = getConfig();
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
const MAX_TOKENS_INPUT = config.OCO_TOKENS_MAX_INPUT;
const MAX_TOKENS_OUTPUT = config.OCO_TOKENS_MAX_OUTPUT;
const INIT_MESSAGES_PROMPT: Array<ChatCompletionRequestMessage> = [
{
role: ChatCompletionRequestMessageRoleEnum.System,
// 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 WHAT were the changes and WHY the changes were done. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
${config?.OCO_EMOJI ? 'Use GitMoji convention to preface the commit.': 'Do not preface the commit with anything.'}
${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."}
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}\`);
});`
},
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
}
];
const generateCommitMessageChatCompletionPrompt = async (
diff: string,
fullGitMojiSpec: boolean,
context: string
): Promise<Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>> => {
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(fullGitMojiSpec, context);
const generateCommitMessageChatCompletionPrompt = (
diff: string
): Array<ChatCompletionRequestMessage> => {
const chatContextAsCompletionRequest = [...INIT_MESSAGES_PROMPT];
chatContextAsCompletionRequest.push({
role: ChatCompletionRequestMessageRoleEnum.User,
role: 'user',
content: diff
});
@@ -71,42 +29,62 @@ const generateCommitMessageChatCompletionPrompt = (
export enum GenerateCommitMessageErrorEnum {
tooMuchTokens = 'TOO_MUCH_TOKENS',
internalError = 'INTERNAL_ERROR',
emptyMessage = 'EMPTY_MESSAGE'
emptyMessage = 'EMPTY_MESSAGE',
outputTokensTooHigh = `Token limit exceeded, OCO_TOKENS_MAX_OUTPUT must not be much higher than the default ${DEFAULT_TOKEN_LIMITS.DEFAULT_MAX_TOKENS_OUTPUT} tokens.`
}
interface GenerateCommitMessageError {
error: GenerateCommitMessageErrorEnum;
}
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => tokenCount(msg.content) + 4
).reduce((a, b) => a + b, 0);
const MAX_REQ_TOKENS = 3000 - INIT_MESSAGES_PROMPT_LENGTH;
const ADJUSTMENT_FACTOR = 20;
export const generateCommitMessageByDiff = async (
diff: string
diff: string,
fullGitMojiSpec: boolean = false,
context: string = ""
): Promise<string> => {
try {
if (tokenCount(diff) >= MAX_REQ_TOKENS) {
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
const INIT_MESSAGES_PROMPT = await getMainCommitPrompt(
fullGitMojiSpec,
context
);
const INIT_MESSAGES_PROMPT_LENGTH = INIT_MESSAGES_PROMPT.map(
(msg) => tokenCount(msg.content as string) + 4
).reduce((a, b) => a + b, 0);
const MAX_REQUEST_TOKENS =
MAX_TOKENS_INPUT -
ADJUSTMENT_FACTOR -
INIT_MESSAGES_PROMPT_LENGTH -
MAX_TOKENS_OUTPUT;
if (tokenCount(diff) >= MAX_REQUEST_TOKENS) {
const commitMessagePromises = await getCommitMsgsPromisesFromFileDiffs(
diff,
MAX_REQ_TOKENS
MAX_REQUEST_TOKENS,
fullGitMojiSpec
);
const commitMessages = await Promise.all(commitMessagePromises);
const commitMessages = [] as string[];
for (const promise of commitMessagePromises) {
commitMessages.push((await promise) as string);
await delay(2000);
}
return commitMessages.join('\n\n');
} else {
const messages = generateCommitMessageChatCompletionPrompt(diff);
const commitMessage = await api.generateCommitMessage(messages);
if (!commitMessage)
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
}
const messages = await generateCommitMessageChatCompletionPrompt(
diff,
fullGitMojiSpec,
context,
);
const engine = getEngine();
const commitMessage = await engine.generateCommitMessage(messages);
if (!commitMessage)
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
} catch (error) {
throw error;
}
@@ -115,7 +93,8 @@ export const generateCommitMessageByDiff = async (
function getMessagesPromisesByChangesInFile(
fileDiff: string,
separator: string,
maxChangeLength: number
maxChangeLength: number,
fullGitMojiSpec: boolean
) {
const hunkHeaderSeparator = '@@ ';
const [fileHeader, ...fileDiffByLines] = fileDiff.split(hunkHeaderSeparator);
@@ -126,25 +105,74 @@ function getMessagesPromisesByChangesInFile(
maxChangeLength
);
const lineDiffsWithHeader = mergedChanges.map(
(change) => fileHeader + change
const lineDiffsWithHeader = [] as string[];
for (const change of mergedChanges) {
const totalChange = fileHeader + change;
if (tokenCount(totalChange) > maxChangeLength) {
// If the totalChange is too large, split it into smaller pieces
const splitChanges = splitDiff(totalChange, maxChangeLength);
lineDiffsWithHeader.push(...splitChanges);
} else {
lineDiffsWithHeader.push(totalChange);
}
}
const engine = getEngine();
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map(
async (lineDiff) => {
const messages = await generateCommitMessageChatCompletionPrompt(
separator + lineDiff,
fullGitMojiSpec
);
return engine.generateCommitMessage(messages);
}
);
const commitMsgsFromFileLineDiffs = lineDiffsWithHeader.map((lineDiff) => {
const messages = generateCommitMessageChatCompletionPrompt(
separator + lineDiff
);
return api.generateCommitMessage(messages);
});
return commitMsgsFromFileLineDiffs;
}
export function getCommitMsgsPromisesFromFileDiffs(
function splitDiff(diff: string, maxChangeLength: number) {
const lines = diff.split('\n');
const splitDiffs = [] as string[];
let currentDiff = '';
if (maxChangeLength <= 0) {
throw new Error(GenerateCommitMessageErrorEnum.outputTokensTooHigh);
}
for (let line of lines) {
// If a single line exceeds maxChangeLength, split it into multiple lines
while (tokenCount(line) > maxChangeLength) {
const subLine = line.substring(0, maxChangeLength);
line = line.substring(maxChangeLength);
splitDiffs.push(subLine);
}
// Check the tokenCount of the currentDiff and the line separately
if (tokenCount(currentDiff) + tokenCount('\n' + line) > maxChangeLength) {
// If adding the next line would exceed the maxChangeLength, start a new diff
splitDiffs.push(currentDiff);
currentDiff = line;
} else {
// Otherwise, add the line to the current diff
currentDiff += '\n' + line;
}
}
// Add the last diff
if (currentDiff) {
splitDiffs.push(currentDiff);
}
return splitDiffs;
}
export const getCommitMsgsPromisesFromFileDiffs = async (
diff: string,
maxDiffLength: number
) {
maxDiffLength: number,
fullGitMojiSpec: boolean
) => {
const separator = 'diff --git ';
const diffByFiles = diff.split(separator).slice(1);
@@ -152,7 +180,7 @@ export function getCommitMsgsPromisesFromFileDiffs(
// merge multiple files-diffs into 1 prompt to save tokens
const mergedFilesDiffs = mergeDiffs(diffByFiles, maxDiffLength);
const commitMessagePromises = [];
const commitMessagePromises = [] as Promise<string | null | undefined>[];
for (const fileDiff of mergedFilesDiffs) {
if (tokenCount(fileDiff) >= maxDiffLength) {
@@ -160,17 +188,25 @@ export function getCommitMsgsPromisesFromFileDiffs(
const messagesPromises = getMessagesPromisesByChangesInFile(
fileDiff,
separator,
maxDiffLength
maxDiffLength,
fullGitMojiSpec
);
commitMessagePromises.push(...messagesPromises);
} else {
const messages = generateCommitMessageChatCompletionPrompt(
separator + fileDiff
const messages = await generateCommitMessageChatCompletionPrompt(
separator + fileDiff,
fullGitMojiSpec
);
commitMessagePromises.push(api.generateCommitMessage(messages));
const engine = getEngine();
commitMessagePromises.push(engine.generateCommitMessage(messages));
}
}
return commitMessagePromises;
};
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -1,12 +1,12 @@
import core from '@actions/core';
import github from '@actions/github';
import exec from '@actions/exec';
import github from '@actions/github';
import { intro, outro } from '@clack/prompts';
import { PushEvent } from '@octokit/webhooks-types';
import { generateCommitMessageByDiff } from './generateCommitMessageFromGitDiff';
import { sleep } from './utils/sleep';
import { randomIntFromInterval } from './utils/randomIntFromInterval';
import { unlinkSync, writeFileSync } from 'fs';
import { generateCommitMessageByDiff } from './generateCommitMessageFromGitDiff';
import { randomIntFromInterval } from './utils/randomIntFromInterval';
import { sleep } from './utils/sleep';
// This should be a token with access to your repository scoped in as a secret.
// The YML workflow will need to set GITHUB_TOKEN with the GitHub Secret Token
@@ -52,7 +52,7 @@ async function improveMessagesInChunks(diffsAndSHAs: DiffAndSHA[]) {
const chunkSize = diffsAndSHAs!.length % 2 === 0 ? 4 : 3;
outro(`Improving commit messages in chunks of ${chunkSize}.`);
const improvePromises = diffsAndSHAs!.map((commit) =>
generateCommitMessageByDiff(commit.diff)
generateCommitMessageByDiff(commit.diff, false)
);
let improvedMessagesAndSHAs: MsgAndSHA[] = [];
@@ -133,6 +133,16 @@ async function improveCommitMessages(
improvedMessagesWithSHAs
);
// Check if there are actually any changes in the commit messages
const messagesChanged = improvedMessagesWithSHAs.some(
({ sha, msg }, index) => msg !== commitsToImprove[index].message
);
if (!messagesChanged) {
console.log('No changes in commit messages detected, skipping rebase');
return;
}
const createCommitMessageFile = (message: string, index: number) =>
writeFileSync(`./commit-${index}.txt`, message);
improvedMessagesWithSHAs.forEach(({ msg }, i) =>

View File

@@ -2,5 +2,7 @@
"localLanguage": "česky",
"commitFix": "fix(server.ts): zlepšení velikosti proměnné port na velká písmena PORT",
"commitFeat": "feat(server.ts): přidání podpory pro proměnnou prostředí process.env.PORT",
"commitDescription": "Proměnná port se nyní jmenuje PORT, což odpovídá konvenci pojmenování, protože PORT je konstanta. Podpora proměnné prostředí process.env.PORT umožňuje snadnější správu nastavení při spuštění."
"commitDescription": "Proměnná port se nyní jmenuje PORT, což odpovídá konvenci pojmenování, protože PORT je konstanta. Podpora proměnné prostředí process.env.PORT umožňuje snadnější správu nastavení při spuštění.",
"commitFixOmitScope": "fix: zlepšení velikosti proměnné port na velká písmena PORT",
"commitFeatOmitScope": "feat: přidání podpory pro proměnnou prostředí process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "Deutsch",
"commitFix": "fix(server.ts): Ändere die Groß- und Kleinschreibung der Port-Variable von Kleinbuchstaben auf Großbuchstaben PORT.",
"commitFeat": "Funktion(server.ts): Unterstützung für die Umgebungsvariable process.env.PORT hinzufügen",
"commitDescription": "Die Port-Variable heißt jetzt PORT, was die Konsistenz mit den Namenskonventionen verbessert, da PORT eine Konstante ist. Die Unterstützung für eine Umgebungsvariable ermöglicht es der Anwendung, flexibler zu sein, da sie jetzt auf jedem verfügbaren Port laufen kann, der über die Umgebungsvariable process.env.PORT angegeben wird."
"commitDescription": "Die Port-Variable heißt jetzt PORT, was die Konsistenz mit den Namenskonventionen verbessert, da PORT eine Konstante ist. Die Unterstützung für eine Umgebungsvariable ermöglicht es der Anwendung, flexibler zu sein, da sie jetzt auf jedem verfügbaren Port laufen kann, der über die Umgebungsvariable process.env.PORT angegeben wird.",
"commitFixOmitScope": "fix: Ändere die Groß- und Kleinschreibung der Port-Variable von Kleinbuchstaben auf Großbuchstaben PORT.",
"commitFeatOmitScope": "Funktion: Unterstützung für die Umgebungsvariable process.env.PORT hinzufügen"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "english",
"commitFix": "fix(server.ts): change port variable case from lowercase port to uppercase PORT to improve semantics",
"commitFeat": "feat(server.ts): add support for process.env.PORT environment variable to be able to run app on a configurable port",
"commitDescription": "The port variable is now named PORT, which improves consistency with the naming conventions as PORT is a constant. Support for an environment variable allows the application to be more flexible as it can now run on any available port specified via the process.env.PORT environment variable."
"commitDescription": "The port variable is now named PORT, which improves consistency with the naming conventions as PORT is a constant. Support for an environment variable allows the application to be more flexible as it can now run on any available port specified via the process.env.PORT environment variable.",
"commitFixOmitScope": "fix: change port variable case from lowercase port to uppercase PORT to improve semantics",
"commitFeatOmitScope": "feat: add support for process.env.PORT environment variable to be able to run app on a configurable port"
}

View File

@@ -2,5 +2,7 @@
"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."
"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.",
"commitFixOmitScope": "fix: cambiar la variable port de minúsculas a mayúsculas PORT",
"commitFeatOmitScope": "feat: añadir soporte para la variable de entorno process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "française",
"commitFix": "corriger(server.ts) : changer la casse de la variable de port de minuscules à majuscules (PORT)",
"commitFeat": "fonctionnalité(server.ts) : ajouter la prise en charge de la variable d'environnement process.env.PORT",
"commitDescription": "La variable de port est maintenant nommée PORT, ce qui améliore la cohérence avec les conventions de nommage car PORT est une constante. La prise en charge d'une variable d'environnement permet à l'application d'être plus flexible car elle peut maintenant s'exécuter sur n'importe quel port disponible spécifié via la variable d'environnement process.env.PORT."
"commitDescription": "La variable de port est maintenant nommée PORT, ce qui améliore la cohérence avec les conventions de nommage car PORT est une constante. La prise en charge d'une variable d'environnement permet à l'application d'être plus flexible car elle peut maintenant s'exécuter sur n'importe quel port disponible spécifié via la variable d'environnement process.env.PORT.",
"commitFixOmitScope": "corriger : changer la casse de la variable de port de minuscules à majuscules (PORT)",
"commitFeatOmitScope": "fonctionnalité : ajouter la prise en charge de la variable d'environnement process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"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"
"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",
"commitFixOmitScope": "fix: mengubah huruf port variable dari huruf kecil ke huruf besar PORT",
"commitFeatOmitScope": "feat: menambahkan support di process.env.PORT environment variabel"
}

View File

@@ -1,22 +1,22 @@
import en from '../i18n/en.json' assert { type: 'json' };
import cs from '../i18n/cs.json' assert { type: 'json' };
import de from '../i18n/de.json' assert { type: 'json' };
import fr from '../i18n/fr.json' assert { type: 'json' };
import it from '../i18n/it.json' assert { type: 'json' };
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' };
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 tr from '../i18n/tr.json' assert { type: 'json' };
import th from '../i18n/th.json' assert { type: 'json' };
import cs from '../i18n/cs.json';
import de from '../i18n/de.json';
import en from '../i18n/en.json';
import es_ES from '../i18n/es_ES.json';
import fr from '../i18n/fr.json';
import id_ID from '../i18n/id_ID.json';
import it from '../i18n/it.json';
import ja from '../i18n/ja.json';
import ko from '../i18n/ko.json';
import nl from '../i18n/nl.json';
import pl from '../i18n/pl.json';
import pt_br from '../i18n/pt_br.json';
import ru from '../i18n/ru.json';
import sv from '../i18n/sv.json';
import th from '../i18n/th.json';
import tr from '../i18n/tr.json';
import vi_VN from '../i18n/vi_VN.json';
import zh_CN from '../i18n/zh_CN.json';
import zh_TW from '../i18n/zh_TW.json';
export enum I18nLocals {
'en' = 'en',
@@ -36,7 +36,7 @@ export enum I18nLocals {
'id_ID' = 'id_ID',
'pl' = 'pl',
'tr' = 'tr',
'th' = 'th',
'th' = 'th'
}
export const i18n = {

View File

@@ -2,5 +2,7 @@
"localLanguage": "italiano",
"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."
"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.",
"commitFixOmitScope": "fix: cambia la grafia della variabile della porta dal minuscolo port al maiuscolo PORT",
"commitFeatOmitScope": "feat: aggiunge il supporto per la variabile di ambiente process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "日本語",
"commitFix": "修正(server.ts): ポート変数を小文字のportから大文字のPORTに変更",
"commitFeat": "新機能(server.ts): 環境変数process.env.PORTのサポートを追加",
"commitDescription": "ポート変数は現在PORTという名前になり、定数であるPORTを使うことで命名規則に一貫性が生まれました。環境変数をサポートすることで、環境変数process.env.PORTで指定された任意の利用可能なポートで実行できるようになり、アプリケーションはより柔軟になりました。"
"commitDescription": "ポート変数は現在PORTという名前になり、定数であるPORTを使うことで命名規則に一貫性が生まれました。環境変数をサポートすることで、環境変数process.env.PORTで指定された任意の利用可能なポートで実行できるようになり、アプリケーションはより柔軟になりました。",
"commitFixOmitScope": "修正: ポート変数を小文字のportから大文字のPORTに変更",
"commitFeatOmitScope": "新機能: 環境変数process.env.PORTのサポートを追加"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "한국어",
"commitFix": "fix(server.ts): 포트 변수를 소문자 port에서 대문자 PORT로 변경",
"commitFeat": "피트(server.ts): process.env.PORT 환경 변수 지원 추가",
"commitDescription": "포트 변수는 이제 PORT로 이름이 지정되어 상수인 PORT와 일관성 있는 이름 규칙을 따릅니다. 환경 변수 지원을 통해 애플리케이션은 이제 process.env.PORT 환경 변수로 지정된 사용 가능한 모든 포트에서 실행할 수 있으므로 더 유연해졌습니다."
"commitDescription": "포트 변수는 이제 PORT로 이름이 지정되어 상수인 PORT와 일관성 있는 이름 규칙을 따릅니다. 환경 변수 지원을 통해 애플리케이션은 이제 process.env.PORT 환경 변수로 지정된 사용 가능한 모든 포트에서 실행할 수 있으므로 더 유연해졌습니다.",
"commitFixOmitScope": "fix: 포트 변수를 소문자 port에서 대문자 PORT로 변경",
"commitFeatOmitScope": "피트: process.env.PORT 환경 변수 지원 추가"
}

View File

@@ -2,5 +2,7 @@
"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."
"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.",
"commitFixOmitScope": "fix: verander poortvariabele van kleine letters poort naar hoofdletters PORT",
"commitFeatOmitScope": "feat: voeg ondersteuning toe voor process.env.PORT omgevingsvariabele"
}

View File

@@ -2,5 +2,7 @@
"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."
"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.",
"commitFixOmitScope": "fix: poprawa wielkości zmiennej port na pisane z dużymi literami PORT",
"commitFeatOmitScope": "feat: dodanie obsługi zmiennej środowiskowej process.env.PORT"
}

View File

@@ -2,5 +2,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."
"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.",
"commitFixOmitScope": "fix: altera o caso da variável de porta de port minúscula para PORT maiúscula",
"commitFeatOmitScope": "feat: adiciona suporte para a variável de ambiente process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "русский",
"commitFix": "fix(server.ts): изменение регистра переменной порта с нижнего регистра port на верхний регистр PORT",
"commitFeat": "feat(server.ts): добавлена поддержка переменной окружения process.env.PORT",
"commitDescription": "Переменная port теперь называется PORT, что улучшает согласованность с соглашениями об именовании констант. Поддержка переменной окружения позволяет приложению быть более гибким, запускаясь на любом доступном порту, указанном с помощью переменной окружения process.env.PORT."
"commitDescription": "Переменная port теперь называется PORT, что улучшает согласованность с соглашениями об именовании констант. Поддержка переменной окружения позволяет приложению быть более гибким, запускаясь на любом доступном порту, указанном с помощью переменной окружения process.env.PORT.",
"commitFixOmitScope": "fix: изменение регистра переменной порта с нижнего регистра port на верхний регистр PORT",
"commitFeatOmitScope": "feat: добавлена поддержка переменной окружения process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"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."
"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.",
"commitFixOmitScope": "fixa: ändra variabelnamnet för port från små bokstäver till stora bokstäver PORT",
"commitFeatOmitScope": "nyhet: lägg till stöd för process.env.PORT miljövariabel"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "ไทย",
"commitFix": "fix(server.ts): เปลี่ยนตัวพิมพ์ของตัวแปร จากตัวพิมพ์เล็ก port เป็นตัวพิมพ์ใหญ่ PORT",
"commitFeat": "feat(server.ts): เพิ่มการรองรับสำหรับตัวแปรสภาพแวดล้อม process.env.PORT",
"commitDescription": "ตอนนี้ตัวแปรพอร์ตมีชื่อว่า PORT, ซึ่งปรับปรุงความสอดคล้องกับหลักการตั้งชื่อเนื่องจาก PORT เป็นค่าคงที่. การสนับสนุนสำหรับตัวแปรสภาพแวดล้อม ช่วยให้แอปพลิเคชันมีความยืดหยุ่นมากขึ้นเนื่องจาก สามารถทำงานบนพอร์ตใด ๆ ตามที่กำหนด ซึ่งระบุผ่านตัวแปรสภาพแวดล้อม process.env.PORT"
"commitDescription": "ตอนนี้ตัวแปรพอร์ตมีชื่อว่า PORT, ซึ่งปรับปรุงความสอดคล้องกับหลักการตั้งชื่อเนื่องจาก PORT เป็นค่าคงที่. การสนับสนุนสำหรับตัวแปรสภาพแวดล้อม ช่วยให้แอปพลิเคชันมีความยืดหยุ่นมากขึ้นเนื่องจาก สามารถทำงานบนพอร์ตใด ๆ ตามที่กำหนด ซึ่งระบุผ่านตัวแปรสภาพแวดล้อม process.env.PORT",
"commitFixOmitScope": "fix: เปลี่ยนตัวพิมพ์ของตัวแปร จากตัวพิมพ์เล็ก port เป็นตัวพิมพ์ใหญ่ PORT",
"commitFeatOmitScope": "feat: เพิ่มการรองรับสำหรับตัวแปรสภาพแวดล้อม process.env.PORT"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "Turkish",
"commitFix": "fix(server.ts): port değişkeni küçük harfli porttan büyük harfli PORT'a değiştirildi",
"commitFeat": "feat(server.ts): process.env.PORT ortam değişkeni için destek eklendi.",
"commitDescription": "Bağlantı noktası değişkeni artık PORT olarak adlandırıldı ve PORT bir sabit değişken olduğu için bu adlandırma tutarlılığı artırır. Ortam değişkeni desteği, artık process.env.PORT ortam değişkeni aracılığıyla belirtilen herhangi bir kullanılabilir bağlantı noktasında çalışabileceğinden uygulamanın daha esnek olmasını sağlar."
"commitDescription": "Bağlantı noktası değişkeni artık PORT olarak adlandırıldı ve PORT bir sabit değişken olduğu için bu adlandırma tutarlılığı artırır. Ortam değişkeni desteği, artık process.env.PORT ortam değişkeni aracılığıyla belirtilen herhangi bir kullanılabilir bağlantı noktasında çalışabileceğinden uygulamanın daha esnek olmasını sağlar.",
"commitFixOmitScope": "fix: port değişkeni küçük harfli porttan büyük harfli PORT'a değiştirildi",
"commitFeatOmitScope": "feat: process.env.PORT ortam değişkeni için destek eklendi."
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "vietnamese",
"commitFix": "fix(server.ts): thay đổi chữ viết thường của biến port thành chữ viết hoa PORT",
"commitFeat": "feat(server.ts): thêm hỗ trợ cho biến môi trường process.env.PORT",
"commitDescription": "Biến port đã được đổi tên thành PORT, giúp cải thiện tính nhất quán trong việc đặt tên theo quy ước vì PORT là một hằng số. Hỗ trợ cho biến môi trường cho phép ứng dụng linh hoạt hơn khi có thể chạy trên bất kỳ cổng nào được chỉ định thông qua biến môi trường process.env.PORT."
"commitDescription": "Biến port đã được đổi tên thành PORT, giúp cải thiện tính nhất quán trong việc đặt tên theo quy ước vì PORT là một hằng số. Hỗ trợ cho biến môi trường cho phép ứng dụng linh hoạt hơn khi có thể chạy trên bất kỳ cổng nào được chỉ định thông qua biến môi trường process.env.PORT.",
"commitFixOmitScope": "fix: thay đổi chữ viết thường của biến port thành chữ viết hoa PORT",
"commitFeatOmitScope": "feat: thêm hỗ trợ cho biến môi trường process.env.PORT"
}

View File

@@ -1,6 +1,8 @@
{
"localLanguage": "简体中文",
"commitFix": "修复(server.ts)将端口变量从小写port改为大写PORT",
"commitFeat": "功能(server.ts)添加对process.env.PORT环境变量的支持",
"commitDescription": "现在端口变量被命名为PORT这提高了命名约定的一致性因为PORT是一个常量。环境变量的支持使应用程序更加灵活因为它现在可以通过process.env.PORT环境变量在任何可用端口上运行。"
"commitFix": "fix(server.ts)将端口变量从小写port改为大写PORT",
"commitFeat": "feat(server.ts)添加对process.env.PORT环境变量的支持",
"commitDescription": "现在端口变量被命名为PORT这提高了命名约定的一致性因为PORT是一个常量。环境变量的支持使应用程序更加灵活因为它现在可以通过process.env.PORT环境变量在任何可用端口上运行。",
"commitFixOmitScope": "fix将端口变量从小写port改为大写PORT",
"commitFeatOmitScope": "feat添加对process.env.PORT环境变量的支持"
}

View File

@@ -2,5 +2,7 @@
"localLanguage": "繁體中文",
"commitFix": "修正(server.ts)將端口變數從小寫端口改為大寫PORT",
"commitFeat": "功能(server.ts)新增對process.env.PORT環境變數的支援",
"commitDescription": "現在port變數已更名為PORT以符合命名慣例因為PORT是一個常量。支援環境變數可以使應用程序更靈活因為它現在可以通過process.env.PORT環境變數運行在任何可用端口上。"
"commitDescription": "現在port變數已更名為PORT以符合命名慣例因為PORT是一個常量。支援環境變數可以使應用程序更靈活因為它現在可以通過process.env.PORT環境變數運行在任何可用端口上。",
"commitFixOmitScope": "修正將端口變數從小寫端口改為大寫PORT",
"commitFeatOmitScope": "功能新增對process.env.PORT環境變數的支援"
}

View File

@@ -0,0 +1,45 @@
import {
CONFIG_KEYS,
getConfig,
OCO_AI_PROVIDER_ENUM,
setConfig
} from '../commands/config';
export default function () {
const config = getConfig({ setDefaultValues: false });
const aiProvider = config.OCO_AI_PROVIDER;
let apiKey: string | undefined;
let apiUrl: string | undefined;
if (aiProvider === OCO_AI_PROVIDER_ENUM.OLLAMA) {
apiKey = config['OCO_OLLAMA_API_KEY'];
apiUrl = config['OCO_OLLAMA_API_URL'];
} else if (aiProvider === OCO_AI_PROVIDER_ENUM.ANTHROPIC) {
apiKey = config['OCO_ANTHROPIC_API_KEY'];
apiUrl = config['OCO_ANTHROPIC_BASE_PATH'];
} else if (aiProvider === OCO_AI_PROVIDER_ENUM.OPENAI) {
apiKey = config['OCO_OPENAI_API_KEY'];
apiUrl = config['OCO_OPENAI_BASE_PATH'];
} else if (aiProvider === OCO_AI_PROVIDER_ENUM.AZURE) {
apiKey = config['OCO_AZURE_API_KEY'];
apiUrl = config['OCO_AZURE_ENDPOINT'];
} else if (aiProvider === OCO_AI_PROVIDER_ENUM.GEMINI) {
apiKey = config['OCO_GEMINI_API_KEY'];
apiUrl = config['OCO_GEMINI_BASE_PATH'];
} else if (aiProvider === OCO_AI_PROVIDER_ENUM.FLOWISE) {
apiKey = config['OCO_FLOWISE_API_KEY'];
apiUrl = config['OCO_FLOWISE_ENDPOINT'];
} else {
throw new Error(
`Migration failed, set AI provider first. Run "oco config set OCO_AI_PROVIDER=<provider>", where <provider> is one of: ${Object.values(
OCO_AI_PROVIDER_ENUM
).join(', ')}`
);
}
if (apiKey) setConfig([[CONFIG_KEYS.OCO_API_KEY, apiKey]]);
if (apiUrl) setConfig([[CONFIG_KEYS.OCO_API_URL, apiUrl]]);
}

View File

@@ -0,0 +1,26 @@
import { getGlobalConfig, setGlobalConfig } from '../commands/config';
export default function () {
const obsoleteKeys = [
'OCO_OLLAMA_API_KEY',
'OCO_OLLAMA_API_URL',
'OCO_ANTHROPIC_API_KEY',
'OCO_ANTHROPIC_BASE_PATH',
'OCO_OPENAI_API_KEY',
'OCO_OPENAI_BASE_PATH',
'OCO_AZURE_API_KEY',
'OCO_AZURE_ENDPOINT',
'OCO_GEMINI_API_KEY',
'OCO_GEMINI_BASE_PATH',
'OCO_FLOWISE_API_KEY',
'OCO_FLOWISE_ENDPOINT'
];
const globalConfig = getGlobalConfig();
const configToOverride = { ...globalConfig };
for (const key of obsoleteKeys) delete configToOverride[key];
setGlobalConfig(configToOverride);
}

View File

@@ -0,0 +1,22 @@
import {
ConfigType,
DEFAULT_CONFIG,
getGlobalConfig,
setConfig
} from '../commands/config';
export default function () {
const setDefaultConfigValues = (config: ConfigType) => {
const entriesToSet: [key: string, value: string | boolean | number][] = [];
for (const entry of Object.entries(DEFAULT_CONFIG)) {
const [key, _value] = entry;
if (config[key] === 'undefined' || config[key] === undefined)
entriesToSet.push(entry);
}
if (entriesToSet.length > 0) setConfig(entriesToSet);
console.log(entriesToSet);
};
setDefaultConfigValues(getGlobalConfig());
}

View File

@@ -0,0 +1,18 @@
import migration00 from './00_use_single_api_key_and_url';
import migration01 from './01_remove_obsolete_config_keys_from_global_file';
import migration02 from './02_set_missing_default_values';
export const migrations = [
{
name: '00_use_single_api_key_and_url',
run: migration00
},
{
name: '01_remove_obsolete_config_keys_from_global_file',
run: migration01
},
{
name: '02_set_missing_default_values',
run: migration02
}
];

71
src/migrations/_run.ts Normal file
View File

@@ -0,0 +1,71 @@
import fs from 'fs';
import { homedir } from 'os';
import { join as pathJoin } from 'path';
import { migrations } from './_migrations';
import { outro } from '@clack/prompts';
import chalk from 'chalk';
import {
getConfig,
getIsGlobalConfigFileExist,
OCO_AI_PROVIDER_ENUM
} from '../commands/config';
const migrationsFile = pathJoin(homedir(), '.opencommit_migrations');
const getCompletedMigrations = (): string[] => {
if (!fs.existsSync(migrationsFile)) {
return [];
}
const data = fs.readFileSync(migrationsFile, 'utf-8');
return data ? JSON.parse(data) : [];
};
const saveCompletedMigration = (migrationName: string) => {
const completedMigrations = getCompletedMigrations();
completedMigrations.push(migrationName);
fs.writeFileSync(
migrationsFile,
JSON.stringify(completedMigrations, null, 2)
);
};
export const runMigrations = async () => {
// if no config file, we assume it's a new installation and no migrations are needed
if (!getIsGlobalConfigFileExist()) return;
const config = getConfig();
if (config.OCO_AI_PROVIDER === OCO_AI_PROVIDER_ENUM.TEST) return;
const completedMigrations = getCompletedMigrations();
let isMigrated = false;
for (const migration of migrations) {
if (!completedMigrations.includes(migration.name)) {
try {
console.log('Applying migration', migration.name);
migration.run();
console.log('Migration applied successfully', migration.name);
saveCompletedMigration(migration.name);
} catch (error) {
outro(
`${chalk.red('Failed to apply migration')} ${
migration.name
}: ${error}`
);
process.exit(1);
}
isMigrated = true;
}
}
if (isMigrated) {
outro(
`${chalk.green(
'✔'
)} Migrations to your config were applied successfully. Please rerun.`
);
process.exit(0);
}
};

View File

@@ -0,0 +1,93 @@
import { spinner } from '@clack/prompts';
import { getConfig } from '../../commands/config';
import { i18n, I18nLocals } from '../../i18n';
import { getEngine } from '../../utils/engine';
import { COMMITLINT_LLM_CONFIG_PATH } from './constants';
import { computeHash } from './crypto';
import { commitlintPrompts, inferPromptsFromCommitlintConfig } from './prompts';
import { getCommitLintPWDConfig } from './pwd-commitlint';
import { CommitlintLLMConfig } from './types';
import * as utils from './utils';
const config = getConfig();
const translation = i18n[(config.OCO_LANGUAGE as I18nLocals) || 'en'];
export const configureCommitlintIntegration = async (force = false) => {
const spin = spinner();
spin.start('Loading @commitlint configuration');
const fileExists = await utils.commitlintLLMConfigExists();
const commitLintConfig = await getCommitLintPWDConfig();
if (commitLintConfig === null) {
throw new Error(
`Failed to load @commitlint config. Please check the following:
* @commitlint >= 9.0.0 is installed in the local directory.
* 'node_modules/@commitlint/load' package exists.
* A valid @commitlint configuration exists.
`
);
}
// debug complete @commitlint configuration
// await fs.writeFile(
// `${OPENCOMMIT_COMMITLINT_CONFIG}-commitlint-debug`,
// JSON.stringify(commitLintConfig, null, 2)
// );
const hash = await computeHash(JSON.stringify(commitLintConfig));
spin.stop(`Read @commitlint configuration (hash: ${hash})`);
if (fileExists) {
// Check if we need to update the prompts.
const { hash: existingHash } = await utils.getCommitlintLLMConfig();
if (hash === existingHash && !force) {
spin.stop(
'Hashes are the same, no need to update the config. Run "force" command to bypass.'
);
return;
}
}
spin.start('Generating consistency with given @commitlint rules');
const prompts = inferPromptsFromCommitlintConfig(commitLintConfig as any);
const consistencyPrompts =
commitlintPrompts.GEN_COMMITLINT_CONSISTENCY_PROMPT(prompts);
// debug prompt which will generate a consistency
// await fs.writeFile(
// `${COMMITLINT_LLM_CONFIG}-debug`,
// consistencyPrompts.map((p) => p.content)
// );
const engine = getEngine();
let consistency =
(await engine.generateCommitMessage(consistencyPrompts)) || '{}';
// Cleanup the consistency answer. Sometimes 'gpt-3.5-turbo' sends rule's back.
prompts.forEach((prompt) => (consistency = consistency.replace(prompt, '')));
// sometimes consistency is preceded by explanatory text like "Here is your JSON:"
consistency = utils.getJSONBlock(consistency);
// ... remaining might be extra set of "\n"
consistency = utils.removeDoubleNewlines(consistency);
const commitlintLLMConfig: CommitlintLLMConfig = {
hash,
prompts,
consistency: {
[translation.localLanguage]: {
...JSON.parse(consistency as string)
}
}
};
await utils.writeCommitlintLLMConfig(commitlintLLMConfig);
spin.stop(`Done - please review contents of ${COMMITLINT_LLM_CONFIG_PATH}`);
};

View File

@@ -0,0 +1 @@
export const COMMITLINT_LLM_CONFIG_PATH = `${process.env.PWD}/.opencommit-commitlint`;

View File

@@ -0,0 +1,15 @@
import crypto from 'crypto';
export const computeHash = async (
content: string,
algorithm: string = 'sha256'
): Promise<string> => {
try {
const hash = crypto.createHash(algorithm);
hash.update(content);
return hash.digest('hex');
} catch (error) {
console.error('Error while computing hash:', error);
throw error;
}
};

View File

@@ -0,0 +1,294 @@
import chalk from 'chalk';
import { OpenAI } from 'openai';
import { outro } from '@clack/prompts';
import {
PromptConfig,
QualifiedConfig,
RuleConfigSeverity,
RuleConfigTuple
} from '@commitlint/types';
import { getConfig } from '../../commands/config';
import { i18n, I18nLocals } from '../../i18n';
import { IDENTITY, INIT_DIFF_PROMPT } from '../../prompts';
const config = getConfig();
const translation = i18n[(config.OCO_LANGUAGE as I18nLocals) || 'en'];
type DeepPartial<T> = {
[P in keyof T]?: {
[K in keyof T[P]]?: T[P][K];
};
};
type PromptFunction = (
applicable: string,
value: any,
prompt: DeepPartial<PromptConfig>
) => string;
type PromptResolverFunction = (
key: string,
applicable: string,
value: any,
prompt?: DeepPartial<PromptConfig>
) => string;
/**
* Extracts more contexte for each type-enum.
* IDEA: replicate the concept for scopes and refactor to a generic feature.
*/
const getTypeRuleExtraDescription = (
type: string,
prompt?: DeepPartial<PromptConfig>
) => prompt?.questions?.type?.enum?.[type]?.description;
/*
IDEA: Compress llm readable prompt for each section of commit message: one line for header, one line for scope, etc.
- The type must be in lowercase and should be one of the following values: featuring, fixing, documenting, styling, refactoring, testing, chores, perf, build, ci, revert.
- The scope should not be empty and provide context for the change (e.g., module or file changed).
- The subject should not be empty, should not end with a period, and should provide a concise description of the change. It should not be in sentence-case, start-case, pascal-case, or upper-case.
*/
const llmReadableRules: {
[ruleName: string]: PromptResolverFunction;
} = {
blankline: (key, applicable) =>
`There should ${applicable} be a blank line at the beginning of the ${key}.`,
caseRule: (key, applicable, value: string | Array<string>) =>
`The ${key} should ${applicable} be in ${Array.isArray(value)
? `one of the following case:
- ${value.join('\n - ')}.`
: `${value} case.`
}`,
emptyRule: (key, applicable) => `The ${key} should ${applicable} be empty.`,
enumRule: (key, applicable, value: string | Array<string>) =>
`The ${key} should ${applicable} be one of the following values:
- ${Array.isArray(value) ? value.join('\n - ') : value}.`,
enumTypeRule: (key, applicable, value: string | Array<string>, prompt) =>
`The ${key} should ${applicable} be one of the following values:
- ${Array.isArray(value)
? value
.map((v) => {
const description = getTypeRuleExtraDescription(v, prompt);
if (description) {
return `${v} (${description})`;
} else return v;
})
.join('\n - ')
: value
}.`,
fullStopRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} end with '${value}'.`,
maxLengthRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} have ${value} characters or less.`,
minLengthRule: (key, applicable, value: string) =>
`The ${key} should ${applicable} have ${value} characters or more.`
};
/**
* TODO: Validate rules to every rule in the @commitlint configuration.
* IDEA: Plugins can extend the list of rule. Provide user with a way to infer or extend when "No prompt handler for rule".
*/
const rulesPrompts: {
[ruleName: string]: PromptFunction;
} = {
'body-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('body', applicable, value),
'body-empty': (applicable: string) =>
llmReadableRules.emptyRule('body', applicable, undefined),
'body-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('body', applicable, value),
'body-leading-blank': (applicable: string) =>
llmReadableRules.blankline('body', applicable, undefined),
'body-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('body', applicable, value),
'body-max-line-length': (applicable: string, value: string) =>
`Each line of the body should ${applicable} have ${value} characters or less.`,
'body-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('body', applicable, value),
'footer-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('footer', applicable, value),
'footer-empty': (applicable: string) =>
llmReadableRules.emptyRule('footer', applicable, undefined),
'footer-leading-blank': (applicable: string) =>
llmReadableRules.blankline('footer', applicable, undefined),
'footer-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('footer', applicable, value),
'footer-max-line-length': (applicable: string, value: string) =>
`Each line of the footer should ${applicable} have ${value} characters or less.`,
'footer-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('footer', applicable, value),
'header-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('header', applicable, value),
'header-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('header', applicable, value),
'header-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('header', applicable, value),
'header-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('header', applicable, value),
'references-empty': (applicable: string) =>
llmReadableRules.emptyRule('references section', applicable, undefined),
'scope-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('scope', applicable, value),
'scope-empty': (applicable: string) =>
llmReadableRules.emptyRule('scope', applicable, undefined),
'scope-enum': (applicable: string, value: string | Array<string>) =>
llmReadableRules.enumRule('type', applicable, value),
'scope-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('scope', applicable, value),
'scope-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('scope', applicable, value),
'signed-off-by': (applicable: string, value: string) =>
`The commit message should ${applicable} have a "Signed-off-by" line with the value "${value}".`,
'subject-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('subject', applicable, value),
'subject-empty': (applicable: string) =>
llmReadableRules.emptyRule('subject', applicable, undefined),
'subject-full-stop': (applicable: string, value: string) =>
llmReadableRules.fullStopRule('subject', applicable, value),
'subject-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('subject', applicable, value),
'subject-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('subject', applicable, value),
'type-case': (applicable: string, value: string | Array<string>) =>
llmReadableRules.caseRule('type', applicable, value),
'type-empty': (applicable: string) =>
llmReadableRules.emptyRule('type', applicable, undefined),
'type-enum': (applicable: string, value: string | Array<string>, prompt) =>
llmReadableRules.enumTypeRule('type', applicable, value, prompt),
'type-max-length': (applicable: string, value: string) =>
llmReadableRules.maxLengthRule('type', applicable, value),
'type-min-length': (applicable: string, value: string) =>
llmReadableRules.minLengthRule('type', applicable, value)
};
const getPrompt = (
ruleName: string,
ruleConfig: RuleConfigTuple<unknown>,
prompt: DeepPartial<PromptConfig>
) => {
const [severity, applicable, value] = ruleConfig;
// Should we exclude "Disabled" properties?
// Is this used to disable a subjacent rule when extending presets?
if (severity === RuleConfigSeverity.Disabled) return null;
const promptFn = rulesPrompts[ruleName];
if (promptFn) {
return promptFn(applicable, value, prompt);
}
// Plugins may add their custom rules.
// We might want to call OpenAI to build this rule's llm-readable prompt.
outro(`${chalk.red('✖')} No prompt handler for rule "${ruleName}".`);
return `Please manualy set the prompt for rule "${ruleName}".`;
};
export const inferPromptsFromCommitlintConfig = (
config: QualifiedConfig
): string[] => {
const { rules, prompt } = config;
if (!rules) return [];
return Object.keys(rules)
.map((ruleName) =>
getPrompt(ruleName, rules[ruleName] as RuleConfigTuple<unknown>, prompt)
)
.filter((prompt) => prompt !== null) as string[];
};
/**
* Breaking down commit message structure for conventional commit, and mapping bits with
* ubiquitous language from @commitlint.
* While gpt-4 does this on it self, gpt-3.5 can't map this on his own atm.
*/
const STRUCTURE_OF_COMMIT = config.OCO_OMIT_SCOPE
? `
- Header of commit is composed of type and subject: <type-of-commit>: <subject-of-commit>
- Description of commit is composed of body and footer (optional): <body-of-commit>\n<footer(s)-of-commit>`
: `
- Header of commit is composed of type, scope, subject: <type-of-commit>(<scope-of-commit>): <subject-of-commit>
- Description of commit is composed of body and footer (optional): <body-of-commit>\n<footer(s)-of-commit>`;
// Prompt to generate LLM-readable rules based on @commitlint rules.
const GEN_COMMITLINT_CONSISTENCY_PROMPT = (
prompts: string[]
): OpenAI.Chat.Completions.ChatCompletionMessageParam[] => [
{
role: 'system',
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages for two different changes in a single codebase and output them in the provided JSON format: one for a bug fix and another for a new feature.
Here are the specific requirements and conventions that should be strictly followed:
Commit Message Conventions:
- The commit message consists of three parts: Header, Body, and Footer.
- Header:
- Format: ${config.OCO_OMIT_SCOPE ? '`<type>: <subject>`' : '`<type>(<scope>): <subject>`'}
- ${prompts.join('\n- ')}
JSON Output Format:
- The JSON output should contain the commit messages for a bug fix and a new feature in the following format:
\`\`\`json
{
"localLanguage": "${translation.localLanguage}",
"commitFix": "<Header of commit for bug fix with scope>",
"commitFeat": "<Header of commit for feature with scope>",
"commitFixOmitScope": "<Header of commit for bug fix without scope>",
"commitFeatOmitScope": "<Header of commit for feature without scope>",
"commitDescription": "<Description of commit for both the bug fix and the feature>"
}
\`\`\`
- The "commitDescription" should not include the commit message's header, only the description.
- Description should not be more than 74 characters.
Additional Details:
- Changing the variable 'port' to uppercase 'PORT' is considered a bug fix.
- Allowing the server to listen on a port specified through the environment variable is considered a new feature.
Example Git Diff is to follow:`
},
INIT_DIFF_PROMPT
];
/**
* Prompt to have LLM generate a message using @commitlint rules.
*
* @param language
* @param prompts
* @returns
*/
const INIT_MAIN_PROMPT = (
language: string,
prompts: string[]
): OpenAI.Chat.Completions.ChatCompletionMessageParam => ({
role: 'system',
content: `${IDENTITY} Your mission is to create clean and comprehensive commit messages in the given @commitlint convention and explain WHAT were the changes ${config.OCO_WHY ? 'and WHY the changes were done' : ''
}. I'll send you an output of 'git diff --staged' command, and you convert it into a commit message.
${config.OCO_EMOJI
? 'Use GitMoji convention to preface the commit.'
: 'Do not preface the commit with anything.'
}
${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."
}
Use the present tense. Use ${language} to answer.
${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.'
: ''
}
${config.OCO_OMIT_SCOPE
? 'Do not include a scope in the commit message format. Use the format: <type>: <subject>'
: ''
}
You will strictly follow the following conventions to generate the content of the commit message:
- ${prompts.join('\n- ')}
The conventions refers to the following structure of commit message:
${STRUCTURE_OF_COMMIT}`
});
export const commitlintPrompts = {
INIT_MAIN_PROMPT,
GEN_COMMITLINT_CONSISTENCY_PROMPT
};

View File

@@ -0,0 +1,74 @@
import fs from 'fs/promises';
import path from 'path';
const findModulePath = (moduleName: string) => {
const searchPaths = [
path.join('node_modules', moduleName),
path.join('node_modules', '.pnpm'),
path.resolve(__dirname, '../..')
];
for (const basePath of searchPaths) {
try {
const resolvedPath = require.resolve(moduleName, { paths: [basePath] });
return resolvedPath;
} catch {
// Continue to the next search path if the module is not found
}
}
throw new Error(`Cannot find module ${moduleName}`);
};
const getCommitLintModuleType = async (): Promise<'cjs' | 'esm'> => {
const packageFile = '@commitlint/load/package.json';
const packageJsonPath = findModulePath(packageFile);
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
if (!packageJson) {
throw new Error(`Failed to parse ${packageFile}`);
}
return packageJson.type === 'module' ? 'esm' : 'cjs';
};
/**
* QualifiedConfig from any version of @commitlint/types
* @see https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/types/src/load.ts
*/
type QualifiedConfigOnAnyVersion = { [key: string]: unknown };
/**
* This code is loading the configuration for the `@commitlint` package from the current working
* directory (`process.env.PWD`) by requiring the `load` module from the `@commitlint` package.
*
* @returns
*/
export const getCommitLintPWDConfig =
async (): Promise<QualifiedConfigOnAnyVersion | null> => {
let load: Function, modulePath: string;
switch (await getCommitLintModuleType()) {
case 'cjs':
/**
* CommonJS (<= commitlint@v18.x.x.)
*/
modulePath = findModulePath('@commitlint/load');
load = require(modulePath).default;
break;
case 'esm':
/**
* ES Module (commitlint@v19.x.x. <= )
* Directory import is not supported in ES Module resolution, so import the file directly
*/
modulePath = findModulePath('@commitlint/load/lib/load.js');
load = (await import(modulePath)).default;
break;
}
if (load && typeof load === 'function') {
return await load();
}
// @commitlint/load is not a function
return null;
};

View File

@@ -0,0 +1,11 @@
import { i18n } from '../../i18n';
export type ConsistencyPrompt = (typeof i18n)[keyof typeof i18n];
export type CommitlintLLMConfig = {
hash: string;
prompts: string[];
consistency: {
[key: string]: ConsistencyPrompt;
};
};

View File

@@ -0,0 +1,57 @@
import fs from 'fs/promises';
import { COMMITLINT_LLM_CONFIG_PATH } from './constants';
import { CommitlintLLMConfig } from './types';
/**
* Removes the "\n" only if occurring twice
*/
export const removeDoubleNewlines = (input: string): string => {
const pattern = /\\n\\n/g;
if (pattern.test(input)) {
const newInput = input.replace(pattern, '');
return removeDoubleNewlines(newInput);
}
return input;
};
export const getJSONBlock = (input: string): string => {
const jsonIndex = input.search('```json');
if (jsonIndex > -1) {
input = input.slice(jsonIndex + 8);
const endJsonIndex = input.search('```');
input = input.slice(0, endJsonIndex);
}
return input;
};
export const commitlintLLMConfigExists = async (): Promise<boolean> => {
let exists;
try {
await fs.access(COMMITLINT_LLM_CONFIG_PATH);
exists = true;
} catch (e) {
exists = false;
}
return exists;
};
export const writeCommitlintLLMConfig = async (
commitlintLLMConfig: CommitlintLLMConfig
): Promise<void> => {
await fs.writeFile(
COMMITLINT_LLM_CONFIG_PATH,
JSON.stringify(commitlintLLMConfig, null, 2)
);
};
export const getCommitlintLLMConfig =
async (): Promise<CommitlintLLMConfig> => {
const content = await fs.readFile(COMMITLINT_LLM_CONFIG_PATH);
const commitLintLLMConfig = JSON.parse(
content.toString()
) as CommitlintLLMConfig;
return commitLintLLMConfig;
};

265
src/prompts.ts Normal file
View File

@@ -0,0 +1,265 @@
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, single sentence, 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 one single message.'
: '';
const getScopeInstruction = () =>
config.OCO_OMIT_SCOPE
? 'Do not include a scope in the commit message format. Use the format: <type>: <subject>'
: '';
/**
* 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>${context}</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 scopeInstruction = getScopeInstruction();
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${scopeInstruction}\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 COMMIT_TYPES = {
fix: '🐛',
feat: '✨'
} as const;
const generateCommitString = (
type: keyof typeof COMMIT_TYPES,
message: string
): string => {
const cleanMessage = removeConventionalCommitWord(message);
return config.OCO_EMOJI
? `${COMMIT_TYPES[type]} ${cleanMessage}`
: message;
};
const getConsistencyContent = (translation: ConsistencyPrompt) => {
const fixMessage = config.OCO_OMIT_SCOPE && translation.commitFixOmitScope
? translation.commitFixOmitScope
: translation.commitFix;
const featMessage = config.OCO_OMIT_SCOPE && translation.commitFeatOmitScope
? translation.commitFeatOmitScope
: translation.commitFeat;
const fix = generateCommitString('fix', fixMessage);
const feat = config.OCO_ONE_LINE_COMMIT
? ''
: generateCommitString('feat', featMessage);
const description = config.OCO_DESCRIPTION
? translation.commitDescription
: '';
return [fix, feat, description].filter(Boolean).join('\n');
};
const INIT_CONSISTENCY_PROMPT = (
translation: ConsistencyPrompt
): OpenAI.Chat.Completions.ChatCompletionMessageParam => ({
role: 'assistant',
content: getConsistencyContent(translation)
});
export const getMainCommitPrompt = async (
fullGitMojiSpec: boolean,
context: string
): Promise<Array<OpenAI.Chat.Completions.ChatCompletionMessageParam>> => {
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)
];
}
};

View File

@@ -1,7 +1,10 @@
import { getOpenCommitLatestVersion } from '../api';
import currentPackage from '../../package.json' assert { type: 'json' };
import chalk from 'chalk';
import { outro } from '@clack/prompts';
import currentPackage from '../../package.json';
import { getOpenCommitLatestVersion } from '../version';
export const checkIsLatestVersion = async () => {
const latestVersion = await getOpenCommitLatestVersion();
@@ -9,7 +12,7 @@ export const checkIsLatestVersion = async () => {
const currentVersion = currentPackage.version;
if (currentVersion !== latestVersion) {
console.warn(
outro(
chalk.yellow(
`
You are not using the latest stable version of OpenCommit with new features and bug fixes.

84
src/utils/engine.ts Normal file
View File

@@ -0,0 +1,84 @@
import { getConfig, OCO_AI_PROVIDER_ENUM } from '../commands/config';
import { AnthropicEngine } from '../engine/anthropic';
import { AzureEngine } from '../engine/azure';
import { AiEngine } from '../engine/Engine';
import { FlowiseEngine } from '../engine/flowise';
import { GeminiEngine } from '../engine/gemini';
import { OllamaEngine } from '../engine/ollama';
import { OpenAiEngine } from '../engine/openAi';
import { MistralAiEngine } from '../engine/mistral';
import { TestAi, TestMockType } from '../engine/testAi';
import { GroqEngine } from '../engine/groq';
import { MLXEngine } from '../engine/mlx';
import { DeepseekEngine } from '../engine/deepseek';
export function parseCustomHeaders(headers: any): Record<string, string> {
let parsedHeaders = {};
if (!headers) {
return parsedHeaders;
}
try {
if (typeof headers === 'object' && !Array.isArray(headers)) {
parsedHeaders = headers;
} else {
parsedHeaders = JSON.parse(headers);
}
} catch (error) {
console.warn('Invalid OCO_API_CUSTOM_HEADERS format, ignoring custom headers');
}
return parsedHeaders;
}
export function getEngine(): AiEngine {
const config = getConfig();
const provider = config.OCO_AI_PROVIDER;
const customHeaders = parseCustomHeaders(config.OCO_API_CUSTOM_HEADERS);
const DEFAULT_CONFIG = {
model: config.OCO_MODEL!,
maxTokensOutput: config.OCO_TOKENS_MAX_OUTPUT!,
maxTokensInput: config.OCO_TOKENS_MAX_INPUT!,
baseURL: config.OCO_API_URL!,
apiKey: config.OCO_API_KEY!,
customHeaders
};
switch (provider) {
case OCO_AI_PROVIDER_ENUM.OLLAMA:
return new OllamaEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.ANTHROPIC:
return new AnthropicEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.TEST:
return new TestAi(config.OCO_TEST_MOCK_TYPE as TestMockType);
case OCO_AI_PROVIDER_ENUM.GEMINI:
return new GeminiEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.AZURE:
return new AzureEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.FLOWISE:
return new FlowiseEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.GROQ:
return new GroqEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.MISTRAL:
return new MistralAiEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.MLX:
return new MLXEngine(DEFAULT_CONFIG);
case OCO_AI_PROVIDER_ENUM.DEEPSEEK:
return new DeepseekEngine(DEFAULT_CONFIG);
default:
return new OpenAiEngine(DEFAULT_CONFIG);
}
}

View File

@@ -1,8 +1,9 @@
import { execa } from 'execa';
import { outro, spinner } from '@clack/prompts';
import { readFileSync } from 'fs';
import ignore, { Ignore } from 'ignore';
import { outro, spinner } from '@clack/prompts';
export const assertGitRepo = async () => {
try {
await execa('git', ['rev-parse']);
@@ -25,13 +26,11 @@ export const getOpenCommitIgnore = (): Ignore => {
return ig;
};
export const getCoreHooksPath = async(): Promise<string> => {
const { stdout } = await execa('git', [
'config',
'core.hooksPath']);
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', [
@@ -76,19 +75,30 @@ export const getChangedFiles = async (): Promise<string[]> => {
export const gitAdd = async ({ files }: { files: string[] }) => {
const gitAddSpinner = spinner();
gitAddSpinner.start('Adding files to commit');
await execa('git', ['add', ...files]);
gitAddSpinner.stop('Done');
gitAddSpinner.stop(`Staged ${files.length} files`);
};
export const getDiff = async ({ files }: { files: string[] }) => {
const lockFiles = files.filter(
(file) => file.includes('.lock') || file.includes('-lock.')
(file) =>
file.includes('.lock') ||
file.includes('-lock.') ||
file.includes('.svg') ||
file.includes('.png') ||
file.includes('.jpg') ||
file.includes('.jpeg') ||
file.includes('.webp') ||
file.includes('.gif')
);
if (lockFiles.length) {
outro(
`Some files are '.lock' files which are excluded by default from 'git diff'. No commit messages are generated for this files:\n${lockFiles.join(
`Some files are excluded by default from 'git diff'. No commit messages are generated for this files:\n${lockFiles.join(
'\n'
)}`
);

View File

@@ -1,4 +1,5 @@
import { tokenCount } from './tokenCount';
export function mergeDiffs(arr: string[], maxStringLength: number): string[] {
const mergedArr: string[] = [];
let currentItem: string = arr[0];

View File

@@ -0,0 +1,51 @@
/**
* Removes content wrapped in specified tags from a string
* @param content The content string to process
* @param tag The tag name without angle brackets (e.g., 'think' for '<think></think>')
* @returns The content with the specified tags and their contents removed, and trimmed
*/
export function removeContentTags<T extends string | null | undefined>(content: T, tag: string): T {
if (!content || typeof content !== 'string') {
return content;
}
// Dynamic implementation for other cases
const openTag = `<${tag}>`;
const closeTag = `</${tag}>`;
// Parse the content and remove tags
let result = '';
let skipUntil: number | null = null;
let depth = 0;
for (let i = 0; i < content.length; i++) {
// Check for opening tag
if (content.substring(i, i + openTag.length) === openTag) {
depth++;
if (depth === 1) {
skipUntil = content.indexOf(closeTag, i + openTag.length);
i = i + openTag.length - 1; // Skip the opening tag
continue;
}
}
// Check for closing tag
else if (content.substring(i, i + closeTag.length) === closeTag && depth > 0) {
depth--;
if (depth === 0) {
i = i + closeTag.length - 1; // Skip the closing tag
skipUntil = null;
continue;
}
}
// Only add character if not inside a tag
if (skipUntil === null) {
result += content[i];
}
}
// Normalize multiple spaces/tabs into a single space (preserves newlines), then trim.
result = result.replace(/[ \t]+/g, ' ').trim();
return result as unknown as T;
}

View File

@@ -0,0 +1,3 @@
export function removeConventionalCommitWord(message: string): string {
return message.replace(/^(fix|feat)\((.+?)\):/, '($2):');
}

View File

@@ -1,5 +1,5 @@
import cl100k_base from '@dqbd/tiktoken/encoders/cl100k_base.json';
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(

14
src/version.ts Normal file
View File

@@ -0,0 +1,14 @@
import { outro } from '@clack/prompts';
import { execa } from 'execa';
export const getOpenCommitLatestVersion = async (): Promise<
string | undefined
> => {
try {
const { stdout } = await execa('npm', ['view', 'opencommit', 'version']);
return stdout;
} catch (_) {
outro('Error while getting the latest version of opencommit');
return undefined;
}
};

22
test/Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl git
# Install Node.js v20
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
RUN apt-get install -y nodejs
# Setup git
RUN git config --global user.email "test@example.com"
RUN git config --global user.name "Test User"
WORKDIR /app
COPY package.json /app/
COPY package-lock.json /app/
RUN ls -la
RUN npm ci
COPY . /app
RUN ls -la
RUN npm run build

205
test/e2e/gitPush.test.ts Normal file
View File

@@ -0,0 +1,205 @@
import path from 'path';
import 'cli-testing-library/extend-expect';
import { exec } from 'child_process';
import { prepareTempDir } from './utils';
import { promisify } from 'util';
import { render } from 'cli-testing-library';
import { resolve } from 'path';
import { rm } from 'fs';
const fsExec = promisify(exec);
const fsRemove = promisify(rm);
/**
* git remote -v
*
* [no remotes]
*/
const prepareNoRemoteGitRepository = async (): Promise<{
gitDir: string;
cleanup: () => Promise<void>;
}> => {
const tempDir = await prepareTempDir();
await fsExec('git init test', { cwd: tempDir });
const gitDir = path.resolve(tempDir, 'test');
const cleanup = async () => {
return fsRemove(tempDir, { recursive: true });
};
return {
gitDir,
cleanup
};
};
/**
* git remote -v
*
* origin /tmp/remote.git (fetch)
* origin /tmp/remote.git (push)
*/
const prepareOneRemoteGitRepository = async (): Promise<{
gitDir: string;
cleanup: () => Promise<void>;
}> => {
const tempDir = await prepareTempDir();
await fsExec('git init --bare remote.git', { cwd: tempDir });
await fsExec('git clone remote.git test', { cwd: tempDir });
const gitDir = path.resolve(tempDir, 'test');
const cleanup = async () => {
return fsRemove(tempDir, { recursive: true });
};
return {
gitDir,
cleanup
};
};
/**
* git remote -v
*
* origin /tmp/remote.git (fetch)
* origin /tmp/remote.git (push)
* other ../remote2.git (fetch)
* other ../remote2.git (push)
*/
const prepareTwoRemotesGitRepository = async (): Promise<{
gitDir: string;
cleanup: () => Promise<void>;
}> => {
const tempDir = await prepareTempDir();
await fsExec('git init --bare remote.git', { cwd: tempDir });
await fsExec('git init --bare other.git', { cwd: tempDir });
await fsExec('git clone remote.git test', { cwd: tempDir });
const gitDir = path.resolve(tempDir, 'test');
await fsExec('git remote add other ../other.git', { cwd: gitDir });
const cleanup = async () => {
return fsRemove(tempDir, { recursive: true });
};
return {
gitDir,
cleanup
};
};
describe('cli flow to push git branch', () => {
it('do nothing when OCO_GITPUSH is set to false', async () => {
const { gitDir, cleanup } = await prepareNoRemoteGitRepository();
await render('echo', [`'console.log("Hello World");' > index.ts`], {
cwd: gitDir
});
await render('git', ['add index.ts'], { cwd: gitDir });
const { queryByText, findByText, userEvent } = await render(
`OCO_AI_PROVIDER='test' OCO_GITPUSH='false' node`,
[resolve('./out/cli.cjs')],
{ cwd: gitDir }
);
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(
await queryByText('Choose a remote to push to')
).not.toBeInTheConsole();
expect(
await queryByText('Do you want to run `git push`?')
).not.toBeInTheConsole();
expect(
await queryByText('Successfully pushed all commits to origin')
).not.toBeInTheConsole();
expect(
await queryByText('Command failed with exit code 1')
).not.toBeInTheConsole();
await cleanup();
});
it('push and cause error when there is no remote', async () => {
const { gitDir, cleanup } = await prepareNoRemoteGitRepository();
await render('echo', [`'console.log("Hello World");' > index.ts`], {
cwd: gitDir
});
await render('git', ['add index.ts'], { cwd: gitDir });
const { queryByText, findByText, userEvent } = await render(
`OCO_AI_PROVIDER='test' OCO_GITPUSH='true' node`,
[resolve('./out/cli.cjs')],
{ cwd: gitDir }
);
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(
await queryByText('Choose a remote to push to')
).not.toBeInTheConsole();
expect(
await queryByText('Do you want to run `git push`?')
).not.toBeInTheConsole();
expect(
await queryByText('Successfully pushed all commits to origin')
).not.toBeInTheConsole();
expect(
await findByText('Command failed with exit code 1')
).toBeInTheConsole();
await cleanup();
});
it('push when one remote is set', async () => {
const { gitDir, cleanup } = await prepareOneRemoteGitRepository();
await render('echo', [`'console.log("Hello World");' > index.ts`], {
cwd: gitDir
});
await render('git', ['add index.ts'], { cwd: gitDir });
const { findByText, userEvent } = await render(
`OCO_AI_PROVIDER='test' OCO_GITPUSH='true' node`,
[resolve('./out/cli.cjs')],
{ cwd: gitDir }
);
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(
await findByText('Do you want to run `git push`?')
).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(
await findByText('Successfully pushed all commits to origin')
).toBeInTheConsole();
await cleanup();
});
it('push when two remotes are set', async () => {
const { gitDir, cleanup } = await prepareTwoRemotesGitRepository();
await render('echo', [`'console.log("Hello World");' > index.ts`], {
cwd: gitDir
});
await render('git', ['add index.ts'], { cwd: gitDir });
const { findByText, userEvent } = await render(
`OCO_AI_PROVIDER='test' OCO_GITPUSH='true' node`,
[resolve('./out/cli.cjs')],
{ cwd: gitDir }
);
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Choose a remote to push to')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(
await findByText('Successfully pushed all commits to origin')
).toBeInTheConsole();
await cleanup();
});
});

View File

@@ -0,0 +1,12 @@
import { resolve } from 'path'
import { render } from 'cli-testing-library'
import 'cli-testing-library/extend-expect';
import { prepareEnvironment } from './utils';
it('cli flow when there are no changes', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
const { findByText } = await render(`OCO_AI_PROVIDER='test' node`, [resolve('./out/cli.cjs')], { cwd: gitDir });
expect(await findByText('No changes detected')).toBeInTheConsole();
await cleanup();
});

55
test/e2e/oneFile.test.ts Normal file
View File

@@ -0,0 +1,55 @@
import { resolve } from 'path'
import { render } from 'cli-testing-library'
import 'cli-testing-library/extend-expect';
import { prepareEnvironment } from './utils';
it('cli flow to generate commit message for 1 new file (staged)', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
await render('echo' ,[`'console.log("Hello World");' > index.ts`], { cwd: gitDir });
await render('git' ,['add index.ts'], { cwd: gitDir });
const { queryByText, findByText, userEvent } = await render(`OCO_AI_PROVIDER='test' OCO_GITPUSH='true' node`, [resolve('./out/cli.cjs')], { cwd: gitDir });
expect(await queryByText('No files are staged')).not.toBeInTheConsole();
expect(await queryByText('Do you want to stage all files and generate commit message?')).not.toBeInTheConsole();
expect(await findByText('Generating the commit message')).toBeInTheConsole();
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Do you want to run `git push`?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Successfully pushed all commits to origin')).toBeInTheConsole();
await cleanup();
});
it('cli flow to generate commit message for 1 changed file (not staged)', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
await render('echo' ,[`'console.log("Hello World");' > index.ts`], { cwd: gitDir });
await render('git' ,['add index.ts'], { cwd: gitDir });
await render('git' ,[`commit -m 'add new file'`], { cwd: gitDir });
await render('echo' ,[`'console.log("Good night World");' >> index.ts`], { cwd: gitDir });
const { findByText, userEvent } = await render(`OCO_AI_PROVIDER='test' OCO_GITPUSH='true' node`, [resolve('./out/cli.cjs')], { cwd: gitDir });
expect(await findByText('No files are staged')).toBeInTheConsole();
expect(await findByText('Do you want to stage all files and generate commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Generating the commit message')).toBeInTheConsole();
expect(await findByText('Confirm the commit message?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Successfully committed')).toBeInTheConsole();
expect(await findByText('Do you want to run `git push`?')).toBeInTheConsole();
userEvent.keyboard('[Enter]');
expect(await findByText('Successfully pushed all commits to origin')).toBeInTheConsole();
await cleanup();
});

View File

@@ -0,0 +1,222 @@
import { resolve } from 'path';
import { render } from 'cli-testing-library';
import 'cli-testing-library/extend-expect';
import { prepareEnvironment, wait } from '../utils';
import path from 'path';
function getAbsolutePath(relativePath: string) {
// Use process.cwd() which should be the project root during test execution
return path.resolve(process.cwd(), 'test/e2e/prompt-module', relativePath);
}
async function setupCommitlint(dir: string, ver: 9 | 18 | 19) {
let packagePath, packageJsonPath, configPath;
switch (ver) {
case 9:
packagePath = getAbsolutePath('./data/commitlint_9/node_modules');
packageJsonPath = getAbsolutePath('./data/commitlint_9/package.json');
configPath = getAbsolutePath('./data/commitlint_9/commitlint.config.js');
break;
case 18:
packagePath = getAbsolutePath('./data/commitlint_18/node_modules');
packageJsonPath = getAbsolutePath('./data/commitlint_18/package.json');
configPath = getAbsolutePath('./data/commitlint_18/commitlint.config.js');
break;
case 19:
packagePath = getAbsolutePath('./data/commitlint_19/node_modules');
packageJsonPath = getAbsolutePath('./data/commitlint_19/package.json');
configPath = getAbsolutePath('./data/commitlint_19/commitlint.config.js');
break;
}
await render('cp', ['-r', packagePath, '.'], { cwd: dir });
await render('cp', [packageJsonPath, '.'], { cwd: dir });
await render('cp', [configPath, '.'], { cwd: dir });
await wait(3000); // Avoid flakiness by waiting
}
describe('cli flow to run "oco commitlint force"', () => {
it('on commitlint@9 using CJS', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
await setupCommitlint(gitDir, 9);
const npmList = await render('npm', ['list', '@commitlint/load'], {
cwd: gitDir
});
expect(await npmList.findByText('@commitlint/load@9')).toBeInTheConsole();
const { findByText } = await render(
`
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} commitlint force \
`,
[],
{ cwd: gitDir }
);
expect(
await findByText('opencommit — configure @commitlint')
).toBeInTheConsole();
expect(
await findByText('Read @commitlint configuration')
).toBeInTheConsole();
expect(
await findByText('Generating consistency with given @commitlint rules')
).toBeInTheConsole();
expect(
await findByText('Done - please review contents of')
).toBeInTheConsole();
await cleanup();
});
it('on commitlint@18 using CJS', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
await setupCommitlint(gitDir, 18);
const npmList = await render('npm', ['list', '@commitlint/load'], {
cwd: gitDir
});
expect(await npmList.findByText('@commitlint/load@18')).toBeInTheConsole();
const { findByText } = await render(
`
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} commitlint force \
`,
[],
{ cwd: gitDir }
);
expect(
await findByText('opencommit — configure @commitlint')
).toBeInTheConsole();
expect(
await findByText('Read @commitlint configuration')
).toBeInTheConsole();
expect(
await findByText('Generating consistency with given @commitlint rules')
).toBeInTheConsole();
expect(
await findByText('Done - please review contents of')
).toBeInTheConsole();
await cleanup();
});
it('on commitlint@19 using ESM', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
await setupCommitlint(gitDir, 19);
const npmList = await render('npm', ['list', '@commitlint/load'], {
cwd: gitDir
});
expect(await npmList.findByText('@commitlint/load@19')).toBeInTheConsole();
const { findByText } = await render(
`
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} commitlint force \
`,
[],
{ cwd: gitDir }
);
expect(
await findByText('opencommit — configure @commitlint')
).toBeInTheConsole();
expect(
await findByText('Read @commitlint configuration')
).toBeInTheConsole();
expect(
await findByText('Generating consistency with given @commitlint rules')
).toBeInTheConsole();
expect(
await findByText('Done - please review contents of')
).toBeInTheConsole();
await cleanup();
});
});
describe('cli flow to generate commit message using @commitlint prompt-module', () => {
it('on commitlint@19 using ESM', async () => {
const { gitDir, cleanup } = await prepareEnvironment();
// Setup commitlint@19
await setupCommitlint(gitDir, 19);
const npmList = await render('npm', ['list', '@commitlint/load'], {
cwd: gitDir
});
expect(await npmList.findByText('@commitlint/load@19')).toBeInTheConsole();
// Run `oco commitlint force`
const commitlintForce = await render(
`
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} commitlint force \
`,
[],
{ cwd: gitDir }
);
expect(
await commitlintForce.findByText('Done - please review contents of')
).toBeInTheConsole();
// Run `oco commitlint get`
const commitlintGet = await render(
`
OCO_TEST_MOCK_TYPE='prompt-module-commitlint-config' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} commitlint get \
`,
[],
{ cwd: gitDir }
);
expect(await commitlintGet.findByText('consistency')).toBeInTheConsole();
// Run 'oco' using .opencommit-commitlint
await render('echo', [`'console.log("Hello World");' > index.ts`], {
cwd: gitDir
});
await render('git', ['add index.ts'], { cwd: gitDir });
const oco = await render(
`
OCO_TEST_MOCK_TYPE='commit-message' \
OCO_PROMPT_MODULE='@commitlint' \
OCO_AI_PROVIDER='test' OCO_GITPUSH='true' \
node ${resolve('./out/cli.cjs')} \
`,
[],
{ cwd: gitDir }
);
expect(
await oco.findByText('Generating the commit message')
).toBeInTheConsole();
expect(
await oco.findByText('Confirm the commit message?')
).toBeInTheConsole();
oco.userEvent.keyboard('[Enter]');
expect(
await oco.findByText('Do you want to run `git push`?')
).toBeInTheConsole();
oco.userEvent.keyboard('[Enter]');
expect(
await oco.findByText('Successfully pushed all commits to origin')
).toBeInTheConsole();
await cleanup();
});
});

View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional']
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"name": "commitlint-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@commitlint/cli": "^18.0.0",
"@commitlint/config-conventional": "^18.0.0"
}
}

View File

@@ -0,0 +1,3 @@
export default {
extends: ['@commitlint/config-conventional']
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
{
"name": "commitlint-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@commitlint/cli": "^19.0.0",
"@commitlint/config-conventional": "^19.0.0"
}
}

View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional']
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"name": "commitlint-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@commitlint/cli": "^9.0.0",
"@commitlint/config-conventional": "^9.0.0"
}
}

11
test/e2e/setup.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
current_dir=$(pwd)
setup_dir="$(cd "$(dirname "$0")" && pwd)"
# Set up for prompt-module/commitlint
cd $setup_dir && cd prompt-module/data/commitlint_9 && npm ci
cd $setup_dir && cd prompt-module/data/commitlint_18 && npm ci
cd $setup_dir && cd prompt-module/data/commitlint_19 && npm ci
cd $current_dir

Some files were not shown because too many files have changed in this diff Show More