tinysemver 2.1.0__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tinysemver-2.1.0/tinysemver.egg-info → tinysemver-3.0.0}/PKG-INFO +48 -26
- {tinysemver-2.1.0 → tinysemver-3.0.0}/README.md +28 -17
- {tinysemver-2.1.0 → tinysemver-3.0.0}/pyproject.toml +23 -9
- tinysemver-3.0.0/tinysemver/__init__.py +75 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/tinysemver/tinysemver.py +95 -230
- {tinysemver-2.1.0 → tinysemver-3.0.0/tinysemver.egg-info}/PKG-INFO +48 -26
- tinysemver-3.0.0/tinysemver.egg-info/requires.txt +14 -0
- tinysemver-2.1.0/tinysemver/__init__.py +0 -0
- tinysemver-2.1.0/tinysemver.egg-info/requires.txt +0 -2
- {tinysemver-2.1.0 → tinysemver-3.0.0}/LICENSE +0 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/setup.cfg +0 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/tinysemver.egg-info/SOURCES.txt +0 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/tinysemver.egg-info/dependency_links.txt +0 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/tinysemver.egg-info/entry_points.txt +0 -0
- {tinysemver-2.1.0 → tinysemver-3.0.0}/tinysemver.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tinysemver
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Semantic-Versioning
|
|
5
|
-
Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Semantic-Versioning... that doesn't need 300,000 lines of JS
|
|
5
|
+
Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>
|
|
6
6
|
License: Apache License
|
|
7
7
|
Version 2.0, January 2004
|
|
8
8
|
http://www.apache.org/licenses/
|
|
@@ -206,10 +206,10 @@ License: Apache License
|
|
|
206
206
|
limitations under the License.
|
|
207
207
|
|
|
208
208
|
Project-URL: Homepage, https://pypi.org/project/tinysemver/
|
|
209
|
-
Project-URL: Documentation, https://github.com/ashvardanian/
|
|
210
|
-
Project-URL: Repository, https://github.com/ashvardanian/
|
|
211
|
-
Project-URL: Issues, https://github.com/ashvardanian/
|
|
212
|
-
Project-URL: Changelog, https://github.com/ashvardanian/
|
|
209
|
+
Project-URL: Documentation, https://github.com/ashvardanian/TinySemVer/blob/main/README.md
|
|
210
|
+
Project-URL: Repository, https://github.com/ashvardanian/TinySemVer.git
|
|
211
|
+
Project-URL: Issues, https://github.com/ashvardanian/TinySemVer/issues
|
|
212
|
+
Project-URL: Changelog, https://github.com/ashvardanian/TinySemVer/blob/main/CHANGELOG.md
|
|
213
213
|
Classifier: Development Status :: 5 - Production/Stable
|
|
214
214
|
Classifier: Intended Audience :: Developers
|
|
215
215
|
Classifier: Topic :: Software Development :: Build Tools
|
|
@@ -223,12 +223,23 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
223
223
|
Classifier: Programming Language :: Python :: 3.11
|
|
224
224
|
Description-Content-Type: text/markdown
|
|
225
225
|
License-File: LICENSE
|
|
226
|
-
Requires-Dist: openai>=1.54.4
|
|
227
226
|
Requires-Dist: rich>=13.8.0
|
|
227
|
+
Provides-Extra: test
|
|
228
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
229
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
230
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "test"
|
|
231
|
+
Provides-Extra: dev
|
|
232
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
233
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
234
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
235
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
236
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
237
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
238
|
+
Dynamic: license-file
|
|
228
239
|
|
|
229
240
|

|
|
230
241
|
|
|
231
|
-
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file
|
|
242
|
+
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
|
|
232
243
|
In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
|
|
233
244
|
Here is how to integrate it into your project CI:
|
|
234
245
|
|
|
@@ -306,19 +317,6 @@ Alternatively, you can just ask for `--help`:
|
|
|
306
317
|
$ tinysemver --help
|
|
307
318
|
```
|
|
308
319
|
|
|
309
|
-
## AI and Rock-n-Roll
|
|
310
|
-
|
|
311
|
-
TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
|
|
312
|
-
|
|
313
|
-
```sh
|
|
314
|
-
$ tinysemver --verbose \
|
|
315
|
-
--github-repository 'ashvardanian/tinysemver' \
|
|
316
|
-
--openai-base-url 'https://api.groq.com/openai/v1' \
|
|
317
|
-
--openai-api-key 'GET_YOURSELF_A_KEY' \
|
|
318
|
-
--openai-model 'llama-3.2-11b-text-preview' \
|
|
319
|
-
--dry-run
|
|
320
|
-
```
|
|
321
|
-
|
|
322
320
|
## Usage Details for the GitHub CI Action
|
|
323
321
|
|
|
324
322
|
TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
|
|
@@ -337,12 +335,12 @@ jobs:
|
|
|
337
335
|
|
|
338
336
|
steps:
|
|
339
337
|
- name: Checkout
|
|
340
|
-
uses: actions/checkout@
|
|
338
|
+
uses: actions/checkout@v5
|
|
341
339
|
with:
|
|
342
340
|
persist-credentials: false # Only if main branch if protected
|
|
343
341
|
|
|
344
342
|
- name: Run TinySemVer
|
|
345
|
-
uses: ashvardanian/tinysemver@
|
|
343
|
+
uses: ashvardanian/tinysemver@v3 # Automatically receives non-breaking updates
|
|
346
344
|
with:
|
|
347
345
|
major-verbs: 'breaking,break,major'
|
|
348
346
|
minor-verbs: 'feature,minor,add,new'
|
|
@@ -363,7 +361,7 @@ jobs:
|
|
|
363
361
|
runs-on: ubuntu-latest
|
|
364
362
|
|
|
365
363
|
steps:
|
|
366
|
-
- uses: actions/checkout@
|
|
364
|
+
- uses: actions/checkout@v5
|
|
367
365
|
with:
|
|
368
366
|
ref: main # Take the most recent updated version
|
|
369
367
|
```
|
|
@@ -382,6 +380,30 @@ If you need to update the version in multiple files, pass a multiline string wit
|
|
|
382
380
|
|
|
383
381
|
For examples, consider checking StringZilla, USearch, and other libraries using TinySemVer.
|
|
384
382
|
|
|
383
|
+
Since version 3.0.0, TinySemVer can optionally maintain moving version tags similar to `actions/checkout@v4`:
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
uses: ashvardanian/tinysemver@v3 # Automatically gets non-breaking updates
|
|
387
|
+
uses: ashvardanian/tinysemver@v3.0 # Automatically gets patch updates
|
|
388
|
+
uses: ashvardanian/tinysemver@v3.0.0 # Pinned, never changes
|
|
389
|
+
uses: ashvardanian/tinysemver@main # Bleeding edge
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
This feature is disabled by default because it requires force-push permissions.
|
|
393
|
+
To enable it, add `push-moving-tags: 'true'` and ensure your token has force-push access:
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
- name: Run TinySemVer
|
|
397
|
+
uses: ashvardanian/tinysemver@v3
|
|
398
|
+
with:
|
|
399
|
+
push: 'true'
|
|
400
|
+
push-moving-tags: 'true'
|
|
401
|
+
github-token: ${{ secrets.TINYSEMVER_TOKEN }}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
When enabled, TinySemVer creates three tags: `v3` (major), `v3.0` (minor), and `v3.0.0` (specific).
|
|
405
|
+
The default `GITHUB_TOKEN` typically lacks force-push permissions for protected branches.
|
|
406
|
+
|
|
385
407
|
### Security Considerations
|
|
386
408
|
|
|
387
409
|
If your default branch is protected with a "pull request before merging" rule:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file
|
|
3
|
+
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
|
|
4
4
|
In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
|
|
5
5
|
Here is how to integrate it into your project CI:
|
|
6
6
|
|
|
@@ -78,19 +78,6 @@ Alternatively, you can just ask for `--help`:
|
|
|
78
78
|
$ tinysemver --help
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
## AI and Rock-n-Roll
|
|
82
|
-
|
|
83
|
-
TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
$ tinysemver --verbose \
|
|
87
|
-
--github-repository 'ashvardanian/tinysemver' \
|
|
88
|
-
--openai-base-url 'https://api.groq.com/openai/v1' \
|
|
89
|
-
--openai-api-key 'GET_YOURSELF_A_KEY' \
|
|
90
|
-
--openai-model 'llama-3.2-11b-text-preview' \
|
|
91
|
-
--dry-run
|
|
92
|
-
```
|
|
93
|
-
|
|
94
81
|
## Usage Details for the GitHub CI Action
|
|
95
82
|
|
|
96
83
|
TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
|
|
@@ -109,12 +96,12 @@ jobs:
|
|
|
109
96
|
|
|
110
97
|
steps:
|
|
111
98
|
- name: Checkout
|
|
112
|
-
uses: actions/checkout@
|
|
99
|
+
uses: actions/checkout@v5
|
|
113
100
|
with:
|
|
114
101
|
persist-credentials: false # Only if main branch if protected
|
|
115
102
|
|
|
116
103
|
- name: Run TinySemVer
|
|
117
|
-
uses: ashvardanian/tinysemver@
|
|
104
|
+
uses: ashvardanian/tinysemver@v3 # Automatically receives non-breaking updates
|
|
118
105
|
with:
|
|
119
106
|
major-verbs: 'breaking,break,major'
|
|
120
107
|
minor-verbs: 'feature,minor,add,new'
|
|
@@ -135,7 +122,7 @@ jobs:
|
|
|
135
122
|
runs-on: ubuntu-latest
|
|
136
123
|
|
|
137
124
|
steps:
|
|
138
|
-
- uses: actions/checkout@
|
|
125
|
+
- uses: actions/checkout@v5
|
|
139
126
|
with:
|
|
140
127
|
ref: main # Take the most recent updated version
|
|
141
128
|
```
|
|
@@ -154,6 +141,30 @@ If you need to update the version in multiple files, pass a multiline string wit
|
|
|
154
141
|
|
|
155
142
|
For examples, consider checking StringZilla, USearch, and other libraries using TinySemVer.
|
|
156
143
|
|
|
144
|
+
Since version 3.0.0, TinySemVer can optionally maintain moving version tags similar to `actions/checkout@v4`:
|
|
145
|
+
|
|
146
|
+
```yaml
|
|
147
|
+
uses: ashvardanian/tinysemver@v3 # Automatically gets non-breaking updates
|
|
148
|
+
uses: ashvardanian/tinysemver@v3.0 # Automatically gets patch updates
|
|
149
|
+
uses: ashvardanian/tinysemver@v3.0.0 # Pinned, never changes
|
|
150
|
+
uses: ashvardanian/tinysemver@main # Bleeding edge
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This feature is disabled by default because it requires force-push permissions.
|
|
154
|
+
To enable it, add `push-moving-tags: 'true'` and ensure your token has force-push access:
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
- name: Run TinySemVer
|
|
158
|
+
uses: ashvardanian/tinysemver@v3
|
|
159
|
+
with:
|
|
160
|
+
push: 'true'
|
|
161
|
+
push-moving-tags: 'true'
|
|
162
|
+
github-token: ${{ secrets.TINYSEMVER_TOKEN }}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
When enabled, TinySemVer creates three tags: `v3` (major), `v3.0` (minor), and `v3.0.0` (specific).
|
|
166
|
+
The default `GITHUB_TOKEN` typically lacks force-push permissions for protected branches.
|
|
167
|
+
|
|
157
168
|
### Security Considerations
|
|
158
169
|
|
|
159
170
|
If your default branch is protected with a "pull request before merging" rule:
|
|
@@ -4,13 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tinysemver"
|
|
7
|
-
version = "
|
|
8
|
-
description = "Semantic-Versioning
|
|
7
|
+
version = "3.0.0"
|
|
8
|
+
description = "Semantic-Versioning... that doesn't need 300,000 lines of JS"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
11
11
|
authors = [
|
|
12
|
-
{ name = "Ash Vardanian", email = "1983160+ashvardanian@users.noreply.github.com" }
|
|
13
|
-
{ name = "Guillaume de Rouville", email = "31691250+grouville@users.noreply.github.com" },
|
|
12
|
+
{ name = "Ash Vardanian", email = "1983160+ashvardanian@users.noreply.github.com" }
|
|
14
13
|
]
|
|
15
14
|
classifiers = [
|
|
16
15
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -25,17 +24,32 @@ classifiers = [
|
|
|
25
24
|
"Programming Language :: Python :: 3.10",
|
|
26
25
|
"Programming Language :: Python :: 3.11",
|
|
27
26
|
]
|
|
28
|
-
dependencies = ["
|
|
27
|
+
dependencies = ["rich >= 13.8.0"]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
test = [
|
|
31
|
+
"pytest >= 7.0.0",
|
|
32
|
+
"pytest-cov >= 4.0.0",
|
|
33
|
+
"pytest-mock >= 3.10.0",
|
|
34
|
+
]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest >= 7.0.0",
|
|
37
|
+
"pytest-cov >= 4.0.0",
|
|
38
|
+
"pytest-mock >= 3.10.0",
|
|
39
|
+
"ruff >= 0.1.0",
|
|
40
|
+
"mypy >= 1.0.0",
|
|
41
|
+
"black >= 23.0.0",
|
|
42
|
+
]
|
|
29
43
|
|
|
30
44
|
[project.scripts]
|
|
31
45
|
tinysemver = "tinysemver.tinysemver:main"
|
|
32
46
|
|
|
33
47
|
[project.urls]
|
|
34
48
|
Homepage = "https://pypi.org/project/tinysemver/"
|
|
35
|
-
Documentation = "https://github.com/ashvardanian/
|
|
36
|
-
Repository = "https://github.com/ashvardanian/
|
|
37
|
-
Issues = "https://github.com/ashvardanian/
|
|
38
|
-
Changelog = "https://github.com/ashvardanian/
|
|
49
|
+
Documentation = "https://github.com/ashvardanian/TinySemVer/blob/main/README.md"
|
|
50
|
+
Repository = "https://github.com/ashvardanian/TinySemVer.git"
|
|
51
|
+
Issues = "https://github.com/ashvardanian/TinySemVer/issues"
|
|
52
|
+
Changelog = "https://github.com/ashvardanian/TinySemVer/blob/main/CHANGELOG.md"
|
|
39
53
|
|
|
40
54
|
[tool.setuptools]
|
|
41
55
|
package-dir = { "" = "." }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""TinySemVer - A tiny but mighty Semantic Versioning tool.
|
|
2
|
+
|
|
3
|
+
This package provides automated semantic versioning for Git repositories
|
|
4
|
+
following Conventional Commits. It can generate changelogs and release notes
|
|
5
|
+
with or without LLM assistance.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from tinysemver import bump
|
|
9
|
+
>>> new_version = bump(
|
|
10
|
+
... path=".",
|
|
11
|
+
... dry_run=True,
|
|
12
|
+
... verbose=True,
|
|
13
|
+
... version_file="VERSION",
|
|
14
|
+
... changelog_file="CHANGELOG.md"
|
|
15
|
+
... )
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from tinysemver.tinysemver import (
|
|
19
|
+
# Main function
|
|
20
|
+
bump,
|
|
21
|
+
# Version utilities
|
|
22
|
+
parse_version,
|
|
23
|
+
bump_version,
|
|
24
|
+
# Commit utilities
|
|
25
|
+
commit_starts_with_verb,
|
|
26
|
+
normalize_verbs,
|
|
27
|
+
group_commits,
|
|
28
|
+
convert_commits_to_message,
|
|
29
|
+
# Git utilities
|
|
30
|
+
get_last_tag,
|
|
31
|
+
get_commits_since_tag,
|
|
32
|
+
get_diff_for_commit,
|
|
33
|
+
# File utilities
|
|
34
|
+
patch_with_regex,
|
|
35
|
+
# Tag creation
|
|
36
|
+
create_tag,
|
|
37
|
+
# Types
|
|
38
|
+
SemVer,
|
|
39
|
+
BumpType,
|
|
40
|
+
PathLike,
|
|
41
|
+
Commit,
|
|
42
|
+
ChangeDiff,
|
|
43
|
+
# Exceptions
|
|
44
|
+
NoNewCommitsError,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__version__ = "2.1.1"
|
|
48
|
+
__all__ = [
|
|
49
|
+
# Main function
|
|
50
|
+
"bump",
|
|
51
|
+
# Version utilities
|
|
52
|
+
"parse_version",
|
|
53
|
+
"bump_version",
|
|
54
|
+
# Commit utilities
|
|
55
|
+
"commit_starts_with_verb",
|
|
56
|
+
"normalize_verbs",
|
|
57
|
+
"group_commits",
|
|
58
|
+
"convert_commits_to_message",
|
|
59
|
+
# Git utilities
|
|
60
|
+
"get_last_tag",
|
|
61
|
+
"get_commits_since_tag",
|
|
62
|
+
"get_diff_for_commit",
|
|
63
|
+
# File utilities
|
|
64
|
+
"patch_with_regex",
|
|
65
|
+
# Tag creation
|
|
66
|
+
"create_tag",
|
|
67
|
+
# Types
|
|
68
|
+
"SemVer",
|
|
69
|
+
"BumpType",
|
|
70
|
+
"PathLike",
|
|
71
|
+
"Commit",
|
|
72
|
+
"ChangeDiff",
|
|
73
|
+
# Exceptions
|
|
74
|
+
"NoNewCommitsError",
|
|
75
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TinySemVer is a tiny
|
|
3
|
+
"""TinySemVer is a tiny minimalistic Semantic Versioning tool.
|
|
4
4
|
|
|
5
5
|
This module traces the commit history of a Git repository after the last tag, and based
|
|
6
6
|
on the commit messages, it determines the type of version bump (major, minor, or patch).
|
|
@@ -15,7 +15,7 @@ Example:
|
|
|
15
15
|
--minor-verbs 'feature,minor,add,new' \
|
|
16
16
|
--patch-verbs 'fix,patch,bug,improve,docs,make' \
|
|
17
17
|
--changelog-file 'CHANGELOG.md' \
|
|
18
|
-
--guide-file '
|
|
18
|
+
--guide-file 'CONTRIBUTING.md' \
|
|
19
19
|
--version-file 'VERSION' \
|
|
20
20
|
--update-version-in 'package.json' '"version": "(.*)"' \
|
|
21
21
|
--update-version-in 'CITATION.cff' '^version: (.*)' \
|
|
@@ -67,39 +67,10 @@ class NoNewCommitsError(Exception):
|
|
|
67
67
|
pass
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
class MayContainVulnerability(Warning):
|
|
71
|
-
"""Raised when the commit may contain a vulnerability."""
|
|
72
|
-
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class MayContainLogicalBugs(Warning):
|
|
77
|
-
"""Raised when the commit may contain logical bugs."""
|
|
78
|
-
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class MayContainBreakingChange(Warning):
|
|
83
|
-
"""Raised when the commit may contain a breaking change."""
|
|
84
|
-
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class MayLackDocumentation(Warning):
|
|
89
|
-
"""Raised when the commit may lack documentation updates."""
|
|
90
|
-
|
|
91
|
-
pass
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class UnknownCommitWarning(Warning):
|
|
95
|
-
"""Something in the commit doesn't add up."""
|
|
96
|
-
|
|
97
|
-
pass
|
|
98
70
|
|
|
99
71
|
|
|
100
72
|
_console = None # Initialize global console variable
|
|
101
73
|
_console_is_rich = True # Initialize global console type variable
|
|
102
|
-
_openai_client = None # Initialize global OpenAI client variable
|
|
103
74
|
|
|
104
75
|
|
|
105
76
|
def print_to_console(message: str) -> None:
|
|
@@ -120,16 +91,6 @@ def print_to_console(message: str) -> None:
|
|
|
120
91
|
print(message)
|
|
121
92
|
|
|
122
93
|
|
|
123
|
-
def get_open_ai_client(base_url: str, api_key: str):
|
|
124
|
-
# Create a global variable for the client
|
|
125
|
-
global _openai_client
|
|
126
|
-
if not _openai_client:
|
|
127
|
-
from openai import OpenAI
|
|
128
|
-
|
|
129
|
-
_openai_client = OpenAI(base_url=base_url, api_key=api_key)
|
|
130
|
-
return _openai_client
|
|
131
|
-
|
|
132
|
-
|
|
133
94
|
def get_last_tag(repository_path: PathLike) -> str:
|
|
134
95
|
"""Retrieve the last Git tag name from the repository."""
|
|
135
96
|
result = subprocess.run(
|
|
@@ -262,6 +223,7 @@ def create_tag(
|
|
|
262
223
|
github_repository: Optional[str] = None,
|
|
263
224
|
push: bool = False,
|
|
264
225
|
create_release: bool = False,
|
|
226
|
+
push_moving_tags: bool = False,
|
|
265
227
|
major_commits: List[Commit] = None,
|
|
266
228
|
minor_commits: List[Commit] = None,
|
|
267
229
|
patch_commits: List[Commit] = None,
|
|
@@ -274,7 +236,8 @@ def create_tag(
|
|
|
274
236
|
env["GIT_COMMITTER_EMAIL"] = user_email
|
|
275
237
|
env["GIT_AUTHOR_NAME"] = user_name
|
|
276
238
|
env["GIT_AUTHOR_EMAIL"] = user_email
|
|
277
|
-
|
|
239
|
+
if github_token:
|
|
240
|
+
env["GITHUB_TOKEN"] = github_token
|
|
278
241
|
|
|
279
242
|
message = f"Release: {tag} [skip ci]"
|
|
280
243
|
message += convert_commits_to_message(major_commits or [], minor_commits or [], patch_commits or [])
|
|
@@ -293,6 +256,29 @@ def create_tag(
|
|
|
293
256
|
|
|
294
257
|
subprocess.run(["git", "tag", "-a", tag, "-m", message, new_commit_sha], cwd=repository_path, env=env)
|
|
295
258
|
print_to_console(f"[bold green]Created new tag:[/bold green] {tag}")
|
|
259
|
+
|
|
260
|
+
# Optionally create moving major and minor version tags for GitHub Actions compatibility
|
|
261
|
+
# This allows users to reference @v2 or @v2.1 instead of @v2.1.1
|
|
262
|
+
# Note: This requires force-push permissions
|
|
263
|
+
if push_moving_tags:
|
|
264
|
+
major_tag = f"v{version[0]}"
|
|
265
|
+
minor_tag = f"v{version[0]}.{version[1]}"
|
|
266
|
+
|
|
267
|
+
# Force-update major version tag (e.g., v2 -> v2.1.1)
|
|
268
|
+
subprocess.run(
|
|
269
|
+
["git", "tag", "-fa", major_tag, "-m", f"Update {major_tag} to {tag}", new_commit_sha],
|
|
270
|
+
cwd=repository_path,
|
|
271
|
+
env=env,
|
|
272
|
+
)
|
|
273
|
+
print_to_console(f"[bold green]Updated major version tag:[/bold green] {major_tag} -> {tag}")
|
|
274
|
+
|
|
275
|
+
# Force-update minor version tag (e.g., v2.1 -> v2.1.1)
|
|
276
|
+
subprocess.run(
|
|
277
|
+
["git", "tag", "-fa", minor_tag, "-m", f"Update {minor_tag} to {tag}", new_commit_sha],
|
|
278
|
+
cwd=repository_path,
|
|
279
|
+
env=env,
|
|
280
|
+
)
|
|
281
|
+
print_to_console(f"[bold green]Updated minor version tag:[/bold green] {minor_tag} -> {tag}")
|
|
296
282
|
if push:
|
|
297
283
|
url = None
|
|
298
284
|
if github_token and github_repository:
|
|
@@ -315,12 +301,41 @@ def create_tag(
|
|
|
315
301
|
f"Failed to push the new commits to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
|
|
316
302
|
)
|
|
317
303
|
|
|
318
|
-
|
|
304
|
+
# Push the specific version tag (e.g., v2.1.1)
|
|
305
|
+
push_result = subprocess.run(["git", "push", url, tag], cwd=repository_path, capture_output=True, env=env)
|
|
319
306
|
if push_result.returncode != 0:
|
|
320
307
|
raise RuntimeError(
|
|
321
|
-
f"Failed to push the
|
|
308
|
+
f"Failed to push the tag '{tag}' to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
print_to_console(f"[bold green]Pushed tag:[/bold green] {tag}")
|
|
312
|
+
|
|
313
|
+
# Optionally force-push the moving major and minor version tags
|
|
314
|
+
if push_moving_tags:
|
|
315
|
+
major_tag = f"v{version[0]}"
|
|
316
|
+
minor_tag = f"v{version[0]}.{version[1]}"
|
|
317
|
+
|
|
318
|
+
# Force-push the major version tag (e.g., v2)
|
|
319
|
+
push_result = subprocess.run(
|
|
320
|
+
["git", "push", url, major_tag, "--force"], cwd=repository_path, capture_output=True, env=env
|
|
321
|
+
)
|
|
322
|
+
if push_result.returncode != 0:
|
|
323
|
+
raise RuntimeError(
|
|
324
|
+
f"Failed to push major tag '{major_tag}' to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Force-push the minor version tag (e.g., v2.1)
|
|
328
|
+
push_result = subprocess.run(
|
|
329
|
+
["git", "push", url, minor_tag, "--force"], cwd=repository_path, capture_output=True, env=env
|
|
322
330
|
)
|
|
323
|
-
|
|
331
|
+
if push_result.returncode != 0:
|
|
332
|
+
raise RuntimeError(
|
|
333
|
+
f"Failed to push minor tag '{minor_tag}' to the remote repository: '{url}' with error: {push_result.stderr.decode('utf-8')}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
print_to_console(f"[bold green]Pushed moving tags:[/bold green]")
|
|
337
|
+
print_to_console(f"[bold green] - Major:[/bold green] {major_tag}")
|
|
338
|
+
print_to_console(f"[bold green] - Minor:[/bold green] {minor_tag}")
|
|
324
339
|
|
|
325
340
|
# Create a release using GitHub CLI if available
|
|
326
341
|
if create_release and github_repository:
|
|
@@ -370,6 +385,7 @@ def patch_with_regex(
|
|
|
370
385
|
) -> None:
|
|
371
386
|
"""Update a file by replacing the first matched group of every RegEx match with a new version."""
|
|
372
387
|
|
|
388
|
+
assert os.path.exists(file_path), f"File missing: {file_path}"
|
|
373
389
|
with open(file_path, "r") as file:
|
|
374
390
|
old_content = file.read()
|
|
375
391
|
|
|
@@ -388,7 +404,7 @@ def patch_with_regex(
|
|
|
388
404
|
# Compile the regex pattern with multiline support
|
|
389
405
|
regex_pattern = re.compile(regex_pattern, re.MULTILINE)
|
|
390
406
|
matches = list(re.finditer(regex_pattern, old_content))
|
|
391
|
-
new_content = re.sub(regex_pattern, replace_first_group, old_content, 1)
|
|
407
|
+
new_content = re.sub(regex_pattern, replace_first_group, old_content, count=1)
|
|
392
408
|
|
|
393
409
|
non_empty_matches = [m for m in matches if len(m.group(0).strip())]
|
|
394
410
|
assert len(non_empty_matches) > 0, f"No matches found in: {file_path}"
|
|
@@ -396,7 +412,7 @@ def patch_with_regex(
|
|
|
396
412
|
for match in non_empty_matches:
|
|
397
413
|
match_line = old_content.count("\n", 0, match.pos) + 1
|
|
398
414
|
old_slice = match.group(0)
|
|
399
|
-
new_slice = re.sub(regex_pattern, replace_first_group, old_slice, 1)
|
|
415
|
+
new_slice = re.sub(regex_pattern, replace_first_group, old_slice, count=1)
|
|
400
416
|
|
|
401
417
|
if verbose:
|
|
402
418
|
print_to_console(f"[bold cyan]Will update file:[/bold cyan] {file_path}:{match_line}")
|
|
@@ -410,117 +426,12 @@ def patch_with_regex(
|
|
|
410
426
|
print_to_console(f"[bold green]File updated successfully:[/bold green] {file_path}")
|
|
411
427
|
|
|
412
428
|
|
|
413
|
-
def validate_commit_with_llms(
|
|
414
|
-
base_url: str,
|
|
415
|
-
api_key: str,
|
|
416
|
-
model: Optional[str],
|
|
417
|
-
commit: Commit,
|
|
418
|
-
change: ChangeDiff,
|
|
419
|
-
) -> Optional[Warning]:
|
|
420
|
-
prompt = f"""
|
|
421
|
-
You are a professional coding assistant helping me to validate a commit message and its changes.
|
|
422
|
-
|
|
423
|
-
1. Check if the changed code is likely to introduce logical bugs.
|
|
424
|
-
2. Check if the commit message is likely to contain a vulnerability.
|
|
425
|
-
3. Check if it contains a breaking change that is likely to affect users.
|
|
426
|
-
4. Check if the documentation wasn't updated for the changes made.
|
|
427
|
-
|
|
428
|
-
Don't let the commit message fool you, just look at the changes made in the code.
|
|
429
|
-
Reply by choosing any number from 0 to 4 and describe the issue, if anything is found.
|
|
430
|
-
|
|
431
|
-
0. No issues found.
|
|
432
|
-
1. The commit may introduce logical bugs, such as the overflow in ...
|
|
433
|
-
2. The commit may contain a vulnerability, such as the SQL injection in ...
|
|
434
|
-
3. The commit may contain a breaking change, such as the removal of ...
|
|
435
|
-
4. The commit may lack documentation updates for the changes made in function ...
|
|
436
|
-
"""
|
|
437
|
-
message = f"""
|
|
438
|
-
#{commit.hash}: {commit.message}
|
|
439
|
-
|
|
440
|
-
Changes:
|
|
441
|
-
|
|
442
|
-
{change}
|
|
443
|
-
"""
|
|
444
|
-
client = get_open_ai_client(base_url=base_url, api_key=api_key)
|
|
445
|
-
response = client.chat.completions.create(
|
|
446
|
-
messages=[
|
|
447
|
-
{"role": "system", "content": prompt},
|
|
448
|
-
{"role": "user", "content": message},
|
|
449
|
-
],
|
|
450
|
-
max_tokens=256,
|
|
451
|
-
model=model,
|
|
452
|
-
stream=False,
|
|
453
|
-
)
|
|
454
|
-
response_text = response.choices[0].message.content
|
|
455
|
-
if response_text.startswith("0."):
|
|
456
|
-
return None
|
|
457
|
-
if response_text.startswith("1."):
|
|
458
|
-
return MayContainLogicalBugs(response_text)
|
|
459
|
-
if response_text.startswith("2."):
|
|
460
|
-
return MayContainVulnerability(response_text)
|
|
461
|
-
if response_text.startswith("3."):
|
|
462
|
-
return MayContainBreakingChange(response_text)
|
|
463
|
-
if response_text.startswith("4."):
|
|
464
|
-
return MayLackDocumentation(response_text)
|
|
465
|
-
return UnknownCommitWarning(response_text)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def aggregate_release_notes_with_llms(
|
|
469
|
-
base_url: str,
|
|
470
|
-
api_key: str,
|
|
471
|
-
model: Optional[str],
|
|
472
|
-
github_repository: str,
|
|
473
|
-
commits: List[Commit],
|
|
474
|
-
changes: List[ChangeDiff],
|
|
475
|
-
) -> str:
|
|
476
|
-
|
|
477
|
-
commits_with_hashes = "\n".join(f"- {commit.hash}: {commit.message}" for commit in commits)
|
|
478
|
-
prompt = f"""
|
|
479
|
-
You are a release notes generator for an advanced software project.
|
|
480
|
-
|
|
481
|
-
Aggregate the release notes for the upcoming version based on the commits and their
|
|
482
|
-
changes replying in a Github-flavored Markdown format.
|
|
483
|
-
|
|
484
|
-
- Mention the new features, improvements, and bug fixes.
|
|
485
|
-
- Warn about potential breaking changes and vulnerabilities.
|
|
486
|
-
- Tag people and teams responsible for the changes.
|
|
487
|
-
- For the most important commits provide a link to the issue or pull request and
|
|
488
|
-
a URL to that commit, like [commit](https://github.com/{github_repository}/commit/...).
|
|
489
|
-
- Don't guess anything, only use the information from the commits and their changes.
|
|
490
|
-
- Use inline math notation, like $\\beta=1$ or math blocks like the following, when needed:
|
|
491
|
-
|
|
492
|
-
```math
|
|
493
|
-
\\S_i(A, B, \\alpha, \\beta) = \\alpha \\cdot A_i + \\beta \\cdot B_i
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
- Use alerting quotes, like: [!CAUTION] or [!TIP], when needed.
|
|
497
|
-
"""
|
|
498
|
-
header_message = f"""Commits:
|
|
499
|
-
|
|
500
|
-
{commits_with_hashes}
|
|
501
|
-
"""
|
|
502
|
-
changes_messages = [f"#{commit.hash}: {commit.message}\n{change}" for commit, change in zip(commits, changes)]
|
|
503
|
-
|
|
504
|
-
client = OpenAI(base_url=base_url, api_key=api_key)
|
|
505
|
-
response = client.chat.completions.create(
|
|
506
|
-
messages=[
|
|
507
|
-
{"role": "system", "content": prompt},
|
|
508
|
-
{"role": "user", "content": header_message},
|
|
509
|
-
*[{"role": "user", "content": message} for message in changes_messages],
|
|
510
|
-
],
|
|
511
|
-
max_tokens=2048,
|
|
512
|
-
model=model,
|
|
513
|
-
stream=False,
|
|
514
|
-
)
|
|
515
|
-
return response.choices[0].message.content
|
|
516
|
-
|
|
517
|
-
|
|
518
429
|
def bump(
|
|
519
430
|
*, # enforce keyword-only arguments
|
|
520
431
|
dry_run: bool = False,
|
|
521
432
|
verbose: bool = False,
|
|
522
433
|
major_verbs: List[str] = ["major", "breaking", "break"],
|
|
523
|
-
minor_verbs: List[str] = ["minor", "feature", "add", "new"],
|
|
434
|
+
minor_verbs: List[str] = ["minor", "feature", "feat", "add", "new"],
|
|
524
435
|
patch_verbs: List[str] = ["patch", "fix", "bug", "improve", "docs", "make"],
|
|
525
436
|
path: Optional[PathLike] = None, # takes current directory as default
|
|
526
437
|
changelog_file: Optional[PathLike] = None, # relative or absolute path to the changelog file
|
|
@@ -534,11 +445,9 @@ def bump(
|
|
|
534
445
|
push: bool = True,
|
|
535
446
|
github_token: Optional[str] = None,
|
|
536
447
|
github_repository: Optional[str] = None,
|
|
537
|
-
openai_base_url: str = "https://api.openai.com",
|
|
538
|
-
openai_api_key: Optional[str] = None,
|
|
539
|
-
openai_model: Optional[str] = None,
|
|
540
448
|
default_branch: str = "main",
|
|
541
449
|
create_release: bool = False,
|
|
450
|
+
push_moving_tags: bool = False,
|
|
542
451
|
) -> SemVer:
|
|
543
452
|
"""Primary function used to update the files (version, changelog, etc.), create a new Git tag, push it to a GitHub repository.
|
|
544
453
|
It can be used for dry-runs to preview the changes without actually creating a new tag.
|
|
@@ -562,9 +471,6 @@ def bump(
|
|
|
562
471
|
push (bool): If True, pushes the changes to the remote GitHub repository. Defaults to True.
|
|
563
472
|
github_token (Optional[str]): The GitHub token for authentication. If None, it attempts to use the GH_TOKEN environment variable. Defaults to None.
|
|
564
473
|
github_repository (Optional[str]): The GitHub repository in 'owner/repo' format. If None, it attempts to use the GH_REPOSITORY environment variable. Defaults to None.
|
|
565
|
-
openai_base_url (str): The OpenAI API base URL. Defaults to "https://api.openai.com".
|
|
566
|
-
openai_api_key (Optional[str]): The OpenAI API key. Defaults to None.
|
|
567
|
-
openai_model (Optional[str]): The OpenAI model to use for text generation. Defaults to None.
|
|
568
474
|
default_branch (str): The default branch to push the changes to. Defaults to "main".
|
|
569
475
|
|
|
570
476
|
Returns:
|
|
@@ -574,12 +480,16 @@ def bump(
|
|
|
574
480
|
repository_path = os.path.abspath(path) if path else os.getcwd()
|
|
575
481
|
assert os.path.isdir(os.path.join(repository_path, ".git")), f"Not a Git repository: {repository_path}"
|
|
576
482
|
|
|
577
|
-
def normalize_path(file_path
|
|
578
|
-
if not file_path
|
|
483
|
+
def normalize_path(file_path) -> str:
|
|
484
|
+
if not file_path:
|
|
579
485
|
return None
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
486
|
+
# Convert `pathlib.Path` to `str`
|
|
487
|
+
file_path_str = str(file_path) if hasattr(file_path, '__fspath__') or isinstance(file_path, os.PathLike) else file_path
|
|
488
|
+
if not file_path_str or len(file_path_str) == 0:
|
|
489
|
+
return None
|
|
490
|
+
if os.path.isabs(file_path_str):
|
|
491
|
+
return file_path_str
|
|
492
|
+
return os.path.join(repository_path, file_path_str)
|
|
583
493
|
|
|
584
494
|
changelog_file = normalize_path(changelog_file)
|
|
585
495
|
version_file = normalize_path(version_file)
|
|
@@ -613,7 +523,7 @@ def bump(
|
|
|
613
523
|
), f"Changelog file is missing or isn't a regular file: {changelog_file}"
|
|
614
524
|
|
|
615
525
|
major_verbs = normalize_verbs(major_verbs, ["major", "breaking", "break"])
|
|
616
|
-
minor_verbs = normalize_verbs(minor_verbs, ["minor", "feature", "add", "new"])
|
|
526
|
+
minor_verbs = normalize_verbs(minor_verbs, ["minor", "feature", "feat", "add", "new"])
|
|
617
527
|
patch_verbs = normalize_verbs(patch_verbs, ["patch", "fix", "bug", "improve", "docs", "make"])
|
|
618
528
|
|
|
619
529
|
last_tag = get_last_tag(repository_path)
|
|
@@ -621,16 +531,16 @@ def bump(
|
|
|
621
531
|
|
|
622
532
|
current_version = parse_version(last_tag)
|
|
623
533
|
if verbose:
|
|
624
|
-
|
|
534
|
+
print_to_console(f"Current version: {current_version[0]}.{current_version[1]}.{current_version[2]}")
|
|
625
535
|
|
|
626
536
|
commits = get_commits_since_tag(repository_path, last_tag)
|
|
627
537
|
if not len(commits):
|
|
628
538
|
raise NoNewCommitsError(f"No new commits since the last {last_tag} tag")
|
|
629
539
|
|
|
630
540
|
if verbose:
|
|
631
|
-
|
|
541
|
+
print_to_console(f"? Commits since last tag: {len(commits)}")
|
|
632
542
|
for hash, commit in commits:
|
|
633
|
-
|
|
543
|
+
print_to_console(f"# {hash}: {commit}")
|
|
634
544
|
|
|
635
545
|
major_commits, minor_commits, patch_commits = group_commits(commits, major_verbs, minor_verbs, patch_verbs)
|
|
636
546
|
assert (
|
|
@@ -645,7 +555,7 @@ def bump(
|
|
|
645
555
|
bump_type = "patch"
|
|
646
556
|
new_version = bump_version(current_version, bump_type)
|
|
647
557
|
if verbose:
|
|
648
|
-
|
|
558
|
+
print_to_console(f"Next version: {new_version[0]}.{new_version[1]}.{new_version[2]} (type: {bump_type})")
|
|
649
559
|
|
|
650
560
|
new_version_str = f"{new_version[0]}.{new_version[1]}.{new_version[2]}"
|
|
651
561
|
if version_file:
|
|
@@ -656,12 +566,12 @@ def bump(
|
|
|
656
566
|
changes = f"\n## {now:%B %d, %Y}: v{new_version_str}\n"
|
|
657
567
|
changes += convert_commits_to_message(major_commits, minor_commits, patch_commits)
|
|
658
568
|
|
|
659
|
-
|
|
569
|
+
print_to_console(f"Will update file: {changelog_file}")
|
|
660
570
|
if verbose:
|
|
661
571
|
changes_lines = changes.count("\n") + 1
|
|
662
|
-
|
|
572
|
+
print_to_console(f"? Appending {changes_lines} lines")
|
|
663
573
|
for line in changes.split("\n"):
|
|
664
|
-
|
|
574
|
+
print_to_console(f"+ {line}")
|
|
665
575
|
|
|
666
576
|
if not dry_run:
|
|
667
577
|
with open(changelog_file, "a") as file:
|
|
@@ -680,43 +590,6 @@ def bump(
|
|
|
680
590
|
for file_path, regex_pattern in update_patch_version_in:
|
|
681
591
|
patch_with_regex(file_path, regex_pattern, str(new_version[2]), dry_run=dry_run, verbose=verbose)
|
|
682
592
|
|
|
683
|
-
# Now log the potential issues with the commits
|
|
684
|
-
if openai_api_key:
|
|
685
|
-
warnings_commits = []
|
|
686
|
-
warnings = []
|
|
687
|
-
changes = [get_diff_for_commit(repository_path, commit.hash) for commit in commits]
|
|
688
|
-
for commit, change in zip(commits, changes):
|
|
689
|
-
try:
|
|
690
|
-
warning = validate_commit_with_llms(
|
|
691
|
-
base_url=openai_base_url,
|
|
692
|
-
api_key=openai_api_key,
|
|
693
|
-
model=openai_model,
|
|
694
|
-
commit=commit,
|
|
695
|
-
change=change,
|
|
696
|
-
)
|
|
697
|
-
if warning:
|
|
698
|
-
warnings_commits.append(commit)
|
|
699
|
-
warnings.append(warning)
|
|
700
|
-
except Exception as e:
|
|
701
|
-
print(f"Failed to validate commit: {commit.hash} with error: {str(e)}")
|
|
702
|
-
traceback.print_exc()
|
|
703
|
-
|
|
704
|
-
if len(warnings):
|
|
705
|
-
print("## Potential issues")
|
|
706
|
-
for commit, warning in zip(warnings_commits, warnings):
|
|
707
|
-
print(f"- Commit #{commit.hash}: {warning}")
|
|
708
|
-
|
|
709
|
-
release_notes = aggregate_release_notes_with_llms(
|
|
710
|
-
base_url=openai_base_url,
|
|
711
|
-
api_key=openai_api_key,
|
|
712
|
-
model=openai_model,
|
|
713
|
-
github_repository=github_repository,
|
|
714
|
-
commits=commits,
|
|
715
|
-
changes=changes,
|
|
716
|
-
)
|
|
717
|
-
print("## Generated release notes:")
|
|
718
|
-
print(release_notes)
|
|
719
|
-
|
|
720
593
|
if not dry_run:
|
|
721
594
|
create_tag(
|
|
722
595
|
repository_path=repository_path,
|
|
@@ -728,11 +601,14 @@ def bump(
|
|
|
728
601
|
github_repository=github_repository,
|
|
729
602
|
push=push,
|
|
730
603
|
create_release=create_release,
|
|
604
|
+
push_moving_tags=push_moving_tags,
|
|
731
605
|
major_commits=major_commits,
|
|
732
606
|
minor_commits=minor_commits,
|
|
733
607
|
patch_commits=patch_commits,
|
|
734
608
|
)
|
|
735
609
|
|
|
610
|
+
return new_version
|
|
611
|
+
|
|
736
612
|
|
|
737
613
|
def main():
|
|
738
614
|
"""
|
|
@@ -834,25 +710,18 @@ def main():
|
|
|
834
710
|
"--github-repository",
|
|
835
711
|
help="GitHub repository in the 'owner/repo' format, if not set will use GH_REPOSITORY env var",
|
|
836
712
|
)
|
|
837
|
-
parser.add_argument(
|
|
838
|
-
"--openai-base-url",
|
|
839
|
-
default="https://api.openai.com",
|
|
840
|
-
help="OpenAI API base URL",
|
|
841
|
-
)
|
|
842
|
-
parser.add_argument(
|
|
843
|
-
"--openai-api-key",
|
|
844
|
-
help="OpenAI API key for text generation",
|
|
845
|
-
)
|
|
846
|
-
parser.add_argument(
|
|
847
|
-
"--openai-model",
|
|
848
|
-
help="OpenAI model to use for text generation (e.g., 'text-davinci-003' or 'text-davinci-002')",
|
|
849
|
-
)
|
|
850
713
|
parser.add_argument(
|
|
851
714
|
"--create-release",
|
|
852
715
|
action="store_true",
|
|
853
716
|
default=False,
|
|
854
717
|
help="Create a GitHub release using the GitHub CLI",
|
|
855
718
|
)
|
|
719
|
+
parser.add_argument(
|
|
720
|
+
"--push-moving-tags",
|
|
721
|
+
action="store_true",
|
|
722
|
+
default=False,
|
|
723
|
+
help="Push moving major and minor version tags (requires force-push permissions)",
|
|
724
|
+
)
|
|
856
725
|
args = parser.parse_args()
|
|
857
726
|
else:
|
|
858
727
|
|
|
@@ -864,7 +733,7 @@ def main():
|
|
|
864
733
|
args.verbose = os.environ.get("TINYSEMVER_VERBOSE", "").lower() == "true"
|
|
865
734
|
args.push = os.environ.get("TINYSEMVER_PUSH", "").lower() == "true"
|
|
866
735
|
args.major_verbs = os.environ.get("TINYSEMVER_MAJOR_VERBS") or "breaking,break,major"
|
|
867
|
-
args.minor_verbs = os.environ.get("TINYSEMVER_MINOR_VERBS") or "feature,minor,add,new"
|
|
736
|
+
args.minor_verbs = os.environ.get("TINYSEMVER_MINOR_VERBS") or "feature,feat,minor,add,new"
|
|
868
737
|
args.patch_verbs = os.environ.get("TINYSEMVER_PATCH_VERBS") or "fix,patch,bug,improve,docs,make"
|
|
869
738
|
args.default_branch = os.environ.get("TINYSEMVER_DEFAULT_BRANCH") or "main"
|
|
870
739
|
args.changelog_file = os.environ.get("TINYSEMVER_CHANGELOG_FILE")
|
|
@@ -894,10 +763,8 @@ def main():
|
|
|
894
763
|
args.git_user_email = os.environ.get("TINYSEMVER_GIT_USER_EMAIL", "tinysemver@ashvardanian.com")
|
|
895
764
|
args.github_token = os.environ.get("GITHUB_TOKEN")
|
|
896
765
|
args.github_repository = os.environ.get("GITHUB_REPOSITORY")
|
|
897
|
-
args.openai_base_url = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com")
|
|
898
|
-
args.openai_api_key = os.environ.get("OPENAI_API_KEY")
|
|
899
|
-
args.openai_model = os.environ.get("OPENAI_MODEL")
|
|
900
766
|
args.create_release = os.environ.get("TINYSEMVER_CREATE_RELEASE", "").lower() == "true"
|
|
767
|
+
args.push_moving_tags = os.environ.get("TINYSEMVER_PUSH_MOVING_TAGS", "").lower() == "true"
|
|
901
768
|
|
|
902
769
|
# It's common for a CI pipeline to have multiple broken settings or missing files.
|
|
903
770
|
# For the best user experience, we want to catch all errors and print them at once,
|
|
@@ -925,11 +792,9 @@ def main():
|
|
|
925
792
|
git_user_email=args.git_user_email,
|
|
926
793
|
github_token=args.github_token,
|
|
927
794
|
github_repository=args.github_repository,
|
|
928
|
-
openai_base_url=args.openai_base_url,
|
|
929
|
-
openai_api_key=args.openai_api_key,
|
|
930
|
-
openai_model=args.openai_model,
|
|
931
795
|
push=args.push,
|
|
932
796
|
create_release=args.create_release,
|
|
797
|
+
push_moving_tags=args.push_moving_tags,
|
|
933
798
|
)
|
|
934
799
|
except NoNewCommitsError as e:
|
|
935
800
|
print(f"! {e}")
|
|
@@ -938,7 +803,7 @@ def main():
|
|
|
938
803
|
print(f"! {e}")
|
|
939
804
|
exit(1)
|
|
940
805
|
except Exception as e:
|
|
941
|
-
traceback.print_exc(
|
|
806
|
+
traceback.print_exc()
|
|
942
807
|
exit(1)
|
|
943
808
|
|
|
944
809
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tinysemver
|
|
3
|
-
Version:
|
|
4
|
-
Summary: Semantic-Versioning
|
|
5
|
-
Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Semantic-Versioning... that doesn't need 300,000 lines of JS
|
|
5
|
+
Author-email: Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>
|
|
6
6
|
License: Apache License
|
|
7
7
|
Version 2.0, January 2004
|
|
8
8
|
http://www.apache.org/licenses/
|
|
@@ -206,10 +206,10 @@ License: Apache License
|
|
|
206
206
|
limitations under the License.
|
|
207
207
|
|
|
208
208
|
Project-URL: Homepage, https://pypi.org/project/tinysemver/
|
|
209
|
-
Project-URL: Documentation, https://github.com/ashvardanian/
|
|
210
|
-
Project-URL: Repository, https://github.com/ashvardanian/
|
|
211
|
-
Project-URL: Issues, https://github.com/ashvardanian/
|
|
212
|
-
Project-URL: Changelog, https://github.com/ashvardanian/
|
|
209
|
+
Project-URL: Documentation, https://github.com/ashvardanian/TinySemVer/blob/main/README.md
|
|
210
|
+
Project-URL: Repository, https://github.com/ashvardanian/TinySemVer.git
|
|
211
|
+
Project-URL: Issues, https://github.com/ashvardanian/TinySemVer/issues
|
|
212
|
+
Project-URL: Changelog, https://github.com/ashvardanian/TinySemVer/blob/main/CHANGELOG.md
|
|
213
213
|
Classifier: Development Status :: 5 - Production/Stable
|
|
214
214
|
Classifier: Intended Audience :: Developers
|
|
215
215
|
Classifier: Topic :: Software Development :: Build Tools
|
|
@@ -223,12 +223,23 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
223
223
|
Classifier: Programming Language :: Python :: 3.11
|
|
224
224
|
Description-Content-Type: text/markdown
|
|
225
225
|
License-File: LICENSE
|
|
226
|
-
Requires-Dist: openai>=1.54.4
|
|
227
226
|
Requires-Dist: rich>=13.8.0
|
|
227
|
+
Provides-Extra: test
|
|
228
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
229
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
230
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "test"
|
|
231
|
+
Provides-Extra: dev
|
|
232
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
233
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
234
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
|
|
235
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
236
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
237
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
238
|
+
Dynamic: license-file
|
|
228
239
|
|
|
229
240
|

|
|
230
241
|
|
|
231
|
-
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file
|
|
242
|
+
__TinySemVer__ is a minimalistic [Semantic Versioning](https://semver.org/) package for projects following [Conventional Commits](https://www.conventionalcommits.org/) in a single short Python file.
|
|
232
243
|
In plain English, if your commit messages look like `feat: add new feature` or `fix: bugfix`, this package will automate releasing new "GIT tags" based on the commit messages.
|
|
233
244
|
Here is how to integrate it into your project CI:
|
|
234
245
|
|
|
@@ -306,19 +317,6 @@ Alternatively, you can just ask for `--help`:
|
|
|
306
317
|
$ tinysemver --help
|
|
307
318
|
```
|
|
308
319
|
|
|
309
|
-
## AI and Rock-n-Roll
|
|
310
|
-
|
|
311
|
-
TinySemVer can leverage a language model to validate the commits and generate clean and infromative release notes.
|
|
312
|
-
|
|
313
|
-
```sh
|
|
314
|
-
$ tinysemver --verbose \
|
|
315
|
-
--github-repository 'ashvardanian/tinysemver' \
|
|
316
|
-
--openai-base-url 'https://api.groq.com/openai/v1' \
|
|
317
|
-
--openai-api-key 'GET_YOURSELF_A_KEY' \
|
|
318
|
-
--openai-model 'llama-3.2-11b-text-preview' \
|
|
319
|
-
--dry-run
|
|
320
|
-
```
|
|
321
|
-
|
|
322
320
|
## Usage Details for the GitHub CI Action
|
|
323
321
|
|
|
324
322
|
TinySemVer can be easily integrated into your GitHub Actions CI pipeline.
|
|
@@ -337,12 +335,12 @@ jobs:
|
|
|
337
335
|
|
|
338
336
|
steps:
|
|
339
337
|
- name: Checkout
|
|
340
|
-
uses: actions/checkout@
|
|
338
|
+
uses: actions/checkout@v5
|
|
341
339
|
with:
|
|
342
340
|
persist-credentials: false # Only if main branch if protected
|
|
343
341
|
|
|
344
342
|
- name: Run TinySemVer
|
|
345
|
-
uses: ashvardanian/tinysemver@
|
|
343
|
+
uses: ashvardanian/tinysemver@v3 # Automatically receives non-breaking updates
|
|
346
344
|
with:
|
|
347
345
|
major-verbs: 'breaking,break,major'
|
|
348
346
|
minor-verbs: 'feature,minor,add,new'
|
|
@@ -363,7 +361,7 @@ jobs:
|
|
|
363
361
|
runs-on: ubuntu-latest
|
|
364
362
|
|
|
365
363
|
steps:
|
|
366
|
-
- uses: actions/checkout@
|
|
364
|
+
- uses: actions/checkout@v5
|
|
367
365
|
with:
|
|
368
366
|
ref: main # Take the most recent updated version
|
|
369
367
|
```
|
|
@@ -382,6 +380,30 @@ If you need to update the version in multiple files, pass a multiline string wit
|
|
|
382
380
|
|
|
383
381
|
For examples, consider checking StringZilla, USearch, and other libraries using TinySemVer.
|
|
384
382
|
|
|
383
|
+
Since version 3.0.0, TinySemVer can optionally maintain moving version tags similar to `actions/checkout@v4`:
|
|
384
|
+
|
|
385
|
+
```yaml
|
|
386
|
+
uses: ashvardanian/tinysemver@v3 # Automatically gets non-breaking updates
|
|
387
|
+
uses: ashvardanian/tinysemver@v3.0 # Automatically gets patch updates
|
|
388
|
+
uses: ashvardanian/tinysemver@v3.0.0 # Pinned, never changes
|
|
389
|
+
uses: ashvardanian/tinysemver@main # Bleeding edge
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
This feature is disabled by default because it requires force-push permissions.
|
|
393
|
+
To enable it, add `push-moving-tags: 'true'` and ensure your token has force-push access:
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
- name: Run TinySemVer
|
|
397
|
+
uses: ashvardanian/tinysemver@v3
|
|
398
|
+
with:
|
|
399
|
+
push: 'true'
|
|
400
|
+
push-moving-tags: 'true'
|
|
401
|
+
github-token: ${{ secrets.TINYSEMVER_TOKEN }}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
When enabled, TinySemVer creates three tags: `v3` (major), `v3.0` (minor), and `v3.0.0` (specific).
|
|
405
|
+
The default `GITHUB_TOKEN` typically lacks force-push permissions for protected branches.
|
|
406
|
+
|
|
385
407
|
### Security Considerations
|
|
386
408
|
|
|
387
409
|
If your default branch is protected with a "pull request before merging" rule:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|