Compare commits

..

68 Commits

Author SHA1 Message Date
di-sukharev
ea271276c4 2.2.4 2023-05-26 12:51:59 +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
di-sukharev
e2eb13a678 style(action.yml): reformat branding section to be more readable and consistent with the rest of the file
feat(action.yml): add keywords to improve discoverability of the action
2023-05-21 16:30:14 +08:00
Sukharev
c23b0b4806 Update action.yml 2023-05-21 16:28:50 +08:00
di-sukharev
ee540108cd add icon and color to github action.yml 2023-05-21 16:27:46 +08:00
di-sukharev
a123fbd703 chore(action.yml): add label and color to the action to improve visibility and organization in the repository 2023-05-21 16:26:59 +08:00
di-sukharev
aff1d902d2 refactor(action.yml): update action name and description for better clarity and marketing purposes 2023-05-21 16:24:45 +08:00
di-sukharev
0d8469ee42 2.0.17 2023-05-21 16:18:15 +08:00
di-sukharev
517734f293 chore(cli.cjs): update package version from 2.0.15 to 2.0.16 to reflect changes made in the latest release 2023-05-21 16:18:06 +08:00
di-sukharev
6aff5ebef4 2.0.16 2023-05-21 16:16:45 +08:00
Sukharev
6afa493726 GitHub action (#173) (#174)
* add option to run OpenCommit as a Github Action
2023-05-21 16:16:26 +08:00
Sukharev
3eb319a919 GitHub action (#173)
* add option to run OpenCommit as a Github Action
2023-05-21 16:15:45 +08:00
di-sukharev
54006826f8 2.0.15 2023-05-19 13:47:51 +08:00
di-sukharev
f674e2d99a 2.0.14 2023-05-19 13:47:45 +08:00
di-sukharev
8140322c32 refactor(prepare-commit-msg-hook.ts): remove console.log statement for commitSource variable to reduce noise in logs 2023-05-19 13:47:26 +08:00
di-sukharev
1cb8d580bb refactor(api.ts): reorder variable declarations to improve readability and semantics
refactor(prepare-commit-msg-hook.ts): remove console.log statement used for debugging purposes
2023-05-19 13:46:33 +08:00
di-sukharev
8daa3ca130 Merge remote-tracking branch 'origin/dev' 2023-05-19 13:42:43 +08:00
Takuya Ono
13015a9033 🐛 fix(config.ts): convert string value to number in OPENAI_MAX_TOKENS validator (#162)
The OPENAI_MAX_TOKENS validator now converts a string value to a number before validating it. This ensures that the validator works correctly when a string value is passed in.
2023-05-07 17:16:32 +08:00
Takuya Ono
7b90b6a287 🐛 fix(config.ts): error message for OPENAI_BASE_PATH validation (#164)
The error message for the OPENAI_BASE_PATH validation has been updated
to be more descriptive and helpful. The message now reads "Must be
string" instead of "${value} is not supported yet". This change improves
the clarity of the error message and makes it easier for developers to
understand what went wrong when the validation fails.
2023-05-07 17:15:55 +08:00
di-sukharev
598881a41c 2.0.13 2023-05-04 16:38:35 +08:00
di-sukharev
1080544631 2.0.12 2023-05-04 16:38:28 +08:00
di-sukharev
de68e6cc7a docs(README.md): add instructions for configuring openAI maxTokens and BASE_PATH parameters to customize API usage 2023-05-04 16:38:02 +08:00
di-sukharev
5addb7df25 2.0.11 2023-05-04 14:29:11 +08:00
di-sukharev
e447575980 2.0.10 2023-05-04 14:29:05 +08:00
di-sukharev
ffebbc6e1b docs: update opencommit-example.png to reflect recent changes in UI 2023-05-04 14:28:55 +08:00
di-sukharev
dab0f58d14 2.0.9 2023-05-04 12:52:22 +08:00
di-sukharev
226e21c28f 2.0.8 2023-05-04 12:52:14 +08:00
di-sukharev
0bb89abccc Merge remote-tracking branch 'origin/dev' 2023-05-04 12:51:35 +08:00
Tanoshii
ad70a90b1f Update commit.ts (#154)
* fixed type
2023-05-04 12:50:09 +08:00
di-sukharev
4deaf56e5a 2.0.7 2023-05-04 12:42:30 +08:00
di-sukharev
7c1fc10248 2.0.6 2023-05-04 12:42:24 +08:00
di-sukharev
0c25a9e32c Merge branch 'dev' 2023-05-04 12:41:35 +08:00
Juan José López Lira
3f5df6ef7c Bugfix/increase tokens (#132)
* feat(api.ts): add support for OPENAI_MAX_TOKENS environment variable
feat(config.ts): add OPENAI_MAX_TOKENS to CONFIG_KEYS and configValidators

* 2.0.2

* 2.0.3

* docs(README.md): add GPT-4 usage instructions and mention the cost difference compared to GPT-3.5-turbo

---------

Co-authored-by: di-sukharev <dim.sukharev@gmail.com>
2023-05-04 12:33:06 +08:00
di-sukharev
6cb85e40e9 2.0.5 2023-04-28 15:36:53 +08:00
di-sukharev
ba82d4d476 2.0.4 2023-04-28 15:36:45 +08:00
di-sukharev
9bf2ed34a5 docs(README.md): add GPT-4 usage instructions and mention the cost difference compared to GPT-3.5-turbo 2023-04-28 15:35:40 +08:00
25 changed files with 50506 additions and 208 deletions

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 304 KiB

2
.gitignore vendored
View File

@@ -1,9 +1,7 @@
node_modules/
coverage/
out/
temp/
build/
dist/
application.log
.DS_Store
/*.env

View File

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

View File

@@ -1,4 +1,4 @@
{
"trailingComma": "none",
"singleQuote": true
"singleQuote": true,
"trailingComma": "none"
}

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:

142
README.md
View File

@@ -5,7 +5,7 @@
<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>
</div>
<h2>GPT CLI to auto-generate impressive commits in 1 second</h2>
<h2>Auto-generate meaningful commits in 1 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>
</div>
@@ -18,7 +18,65 @@
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.
## Setup
## Setup OpenCommit as a Github Action 🔥
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`.
Create a file `.github/workflows/opencommit.yml` with 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_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 `oco`. 2 seconds and your staged changes are committed with a meaningful message.
1. Install OpenCommit globally to use in any repository:
@@ -31,7 +89,7 @@ All the commits in this repo are done with OpenCommit — look into [the commits
3. Set the key to OpenCommit config:
```sh
opencommit config set OPENAI_API_KEY=<your_api_key>
opencommit config set OCO_OPENAI_API_KEY=<your_api_key>
```
Your api key is stored locally in `~/.opencommit` config file.
@@ -45,70 +103,88 @@ git add <files...>
opencommit
```
You can also use the `oc` shortcut:
You can also use the `oco` shortcut:
```sh
git add <files...>
oc
```
## Features
## Configuration
### Preface commits with emoji 🤠
### Local per repo configuration
[GitMoji](https://gitmoji.dev/) convention is used.
Create an `.env` file and add OpenCommit config variables there like this:
To add emoji:
```sh
oc config set emoji=true
```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_LANGUAGE=<locale, scroll to the bottom to see options>
```
To remove emoji:
### 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.
Simply run any of the variable above like this:
```sh
oc config set emoji=false
oco config set OCO_OPENAI_API_KEY=gpt-4
```
### Postface commits with descriptions of changes
To add descriptions:
Configure [GitMoji](https://gitmoji.dev/) to preface a message.
```sh
oc config set description=true
oco config set OCO_EMOJI=true
```
To remove description:
To remove preface emoji:
```sh
oc config set description=false
oco config set OCO_EMOJI=false
```
### Internationalization support
### Switch to GPT-4
To specify the language used to generate commit messages:
By default OpenCommit uses GPT-3.5-turbo (ChatGPT).
You may switch to GPT-4 which performs better, but costs ~x15 times more 🤠
```sh
oco config set OCO_MODEL=gpt-4
```
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.
## Locale configuration
To globally specify the language used to generate commit messages:
```sh
# de, German ,Deutsch
oc config set language=de
oc config set language=German
oc config set language=Deutsch
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 language=fr
oc config set language=French
oc config set 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 set is **English**
All available languages are currently listed in the [i18n](https://github.com/di-sukharev/opencommit/tree/master/src/i18n) folder
### Git flags
The `opencommit` or `oc` commands can be used in place of the `git commit -m "${generatedMessage}"` command. This means that any regular flags that are used with the `git commit` command will also be applied when using `opencommit` or `oc`.
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
oc --no-verify
oco --no-verify
```
is translated to :
@@ -130,20 +206,20 @@ This is useful for preventing opencommit from uploading artifacts and large file
By default, opencommit ignores files matching: `*-lock.*` and `*.lock`
## Git hook
## 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.
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:

29
action.yml Normal file
View File

@@ -0,0 +1,29 @@
name: 'OpenCommit — improve commits with AI 🧙'
description: 'Replaces lame commit messages with meaningful AI-generated messages when you push to remote'
author: 'https://github.com/di-sukharev'
repo: 'https://github.com/di-sukharev/opencommit/tree/github-action'
branding:
icon: 'git-commit'
color: 'green'
keywords:
[
'git',
'chatgpt',
'gpt',
'ai',
'openai',
'opencommit',
'aicommit',
'aicommits',
'gptcommit',
'commit'
]
inputs:
GITHUB_TOKEN:
description: 'GitHub token'
required: true
runs:
using: 'node16'
main: 'out/github-action.cjs'

View File

@@ -1,14 +1,24 @@
import { build } from 'esbuild'
import fs from 'fs'
import { build } from 'esbuild';
import fs from 'fs';
await build({
entryPoints: ['./src/cli.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: './out/cli.cjs',
entryPoints: ['./src/cli.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: './out/cli.cjs'
});
const wasmFile = fs.readFileSync('./node_modules/@dqbd/tiktoken/lite/tiktoken_bg.wasm')
await build({
entryPoints: ['./src/github-action.ts'],
bundle: true,
platform: 'node',
format: 'cjs',
outfile: './out/github-action.cjs'
});
fs.writeFileSync('./out/tiktoken_bg.wasm', wasmFile)
const wasmFile = fs.readFileSync(
'./node_modules/@dqbd/tiktoken/lite/tiktoken_bg.wasm'
);
fs.writeFileSync('./out/tiktoken_bg.wasm', wasmFile);

22160
out/cli.cjs Executable file

File diff suppressed because one or more lines are too long

27341
out/github-action.cjs Normal file

File diff suppressed because one or more lines are too long

BIN
out/tiktoken_bg.wasm Normal file

Binary file not shown.

423
package-lock.json generated
View File

@@ -1,16 +1,21 @@
{
"name": "opencommit",
"version": "2.0.3",
"version": "2.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "opencommit",
"version": "2.0.3",
"version": "2.2.4",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0",
"@octokit/webhooks-types": "^6.11.0",
"axios": "^1.3.4",
"chalk": "^5.2.0",
"cleye": "^1.3.2",
@@ -21,7 +26,7 @@
"openai": "^3.2.1"
},
"bin": {
"oc": "out/cli.cjs",
"oco": "out/cli.cjs",
"opencommit": "out/cli.cjs"
},
"devDependencies": {
@@ -38,6 +43,112 @@
"typescript": "^4.9.3"
}
},
"node_modules/@actions/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/exec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/github": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/auth-token": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
"dependencies": {
"@octokit/types": "^6.0.3"
}
},
"node_modules/@actions/github/node_modules/@octokit/core": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
"dependencies": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.3",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"dependencies": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"dependencies": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/request": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
"dependencies": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
}
},
"node_modules/@actions/github/node_modules/@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"dependencies": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"node_modules/@actions/http-client": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.0.tgz",
"integrity": "sha512-BonhODnXr3amchh4qkmjPMUO8mFi/zLaaCeCAJZqch8iQqyDnVIkySjB38VHAC8IJ+bnlgfOqlhpyCUZHlQsqw==",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@actions/io": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
},
"node_modules/@clack/core": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.2.tgz",
@@ -246,6 +357,231 @@
"node": ">= 8"
}
},
"node_modules/@octokit/auth-token": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.3.tgz",
"integrity": "sha512-/aFM2M4HVDBT/jjDBa84sJniv1t9Gm/rLkalaz9htOm+L+8JMj1k9w0CkUdcxNyNxZPlTxKPVko+m1VlM58ZVA==",
"peer": true,
"dependencies": {
"@octokit/types": "^9.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/auth-token/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/auth-token/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/core": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz",
"integrity": "sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg==",
"peer": true,
"dependencies": {
"@octokit/auth-token": "^3.0.0",
"@octokit/graphql": "^5.0.0",
"@octokit/request": "^6.0.0",
"@octokit/request-error": "^3.0.0",
"@octokit/types": "^9.0.0",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/core/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/core/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/endpoint": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.5.tgz",
"integrity": "sha512-LG4o4HMY1Xoaec87IqQ41TQ+glvIeTKqfjkCEmt5AIwDZJwQeVZFIEYXrYY6yLwK+pAScb9Gj4q+Nz2qSw1roA==",
"peer": true,
"dependencies": {
"@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/graphql": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.5.tgz",
"integrity": "sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ==",
"peer": true,
"dependencies": {
"@octokit/request": "^6.0.0",
"@octokit/types": "^9.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/openapi-types": {
"version": "12.11.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"dependencies": {
"@octokit/types": "^6.40.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"dependencies": {
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
"@octokit/core": ">=3"
}
},
"node_modules/@octokit/request": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.3.tgz",
"integrity": "sha512-TNAodj5yNzrrZ/VxP+H5HiYaZep0H3GU0O7PaF+fhDrt8FPrnkei9Aal/txsN/1P7V3CPiThG0tIvpPDYUsyAA==",
"peer": true,
"dependencies": {
"@octokit/endpoint": "^7.0.0",
"@octokit/request-error": "^3.0.0",
"@octokit/types": "^9.0.0",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.7",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/request-error": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
"integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
"peer": true,
"dependencies": {
"@octokit/types": "^9.0.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "17.1.2",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-17.1.2.tgz",
"integrity": "sha512-OaS7Ol4Y+U50PbejfzQflGWRMxO04nYWO5ZBv6JerqMKE2WS/tI9VoVDDPXHBlRMGG2fOdKwtVGlFfc7AVIstw==",
"peer": true
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.2.2.tgz",
"integrity": "sha512-9BjDxjgQIvCjNWZsbqyH5QC2Yni16oaE6xL+8SUBMzcYPF4TGQBXGA97Cl3KceK9mwiNMb1mOYCz6FbCCLEL+g==",
"peer": true,
"dependencies": {
"@octokit/openapi-types": "^17.1.2"
}
},
"node_modules/@octokit/types": {
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
"dependencies": {
"@octokit/openapi-types": "^12.11.0"
}
},
"node_modules/@octokit/webhooks-schemas": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks-schemas/-/webhooks-schemas-6.11.0.tgz",
"integrity": "sha512-ekca2jZhb2vfQy43rjvJoV77IwEKvA42BmJ2m8H3WaNfG9BF05RodnFjh3MSOksNseoNO8w8IPLZ3d5546NH2w=="
},
"node_modules/@octokit/webhooks-types": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.11.0.tgz",
"integrity": "sha512-AanzbulOHljrku1NGfafxdpTCfw2ENaWzH01N2vqQM+cUFbk868Cgh0xylz0JIM9BoKbfI++bdD6EYX0Q/UTEw=="
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@@ -653,6 +989,11 @@
}
]
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
},
"node_modules/bl": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
@@ -882,6 +1223,11 @@
"node": ">=0.4.0"
}
},
"node_modules/deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -2039,6 +2385,14 @@
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@@ -2264,6 +2618,25 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
"node_modules/node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/npm-run-path": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
@@ -2293,7 +2666,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -2903,6 +3275,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -2972,6 +3349,14 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -3017,6 +3402,11 @@
"node": ">=4.2.0"
}
},
"node_modules/universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -3031,6 +3421,14 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -3045,6 +3443,20 @@
"defaults": "^1.0.3"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3112,8 +3524,7 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",

View File

@@ -1,7 +1,7 @@
{
"name": "opencommit",
"version": "2.0.3",
"description": "GPT CLI to auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"version": "2.2.4",
"description": "Auto-generate impressive commits in 1 second. Killing lame commits with AI 🤯🔫",
"keywords": [
"git",
"chatgpt",
@@ -17,7 +17,6 @@
"main": "cli.js",
"bin": {
"opencommit": "./out/cli.cjs",
"oc": "./out/cli.cjs",
"oco": "./out/cli.cjs"
},
"repository": {
@@ -42,7 +41,9 @@
"start": "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",
"deploy": "npm run build:push && npm version patch && npm run postversion",
"build:push": "npm run build && git add . && git commit -m 'build' && git push",
"postversion": "npm run build && git add . && git commit --amend --no-edit && git push && git push --tags && npm publish --tag latest",
"lint": "eslint src --ext ts && tsc --noEmit",
"format": "prettier --write src"
},
@@ -60,8 +61,13 @@
"typescript": "^4.9.3"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.1.1",
"@clack/prompts": "^0.6.1",
"@dqbd/tiktoken": "^1.0.2",
"@octokit/webhooks-schemas": "^6.11.0",
"@octokit/webhooks-types": "^6.11.0",
"axios": "^1.3.4",
"chalk": "^5.2.0",
"cleye": "^1.3.2",

View File

@@ -11,8 +11,9 @@ import { CONFIG_MODES, getConfig } from './commands/config';
const config = getConfig();
let apiKey = config?.OPENAI_API_KEY;
let basePath = config?.OPENAI_BASE_PATH;
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);
@@ -20,7 +21,7 @@ if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set) {
intro('opencommit');
outro(
'OPENAI_API_KEY is not set, please run `oc config set OPENAI_API_KEY=<your token>. Make sure you add payment details, so API works.`'
'OCO_OPENAI_API_KEY is not set, please run `oco 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'
@@ -29,7 +30,7 @@ if (!apiKey && command !== 'config' && mode !== CONFIG_MODES.set) {
process.exit(1);
}
const MODEL = config?.model || 'gpt-3.5-turbo';
const MODEL = config?.OCO_MODEL || 'gpt-3.5-turbo';
class OpenAi {
private openAiApiConfiguration = new OpenAiApiConfiguration({
@@ -47,20 +48,24 @@ class OpenAi {
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({
model: MODEL,
messages,
temperature: 0,
top_p: 0.1,
max_tokens: 196
});
const { data } = await this.openAI.createChatCompletion(params);
const message = data.choices[0].message;
return message?.content;
} catch (error: unknown) {
outro(`${chalk.red('✖')} ${error}`);
} 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) &&
@@ -74,7 +79,7 @@ class OpenAi {
);
}
process.exit(1);
throw err;
}
};
}

View File

@@ -7,7 +7,7 @@ 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';
const extraArgs = process.argv.slice(2);
@@ -21,7 +21,7 @@ cli(
help: { description: packageJSON.description }
},
async () => {
// await checkIsLatestVersion();
await checkIsLatestVersion();
if (await isHookCalled()) {
prepareCommitMessageHook();

View File

@@ -1,7 +1,6 @@
import { execa } from 'execa';
import {
GenerateCommitMessageErrorEnum,
generateCommitMessageWithChatCompletion
generateCommitMessageByDiff
} from '../generateCommitMessageFromGitDiff';
import {
assertGitRepo,
@@ -35,103 +34,98 @@ const generateCommitMessageFromGitDiff = async (
const commitSpinner = spinner();
commitSpinner.start('Generating the commit message');
const commitMessage = await generateCommitMessageWithChatCompletion(diff);
try {
const commitMessage = await generateCommitMessageByDiff(diff);
// TODO: show proper error messages
if (typeof commitMessage !== 'string') {
const errorMessages = {
[GenerateCommitMessageErrorEnum.emptyMessage]:
'empty openAI response, weird, try again',
[GenerateCommitMessageErrorEnum.internalError]:
'internal error, try again',
[GenerateCommitMessageErrorEnum.tooMuchTokens]:
'too much tokens in git diff, stage and commit files in parts'
};
commitSpinner.stop('📝 Commit message generated');
outro(`${chalk.red('✖')} ${errorMessages[commitMessage.error]}`);
process.exit(1);
}
commitSpinner.stop('📝 Commit message generated');
outro(
`Commit message:
outro(
`Commit message:
${chalk.grey('——————————————————')}
${commitMessage}
${chalk.grey('——————————————————')}`
);
);
const isCommitConfirmedByUser = await confirm({
message: 'Confirm the commit message?'
});
const isCommitConfirmedByUser = await confirm({
message: 'Confirm the commit message?'
});
if (isCommitConfirmedByUser && !isCancel(isCommitConfirmedByUser)) {
const { stdout } = await execa('git', [
'commit',
'-m',
commitMessage,
...extraArgs
]);
if (isCommitConfirmedByUser && !isCancel(isCommitConfirmedByUser)) {
const { stdout } = await execa('git', [
'commit',
'-m',
commitMessage,
...extraArgs
]);
outro(`${chalk.green('✔')} successfully committed`);
outro(`${chalk.green('✔')} Successfully committed`);
outro(stdout);
outro(stdout);
const remotes = await getGitRemotes();
if (!remotes.length) {
const { stdout } = await execa('git', ['push']);
if (stdout) outro(stdout);
process.exit(0);
}
if (remotes.length === 1) {
const isPushConfirmedByUser = await confirm({
message: 'Do you want to run `git push`?'
});
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${remotes[0]}\``);
const { stdout } = await execa('git', [
'push',
'--verbose',
remotes[0]
]);
pushSpinner.stop(
`${chalk.green('✔')} successfully pushed all commits to ${remotes[0]}`
);
const remotes = await getGitRemotes();
if (!remotes.length) {
const { stdout } = await execa('git', ['push']);
if (stdout) outro(stdout);
} else {
outro('`git push` aborted');
process.exit(0);
}
} else {
const selectedRemote = (await select({
message: 'Choose a remote to push to',
options: remotes.map((remote) => ({ value: remote, label: remote }))
})) as string;
if (!isCancel(selectedRemote)) {
const pushSpinner = spinner();
if (remotes.length === 1) {
const isPushConfirmedByUser = await confirm({
message: 'Do you want to run `git push`?'
});
pushSpinner.start(`Running \`git push ${selectedRemote}\``);
if (isPushConfirmedByUser && !isCancel(isPushConfirmedByUser)) {
const pushSpinner = spinner();
const { stdout } = await execa('git', ['push', selectedRemote]);
pushSpinner.start(`Running \`git push ${remotes[0]}\``);
pushSpinner.stop(
`${chalk.green(
'✔'
)} successfully pushed all commits to ${selectedRemote}`
);
const { stdout } = await execa('git', [
'push',
'--verbose',
remotes[0]
]);
if (stdout) outro(stdout);
} else outro(`${chalk.gray('')} process cancelled`);
pushSpinner.stop(
`${chalk.green('')} Successfully pushed all commits to ${
remotes[0]
}`
);
if (stdout) outro(stdout);
} else {
outro('`git push` aborted');
process.exit(0);
}
} else {
const selectedRemote = (await select({
message: 'Choose a remote to push to',
options: remotes.map((remote) => ({ value: remote, label: remote }))
})) as string;
if (!isCancel(selectedRemote)) {
const pushSpinner = spinner();
pushSpinner.start(`Running \`git push ${selectedRemote}\``);
const { stdout } = await execa('git', ['push', selectedRemote]);
pushSpinner.stop(
`${chalk.green(
'✔'
)} Successfully pushed all commits to ${selectedRemote}`
);
if (stdout) outro(stdout);
} else outro(`${chalk.gray('✖')} process cancelled`);
}
}
} catch (error) {
commitSpinner.stop('📝 Commit message generated');
const err = error as Error;
outro(`${chalk.red('✖')} ${err?.message || err}`);
process.exit(1);
}
};
@@ -144,7 +138,7 @@ export async function commit(
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);
}
}

View File

@@ -8,13 +8,18 @@ import chalk from 'chalk';
import { COMMANDS } from '../CommandsEnum';
import { getI18nLocal } from '../i18n';
import * as dotenv from 'dotenv';
dotenv.config();
export enum CONFIG_KEYS {
OPENAI_API_KEY = 'OPENAI_API_KEY',
OPENAI_BASE_PATH = 'OPENAI_BASE_PATH',
description = 'description',
emoji = 'emoji',
model = 'model',
language = 'language'
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_DESCRIPTION = 'OCO_DESCRIPTION',
OCO_EMOJI = 'OCO_EMOJI',
OCO_MODEL = 'OCO_MODEL',
OCO_LANGUAGE = 'OCO_LANGUAGE'
}
export enum CONFIG_MODES {
@@ -37,15 +42,15 @@ const validateConfig = (
};
export const configValidators = {
[CONFIG_KEYS.OPENAI_API_KEY](value: any) {
validateConfig(CONFIG_KEYS.OPENAI_API_KEY, value, 'Cannot be empty');
[CONFIG_KEYS.OCO_OPENAI_API_KEY](value: any) {
validateConfig(CONFIG_KEYS.OCO_OPENAI_API_KEY, value, 'Cannot be empty');
validateConfig(
CONFIG_KEYS.OPENAI_API_KEY,
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.startsWith('sk-'),
'Must start with "sk-"'
);
validateConfig(
CONFIG_KEYS.OPENAI_API_KEY,
CONFIG_KEYS.OCO_OPENAI_API_KEY,
value.length === 51,
'Must be 51 characters long'
);
@@ -53,9 +58,9 @@ export const configValidators = {
return value;
},
[CONFIG_KEYS.description](value: any) {
[CONFIG_KEYS.OCO_DESCRIPTION](value: any) {
validateConfig(
CONFIG_KEYS.description,
CONFIG_KEYS.OCO_DESCRIPTION,
typeof value === 'boolean',
'Must be true or false'
);
@@ -63,9 +68,28 @@ export const configValidators = {
return value;
},
[CONFIG_KEYS.emoji](value: any) {
[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);
validateConfig(
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
!isNaN(value),
'Must be a number'
);
}
validateConfig(
CONFIG_KEYS.emoji,
CONFIG_KEYS.OCO_OPENAI_MAX_TOKENS,
value ? typeof value === 'number' : undefined,
'Must be a number'
);
return value;
},
[CONFIG_KEYS.OCO_EMOJI](value: any) {
validateConfig(
CONFIG_KEYS.OCO_EMOJI,
typeof value === 'boolean',
'Must be true or false'
);
@@ -73,28 +97,28 @@ export const configValidators = {
return value;
},
[CONFIG_KEYS.language](value: any) {
[CONFIG_KEYS.OCO_LANGUAGE](value: any) {
validateConfig(
CONFIG_KEYS.language,
CONFIG_KEYS.OCO_LANGUAGE,
getI18nLocal(value),
`${value} is not supported yet`
);
return getI18nLocal(value);
},
[CONFIG_KEYS.OPENAI_BASE_PATH](value: any) {
[CONFIG_KEYS.OCO_OPENAI_BASE_PATH](value: any) {
validateConfig(
CONFIG_KEYS.OPENAI_BASE_PATH,
typeof value == 'string',
`${value} is not supported yet`
CONFIG_KEYS.OCO_OPENAI_BASE_PATH,
typeof value === 'string',
'Must be string'
);
return value;
},
[CONFIG_KEYS.model](value: any) {
[CONFIG_KEYS.OCO_MODEL](value: any) {
validateConfig(
CONFIG_KEYS.OPENAI_BASE_PATH,
value === 'gpt-3.5-turbo' || value === 'gpt-4',
CONFIG_KEYS.OCO_MODEL,
['gpt-3.5-turbo', 'gpt-4'].includes(value),
`${value} is not supported yet, use 'gpt-4' or 'gpt-3.5-turbo' (default)`
);
return value;
@@ -108,18 +132,43 @@ export type ConfigType = {
const configPath = pathJoin(homedir(), '.opencommit');
export const getConfig = (): ConfigType | null => {
const configFromEnv = {
OCO_OPENAI_API_KEY: process.env.OCO_OPENAI_API_KEY,
OCO_OPENAI_MAX_TOKENS: process.env.OCO_OPENAI_MAX_TOKENS ? Number(process.env.OCO_OPENAI_MAX_TOKENS) : undefined,
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 || 'gpt-3.5-turbo',
OCO_LANGUAGE: process.env.OCO_LANGUAGE || 'en'
};
const configExists = existsSync(configPath);
if (!configExists) return null;
if (!configExists) return configFromEnv;
const configFile = readFileSync(configPath, 'utf8');
const config = iniParse(configFile);
for (const configKey of Object.keys(config)) {
const validValue = configValidators[configKey as CONFIG_KEYS](
config[configKey]
);
if (!config[configKey] || ['null', 'undefined'].includes(config[configKey])) {
config[configKey] = undefined;
continue;
}
try {
const validator = configValidators[configKey as CONFIG_KEYS];
const validValue = validator(
config[configKey] ?? configFromEnv[configKey as CONFIG_KEYS]
);
config[configKey] = validValue;
config[configKey] = validValue;
} catch (error) {
outro(
`'${configKey}' name is invalid, it should be either 'OCO_${configKey.toUpperCase()}' or it doesn't exist.`
);
outro(
`Manually fix the '.env' file or global '~/.opencommit' config file.`
);
process.exit(1);
}
}
return config;
@@ -148,7 +197,7 @@ export const setConfig = (keyValues: [key: string, value: string][]) => {
writeFileSync(configPath, iniStringify(config), 'utf8');
outro(`${chalk.green('✔')} config successfully set`);
outro(`${chalk.green('✔')} Config successfully set`);
};
export const configCommand = command(

View File

@@ -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'`
);
} catch (error) {
outro(`${chalk.red('✖')} ${error}`);

View File

@@ -3,7 +3,7 @@ import chalk from 'chalk';
import { intro, outro, spinner } from '@clack/prompts';
import { getChangedFiles, getDiff, getStagedFiles, gitAdd } from '../utils/git';
import { getConfig } from './config';
import { generateCommitMessageWithChatCompletion } from '../generateCommitMessageFromGitDiff';
import { generateCommitMessageByDiff } from '../generateCommitMessageFromGitDiff';
const [messageFilePath, commitSource] = process.argv.slice(2);
@@ -24,7 +24,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,7 +37,7 @@ export const prepareCommitMessageHook = async (
const config = getConfig();
if (!config?.OPENAI_API_KEY) {
if (!config?.OCO_OPENAI_API_KEY) {
throw new Error(
'No OPEN_AI_API exists. Set your OPEN_AI_API=<key> in ~/.opencommit'
);
@@ -45,13 +45,11 @@ export const prepareCommitMessageHook = async (
const spin = spinner();
spin.start('Generating commit message');
const commitMessage = await generateCommitMessageWithChatCompletion(
const commitMessage = await generateCommitMessageByDiff(
await getDiff({ files: staged })
);
if (typeof commitMessage !== 'string') {
spin.stop('Error');
throw new Error(commitMessage.error);
} else spin.stop('Done');
spin.stop('Done');
const fileContent = await fs.readFile(messageFilePath);

View File

@@ -9,15 +9,15 @@ import { i18n, I18nLocals } from './i18n';
import { tokenCount } from './utils/tokenCount';
const config = getConfig();
const translation = i18n[(config?.language as I18nLocals) || 'en'];
const translation = i18n[(config?.OCO_LANGUAGE as I18nLocals) || 'en'];
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?.emoji? 'Use GitMoji convention to preface the commit.': 'Do not preface the commit with anything.'}
${config?.description ? 'Add a short description of WHY the changes are done after the commit message. Don\'t start it with "This commit", just describe the changes.': "Don't add any descriptions to the commit, only commit message."}
${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.`
},
{
@@ -49,9 +49,9 @@ app.use((_, res, next) => {
},
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: `${config?.emoji ? '🐛 ' : ''}${translation.commitFix}
${config?.emoji ? '✨ ' : ''}${translation.commitFeat}
${config?.description ? translation.commitDescription : ''}`
content: `${config?.OCO_EMOJI ? '🐛 ' : ''}${translation.commitFix}
${config?.OCO_EMOJI ? '✨ ' : ''}${translation.commitFeat}
${config?.OCO_DESCRIPTION ? translation.commitDescription : ''}`
}
];
@@ -74,19 +74,16 @@ export enum GenerateCommitMessageErrorEnum {
emptyMessage = 'EMPTY_MESSAGE'
}
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 = 3900 - INIT_MESSAGES_PROMPT_LENGTH;
const MAX_REQ_TOKENS = 3000 - INIT_MESSAGES_PROMPT_LENGTH;
export const generateCommitMessageWithChatCompletion = async (
export const generateCommitMessageByDiff = async (
diff: string
): Promise<string | GenerateCommitMessageError> => {
): Promise<string> => {
try {
if (tokenCount(diff) >= MAX_REQ_TOKENS) {
const commitMessagePromises = getCommitMsgsPromisesFromFileDiffs(
@@ -103,12 +100,12 @@ export const generateCommitMessageWithChatCompletion = async (
const commitMessage = await api.generateCommitMessage(messages);
if (!commitMessage)
return { error: GenerateCommitMessageErrorEnum.emptyMessage };
throw new Error(GenerateCommitMessageErrorEnum.emptyMessage);
return commitMessage;
}
} catch (error) {
return { error: GenerateCommitMessageErrorEnum.internalError };
throw error;
}
};

216
src/github-action.ts Normal file
View File

@@ -0,0 +1,216 @@
import core from '@actions/core';
import github from '@actions/github';
import exec from '@actions/exec';
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';
// 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
// GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
// https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#about-the-github_token-secret
const GITHUB_TOKEN = core.getInput('GITHUB_TOKEN');
const octokit = github.getOctokit(GITHUB_TOKEN);
const context = github.context;
const owner = context.repo.owner;
const repo = context.repo.repo;
type SHA = string;
type Diff = string;
async function getCommitDiff(commitSha: string) {
const diffResponse = await octokit.request<string>(
'GET /repos/{owner}/{repo}/commits/{ref}',
{
owner,
repo,
ref: commitSha,
headers: {
Accept: 'application/vnd.github.v3.diff'
}
}
);
return { sha: commitSha, diff: diffResponse.data };
}
interface DiffAndSHA {
sha: SHA;
diff: Diff;
}
interface MsgAndSHA {
sha: SHA;
msg: string;
}
// send only 3-4 size chunks of diffs in steps,
// because openAI restricts "too many requests" at once with 429 error
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)
);
let improvedMessagesAndSHAs: MsgAndSHA[] = [];
for (let step = 0; step < improvePromises.length; step += chunkSize) {
const chunkOfPromises = improvePromises.slice(step, step + chunkSize);
try {
const chunkOfImprovedMessages = await Promise.all(chunkOfPromises);
const chunkOfImprovedMessagesBySha = chunkOfImprovedMessages.map(
(improvedMsg, i) => {
const index = improvedMessagesAndSHAs.length;
const sha = diffsAndSHAs![index + i].sha;
return { sha, msg: improvedMsg };
}
);
improvedMessagesAndSHAs.push(...chunkOfImprovedMessagesBySha);
// sometimes openAI errors with 429 code (too many requests),
// so lets sleep a bit
const sleepFor =
1000 * randomIntFromInterval(1, 5) + 100 * randomIntFromInterval(1, 5);
outro(
`Improved ${chunkOfPromises.length} messages. Sleeping for ${sleepFor}`
);
await sleep(sleepFor);
} catch (error) {
outro(error as string);
// if sleeping in try block still fails with 429,
// openAI wants at least 1 minute before next request
const sleepFor = 60000 + 1000 * randomIntFromInterval(1, 5);
outro(`Retrying after sleeping for ${sleepFor}`);
await sleep(sleepFor);
// go to previous step
step -= chunkSize;
}
}
return improvedMessagesAndSHAs;
}
const getDiffsBySHAs = async (SHAs: string[]) => {
const diffPromises = SHAs.map((sha) => getCommitDiff(sha));
const diffs = await Promise.all(diffPromises).catch((error) => {
outro(`Error in Promise.all(getCommitDiffs(SHAs)): ${error}.`);
throw error;
});
return diffs;
};
async function improveCommitMessages(
commitsToImprove: { id: string; message: string }[]
): Promise<void> {
if (commitsToImprove.length) {
outro(`Found ${commitsToImprove.length} commits to improve.`);
} else {
outro('No new commits found.');
return;
}
outro('Fetching commit diffs by SHAs.');
const commitSHAsToImprove = commitsToImprove.map((commit) => commit.id);
const diffsWithSHAs = await getDiffsBySHAs(commitSHAsToImprove);
outro('Done.');
const improvedMessagesWithSHAs = await improveMessagesInChunks(diffsWithSHAs);
console.log(
`Improved ${improvedMessagesWithSHAs.length} commits: `,
improvedMessagesWithSHAs
);
const createCommitMessageFile = (message: string, index: number) =>
writeFileSync(`./commit-${index}.txt`, message);
improvedMessagesWithSHAs.forEach(({ msg }, i) =>
createCommitMessageFile(msg, i)
);
writeFileSync(`./count.txt`, '0');
writeFileSync(
'./rebase-exec.sh',
`#!/bin/bash
count=$(cat count.txt)
git commit --amend -F commit-$count.txt
echo $(( count + 1 )) > count.txt`
);
await exec.exec(`chmod +x ./rebase-exec.sh`);
await exec.exec(
'git',
['rebase', `${commitsToImprove[0].id}^`, '--exec', './rebase-exec.sh'],
{
env: {
GIT_SEQUENCE_EDITOR: 'sed -i -e "s/^pick/reword/g"',
GIT_COMMITTER_NAME: process.env.GITHUB_ACTOR!,
GIT_COMMITTER_EMAIL: `${process.env.GITHUB_ACTOR}@users.noreply.github.com`
}
}
);
const deleteCommitMessageFile = (index: number) =>
unlinkSync(`./commit-${index}.txt`);
commitsToImprove.forEach((_commit, i) => deleteCommitMessageFile(i));
unlinkSync('./count.txt');
unlinkSync('./rebase-exec.sh');
outro('Force pushing non-interactively rebased commits into remote.');
await exec.exec('git', ['status']);
// Force push the rebased commits
await exec.exec('git', ['push', `--force`]);
outro('Done 🧙');
}
async function run() {
intro('OpenCommit — improving lame commit messages');
try {
if (github.context.eventName === 'push') {
outro(`Processing commits in a Push event`);
const payload = github.context.payload as PushEvent;
const commits = payload.commits;
// Set local Git user identity for future git history manipulations
if (payload.pusher.email)
await exec.exec('git', ['config', 'user.email', payload.pusher.email]);
await exec.exec('git', ['config', 'user.name', payload.pusher.name]);
await exec.exec('git', ['status']);
await exec.exec('git', ['log', '--oneline']);
await improveCommitMessages(commits);
} else {
outro('Wrong action.');
core.error(
`OpenCommit was called on ${github.context.payload.action}. OpenCommit is supposed to be used on "push" action.`
);
}
} catch (error: any) {
const err = error?.message || error;
core.setFailed(err);
}
}
run();

View File

@@ -0,0 +1,4 @@
export function randomIntFromInterval(min: number, max: number) {
// min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}

3
src/utils/sleep.ts Normal file
View File

@@ -0,0 +1,3 @@
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -5,8 +5,8 @@
"module": "ESNext",
// "rootDir": "./src",
"moduleResolution": "node",
"resolveJsonModule": true,
"moduleResolution": "node",
"allowJs": true,