autopub 0.3.0__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- autopub/__init__.py +182 -0
- autopub/cli/__init__.py +121 -0
- autopub/cli/plugins.py +37 -0
- autopub/exceptions.py +47 -0
- autopub/plugins/__init__.py +40 -0
- autopub/plugins/bump_version.py +41 -0
- autopub/plugins/pdm.py +18 -0
- autopub/plugins/poetry.py +16 -0
- autopub/plugins/update_changelog.py +50 -0
- autopub/types.py +11 -0
- {autopub-0.3.0.dist-info → autopub-1.0.0a1.dist-info}/METADATA +7 -3
- autopub-1.0.0a1.dist-info/RECORD +15 -0
- {autopub-0.3.0.dist-info → autopub-1.0.0a1.dist-info}/WHEEL +1 -1
- autopub-1.0.0a1.dist-info/entry_points.txt +3 -0
- autopub/autopub.py +0 -83
- autopub/base.py +0 -160
- autopub/build_release.py +0 -14
- autopub/check_release.py +0 -19
- autopub/commit_release.py +0 -33
- autopub/create_github_release.py +0 -66
- autopub/deploy_release.py +0 -18
- autopub/github_contributor.py +0 -83
- autopub/prepare_release.py +0 -98
- autopub/publish_release.py +0 -17
- autopub-0.3.0.dist-info/RECORD +0 -16
- autopub-0.3.0.dist-info/entry_points.txt +0 -3
- {autopub-0.3.0.dist-info → autopub-1.0.0a1.dist-info}/LICENSE +0 -0
autopub/__init__.py
CHANGED
@@ -0,0 +1,182 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
import json
|
5
|
+
from collections.abc import Iterable
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
import frontmatter
|
9
|
+
|
10
|
+
from autopub.exceptions import (
|
11
|
+
ArtifactHashMismatch,
|
12
|
+
ArtifactNotFound,
|
13
|
+
AutopubException,
|
14
|
+
NoPackageManagerPluginFound,
|
15
|
+
ReleaseFileEmpty,
|
16
|
+
ReleaseFileNotFound,
|
17
|
+
ReleaseNotesEmpty,
|
18
|
+
ReleaseTypeInvalid,
|
19
|
+
ReleaseTypeMissing,
|
20
|
+
)
|
21
|
+
from autopub.plugins import AutopubPackageManagerPlugin, AutopubPlugin
|
22
|
+
from autopub.types import ReleaseInfo
|
23
|
+
|
24
|
+
|
25
|
+
class Autopub:
|
26
|
+
RELEASE_FILE_PATH = "RELEASE.md"
|
27
|
+
|
28
|
+
def __init__(self, plugins: Iterable[type[AutopubPlugin]] = ()) -> None:
|
29
|
+
self.plugins = [plugin_class() for plugin_class in plugins]
|
30
|
+
|
31
|
+
@property
|
32
|
+
def release_file(self) -> Path:
|
33
|
+
return Path.cwd() / self.RELEASE_FILE_PATH
|
34
|
+
|
35
|
+
@property
|
36
|
+
def release_notes(self) -> str:
|
37
|
+
return self.release_file.read_text()
|
38
|
+
|
39
|
+
@property
|
40
|
+
def release_file_hash(self) -> str:
|
41
|
+
return hashlib.sha256(self.release_notes.encode("utf-8")).hexdigest()
|
42
|
+
|
43
|
+
@property
|
44
|
+
def release_data_file(self) -> Path:
|
45
|
+
return Path(".autopub") / "release_data.json"
|
46
|
+
|
47
|
+
# TODO: typed dict
|
48
|
+
@property
|
49
|
+
def release_data(self) -> ReleaseInfo:
|
50
|
+
if not self.release_data_file.exists():
|
51
|
+
raise ArtifactNotFound()
|
52
|
+
|
53
|
+
release_data = json.loads(self.release_data_file.read_text())
|
54
|
+
|
55
|
+
if release_data["hash"] != self.release_file_hash:
|
56
|
+
raise ArtifactHashMismatch()
|
57
|
+
|
58
|
+
return ReleaseInfo(
|
59
|
+
release_type=release_data["release_type"],
|
60
|
+
release_notes=release_data["release_notes"],
|
61
|
+
additional_info=release_data["plugin_data"],
|
62
|
+
)
|
63
|
+
|
64
|
+
def check(self) -> ReleaseInfo:
|
65
|
+
release_file = Path(self.RELEASE_FILE_PATH)
|
66
|
+
|
67
|
+
if not release_file.exists():
|
68
|
+
raise ReleaseFileNotFound()
|
69
|
+
|
70
|
+
try:
|
71
|
+
release_info = self._validate_release_notes(self.release_notes)
|
72
|
+
except AutopubException as e:
|
73
|
+
for plugin in self.plugins:
|
74
|
+
plugin.on_release_notes_invalid(e)
|
75
|
+
raise
|
76
|
+
|
77
|
+
for plugin in self.plugins:
|
78
|
+
plugin.on_release_notes_valid(release_info)
|
79
|
+
|
80
|
+
self._write_artifact(release_info)
|
81
|
+
|
82
|
+
return release_info
|
83
|
+
|
84
|
+
def build(self) -> None:
|
85
|
+
if not any(
|
86
|
+
isinstance(plugin, AutopubPackageManagerPlugin) for plugin in self.plugins
|
87
|
+
):
|
88
|
+
raise NoPackageManagerPluginFound()
|
89
|
+
|
90
|
+
for plugin in self.plugins:
|
91
|
+
if isinstance(plugin, AutopubPackageManagerPlugin):
|
92
|
+
plugin.build()
|
93
|
+
|
94
|
+
def prepare(self) -> None:
|
95
|
+
for plugin in self.plugins:
|
96
|
+
plugin.prepare(self.release_data)
|
97
|
+
|
98
|
+
def publish(self, repository: str | None = None) -> None:
|
99
|
+
# TODO: shall we put this in a function, to make it
|
100
|
+
# clear that we are triggering the logic to check the release file?
|
101
|
+
self.release_data
|
102
|
+
|
103
|
+
for plugin in self.plugins:
|
104
|
+
if isinstance(plugin, AutopubPackageManagerPlugin):
|
105
|
+
plugin.publish(repository=repository)
|
106
|
+
|
107
|
+
def _write_artifact(self, release_info: ReleaseInfo) -> None:
|
108
|
+
data = {
|
109
|
+
"hash": self.release_file_hash,
|
110
|
+
"release_type": release_info.release_type,
|
111
|
+
"release_notes": release_info.release_notes,
|
112
|
+
"plugin_data": {
|
113
|
+
key: value
|
114
|
+
for plugin in self.plugins
|
115
|
+
for key, value in plugin.data.items()
|
116
|
+
},
|
117
|
+
}
|
118
|
+
|
119
|
+
self.release_data_file.parent.mkdir(exist_ok=True)
|
120
|
+
self.release_data_file.write_text(json.dumps(data))
|
121
|
+
|
122
|
+
def _deprecated_load(self, release_notes: str) -> ReleaseInfo:
|
123
|
+
# supports loading of old release notes format, which is
|
124
|
+
# deprecated and will be removed in a future release
|
125
|
+
# and looks like this:
|
126
|
+
# Release type: patch
|
127
|
+
# release notes here.
|
128
|
+
|
129
|
+
try:
|
130
|
+
release_info, release_notes = release_notes.split("\n", 1)
|
131
|
+
except ValueError as e:
|
132
|
+
raise ReleaseTypeMissing() from e
|
133
|
+
|
134
|
+
release_info = release_info.lower()
|
135
|
+
|
136
|
+
if not release_info.startswith("release type:"):
|
137
|
+
raise ReleaseTypeMissing()
|
138
|
+
|
139
|
+
release_type = release_info.split(":", 1)[1].strip().lower()
|
140
|
+
|
141
|
+
return ReleaseInfo(
|
142
|
+
release_type=release_type, release_notes=release_notes.strip()
|
143
|
+
)
|
144
|
+
|
145
|
+
def _load_from_frontmatter(self, release_notes: str) -> ReleaseInfo:
|
146
|
+
# supports loading of new release notes format, which looks like this:
|
147
|
+
# ---
|
148
|
+
# release type: patch
|
149
|
+
# ---
|
150
|
+
# release notes here.
|
151
|
+
|
152
|
+
post = frontmatter.loads(release_notes)
|
153
|
+
|
154
|
+
data: dict[str, str] = post.to_dict()
|
155
|
+
|
156
|
+
release_type = data.pop("release type").lower()
|
157
|
+
|
158
|
+
if release_type not in ("major", "minor", "patch"):
|
159
|
+
raise ReleaseTypeInvalid(release_type)
|
160
|
+
|
161
|
+
if post.content.strip() == "":
|
162
|
+
raise ReleaseNotesEmpty()
|
163
|
+
|
164
|
+
return ReleaseInfo(
|
165
|
+
release_type=release_type,
|
166
|
+
release_notes=post.content,
|
167
|
+
additional_info=data,
|
168
|
+
)
|
169
|
+
|
170
|
+
def _validate_release_notes(self, release_notes: str) -> ReleaseInfo:
|
171
|
+
if not release_notes:
|
172
|
+
raise ReleaseFileEmpty()
|
173
|
+
|
174
|
+
try:
|
175
|
+
release_info = self._load_from_frontmatter(release_notes)
|
176
|
+
except KeyError:
|
177
|
+
release_info = self._deprecated_load(release_notes)
|
178
|
+
|
179
|
+
for plugin in self.plugins:
|
180
|
+
plugin.validate_release_notes(release_info)
|
181
|
+
|
182
|
+
return release_info
|
autopub/cli/__init__.py
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
from typing import Optional, TypedDict
|
2
|
+
|
3
|
+
import rich
|
4
|
+
import typer
|
5
|
+
from rich.console import Group
|
6
|
+
from rich.markdown import Markdown
|
7
|
+
from rich.padding import Padding
|
8
|
+
from rich.panel import Panel
|
9
|
+
from typing_extensions import Annotated
|
10
|
+
|
11
|
+
from autopub import Autopub
|
12
|
+
from autopub.cli.plugins import find_plugins
|
13
|
+
from autopub.exceptions import AutopubException
|
14
|
+
|
15
|
+
app = typer.Typer()
|
16
|
+
|
17
|
+
|
18
|
+
class State(TypedDict):
|
19
|
+
plugins: list[str]
|
20
|
+
|
21
|
+
|
22
|
+
state: State = {"plugins": []}
|
23
|
+
|
24
|
+
|
25
|
+
@app.command()
|
26
|
+
def check():
|
27
|
+
"""This commands checks if the current PR has a valid release file."""
|
28
|
+
|
29
|
+
autopub = Autopub(plugins=find_plugins(state["plugins"]))
|
30
|
+
|
31
|
+
try:
|
32
|
+
release_info = autopub.check()
|
33
|
+
except AutopubException as e:
|
34
|
+
rich.print(Panel.fit(f"[red]{e.message}"))
|
35
|
+
|
36
|
+
raise typer.Exit(1) from e
|
37
|
+
else:
|
38
|
+
rich.print(
|
39
|
+
Padding(
|
40
|
+
Group(
|
41
|
+
(
|
42
|
+
"[bold on bright_magenta] Release type: [/] "
|
43
|
+
f"[yellow italic underline]{release_info.release_type}[/]\n"
|
44
|
+
),
|
45
|
+
"[bold on bright_magenta] Release notes: [/]\n",
|
46
|
+
Markdown(release_info.release_notes),
|
47
|
+
"\n---\n\n[green bold]Release file is valid![/] 🚀",
|
48
|
+
),
|
49
|
+
(1, 1),
|
50
|
+
)
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
@app.command()
|
55
|
+
def build():
|
56
|
+
autopub = Autopub(plugins=find_plugins(state["plugins"]))
|
57
|
+
|
58
|
+
try:
|
59
|
+
autopub.build()
|
60
|
+
except AutopubException as e:
|
61
|
+
rich.print(Panel.fit(f"[red]{e.message}"))
|
62
|
+
|
63
|
+
raise typer.Exit(1) from e
|
64
|
+
else:
|
65
|
+
rich.print(Panel.fit("[green]Build succeeded"))
|
66
|
+
|
67
|
+
|
68
|
+
@app.command()
|
69
|
+
def prepare():
|
70
|
+
autopub = Autopub(plugins=find_plugins(state["plugins"]))
|
71
|
+
|
72
|
+
try:
|
73
|
+
autopub.prepare()
|
74
|
+
except AutopubException as e:
|
75
|
+
rich.print(Panel.fit(f"[red]{e.message}"))
|
76
|
+
|
77
|
+
raise typer.Exit(1) from e
|
78
|
+
else:
|
79
|
+
rich.print(Panel.fit("[green]Preparation succeeded"))
|
80
|
+
|
81
|
+
|
82
|
+
@app.command()
|
83
|
+
def publish(
|
84
|
+
repository: Annotated[
|
85
|
+
Optional[str],
|
86
|
+
typer.Option("--repository", "-r", help="Repository to publish to"),
|
87
|
+
] = None,
|
88
|
+
):
|
89
|
+
autopub = Autopub(plugins=find_plugins(state["plugins"]))
|
90
|
+
|
91
|
+
try:
|
92
|
+
autopub.publish(repository=repository)
|
93
|
+
except AutopubException as e:
|
94
|
+
rich.print(Panel.fit(f"[red]{e.message}"))
|
95
|
+
|
96
|
+
raise typer.Exit(1) from e
|
97
|
+
else:
|
98
|
+
rich.print(Panel.fit("[green]Publishing succeeded"))
|
99
|
+
|
100
|
+
|
101
|
+
@app.callback(invoke_without_command=True)
|
102
|
+
def main(
|
103
|
+
plugins: list[str] = typer.Option(
|
104
|
+
[],
|
105
|
+
"--plugin",
|
106
|
+
"-p",
|
107
|
+
help="List of plugins to use",
|
108
|
+
),
|
109
|
+
should_show_version: Annotated[
|
110
|
+
Optional[bool], typer.Option("--version", is_eager=True)
|
111
|
+
] = None,
|
112
|
+
):
|
113
|
+
state["plugins"] = plugins
|
114
|
+
state["plugins"].extend(["bump_version"])
|
115
|
+
|
116
|
+
if should_show_version:
|
117
|
+
from importlib.metadata import version
|
118
|
+
|
119
|
+
print(version("autopub"))
|
120
|
+
|
121
|
+
raise typer.Exit()
|
autopub/cli/plugins.py
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
from importlib import import_module
|
2
|
+
|
3
|
+
from autopub.plugins import AutopubPlugin
|
4
|
+
|
5
|
+
|
6
|
+
def _find_plugin(module: object) -> type[AutopubPlugin] | None:
|
7
|
+
for obj in module.__dict__.values():
|
8
|
+
if (
|
9
|
+
isinstance(obj, type)
|
10
|
+
and issubclass(obj, AutopubPlugin)
|
11
|
+
and obj is not AutopubPlugin
|
12
|
+
):
|
13
|
+
return obj
|
14
|
+
|
15
|
+
return None
|
16
|
+
|
17
|
+
|
18
|
+
def find_plugins(names: list[str]) -> list[type[AutopubPlugin]]:
|
19
|
+
plugins: list[type] = []
|
20
|
+
|
21
|
+
for plugin_name in names:
|
22
|
+
try:
|
23
|
+
# TODO: find plugins outside the autopub namespace
|
24
|
+
plugin_module = import_module(f"autopub.plugins.{plugin_name}")
|
25
|
+
|
26
|
+
plugin_class = _find_plugin(plugin_module)
|
27
|
+
|
28
|
+
if plugin_class is None:
|
29
|
+
print(f"Could not find plugin {plugin_name}")
|
30
|
+
# TODO: raise
|
31
|
+
continue
|
32
|
+
|
33
|
+
plugins.append(plugin_class)
|
34
|
+
except ImportError as e:
|
35
|
+
print(f"Error importing plugin {plugin_name}: {e}")
|
36
|
+
|
37
|
+
return plugins
|
autopub/exceptions.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class AutopubException(Exception):
|
2
|
+
message: str
|
3
|
+
|
4
|
+
def __init__(self) -> None:
|
5
|
+
super().__init__(self.message)
|
6
|
+
|
7
|
+
|
8
|
+
class ReleaseFileNotFound(AutopubException):
|
9
|
+
message = "Release file not found"
|
10
|
+
|
11
|
+
|
12
|
+
class ReleaseFileEmpty(AutopubException):
|
13
|
+
message = "Release file is empty"
|
14
|
+
|
15
|
+
|
16
|
+
class ReleaseNotesEmpty(AutopubException):
|
17
|
+
message = "Release notes are empty"
|
18
|
+
|
19
|
+
|
20
|
+
class ReleaseTypeMissing(AutopubException):
|
21
|
+
message: str = "Release note is missing release type"
|
22
|
+
|
23
|
+
|
24
|
+
class ReleaseTypeInvalid(AutopubException):
|
25
|
+
def __init__(self, release_type: str):
|
26
|
+
self.message = f"Release type {release_type} is invalid"
|
27
|
+
super().__init__()
|
28
|
+
|
29
|
+
|
30
|
+
class NoPackageManagerPluginFound(AutopubException):
|
31
|
+
message = "No package manager plugin found"
|
32
|
+
|
33
|
+
|
34
|
+
class ArtifactNotFound(AutopubException):
|
35
|
+
message = "Artifact not found, did you run `autopub check`?"
|
36
|
+
|
37
|
+
|
38
|
+
class ArtifactHashMismatch(AutopubException):
|
39
|
+
message = "Artifact hash mismatch, did you run `autopub check`?"
|
40
|
+
|
41
|
+
|
42
|
+
class CommandFailed(AutopubException):
|
43
|
+
def __init__(self, command: list[str], returncode: int) -> None:
|
44
|
+
self.message = (
|
45
|
+
f"Command {' '.join(command)} failed with return code {returncode}"
|
46
|
+
)
|
47
|
+
super().__init__()
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import subprocess
|
4
|
+
from typing import Any, Protocol, runtime_checkable
|
5
|
+
|
6
|
+
from autopub.exceptions import AutopubException, CommandFailed
|
7
|
+
from autopub.types import ReleaseInfo
|
8
|
+
|
9
|
+
|
10
|
+
class AutopubPlugin:
|
11
|
+
data: dict[str, object] = {}
|
12
|
+
|
13
|
+
def run_command(self, command: list[str]) -> None:
|
14
|
+
try:
|
15
|
+
subprocess.run(command, check=True)
|
16
|
+
except subprocess.CalledProcessError as e:
|
17
|
+
raise CommandFailed(command=command, returncode=e.returncode) from e
|
18
|
+
|
19
|
+
def prepare(self, release_info: ReleaseInfo) -> None: # pragma: no cover
|
20
|
+
...
|
21
|
+
|
22
|
+
def validate_release_notes(self, release_info: ReleaseInfo): # pragma: no cover
|
23
|
+
...
|
24
|
+
|
25
|
+
def on_release_notes_valid(self, release_info: ReleaseInfo): # pragma: no cover
|
26
|
+
...
|
27
|
+
|
28
|
+
def on_release_notes_invalid(self, exception: AutopubException): # pragma: no cover
|
29
|
+
...
|
30
|
+
|
31
|
+
|
32
|
+
@runtime_checkable
|
33
|
+
class AutopubPackageManagerPlugin(Protocol):
|
34
|
+
def build(self) -> None: # pragma: no cover
|
35
|
+
...
|
36
|
+
|
37
|
+
def publish(
|
38
|
+
self, repository: str | None = None, **kwargs: Any
|
39
|
+
) -> None: # pragma: no cover
|
40
|
+
...
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import pathlib
|
4
|
+
|
5
|
+
import tomlkit
|
6
|
+
from dunamai import Version
|
7
|
+
|
8
|
+
from autopub.plugins import AutopubPlugin
|
9
|
+
from autopub.types import ReleaseInfo
|
10
|
+
|
11
|
+
|
12
|
+
class BumpVersionPlugin(AutopubPlugin):
|
13
|
+
@property
|
14
|
+
def pyproject_config(self) -> tomlkit.TOMLDocument:
|
15
|
+
content = pathlib.Path("pyproject.toml").read_text()
|
16
|
+
|
17
|
+
return tomlkit.parse(content)
|
18
|
+
|
19
|
+
def _get_version(self, config: tomlkit.TOMLDocument) -> str:
|
20
|
+
try:
|
21
|
+
return config["tool"]["poetry"]["version"] # type: ignore
|
22
|
+
except KeyError:
|
23
|
+
return config["project"]["version"] # type: ignore
|
24
|
+
|
25
|
+
def _update_version(self, config: tomlkit.TOMLDocument, new_version: str) -> None:
|
26
|
+
try:
|
27
|
+
config["tool"]["poetry"]["version"] = new_version # type: ignore
|
28
|
+
except KeyError:
|
29
|
+
config["project"]["version"] = new_version # type: ignore
|
30
|
+
|
31
|
+
def prepare(self, release_info: ReleaseInfo) -> None:
|
32
|
+
config = self.pyproject_config
|
33
|
+
|
34
|
+
version = Version(self._get_version(config))
|
35
|
+
|
36
|
+
bump_type = {"major": 0, "minor": 1, "patch": 2}[release_info.release_type]
|
37
|
+
new_version = version.bump(bump_type).serialize()
|
38
|
+
|
39
|
+
self._update_version(config, new_version)
|
40
|
+
|
41
|
+
pathlib.Path("pyproject.toml").write_text(tomlkit.dumps(config)) # type: ignore
|
autopub/plugins/pdm.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from autopub.plugins import AutopubPackageManagerPlugin, AutopubPlugin
|
6
|
+
|
7
|
+
|
8
|
+
class PDMPlugin(AutopubPlugin, AutopubPackageManagerPlugin):
|
9
|
+
def build(self) -> None:
|
10
|
+
self.run_command(["pdm", "build"])
|
11
|
+
|
12
|
+
def publish(self, repository: str | None = None, **kwargs: Any) -> None:
|
13
|
+
additional_args: list[str] = []
|
14
|
+
|
15
|
+
if repository:
|
16
|
+
additional_args += ["--repository", repository]
|
17
|
+
|
18
|
+
self.run_command(["pdm", "publish", *additional_args])
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from autopub.plugins import AutopubPackageManagerPlugin, AutopubPlugin
|
4
|
+
|
5
|
+
|
6
|
+
class PoetryPlugin(AutopubPlugin, AutopubPackageManagerPlugin):
|
7
|
+
def build(self) -> None:
|
8
|
+
self.run_command(["poetry", "build"])
|
9
|
+
|
10
|
+
def publish(self, repository: str | None = None, **kwargs: str) -> None:
|
11
|
+
additional_args: list[str] = []
|
12
|
+
|
13
|
+
if repository:
|
14
|
+
additional_args += ["--repository", repository]
|
15
|
+
|
16
|
+
self.run_command(["poetry", "publish", *additional_args])
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import date
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from autopub.plugins import AutopubPlugin
|
7
|
+
from autopub.types import ReleaseInfo
|
8
|
+
|
9
|
+
# TODO: from config
|
10
|
+
CHANGELOG_HEADER = "========="
|
11
|
+
VERSION_HEADER = "-"
|
12
|
+
|
13
|
+
|
14
|
+
class UpdateChangelogPlugin(AutopubPlugin):
|
15
|
+
@property
|
16
|
+
def changelog_file(self) -> Path:
|
17
|
+
return Path("CHANGELOG.md")
|
18
|
+
|
19
|
+
def post_prepare(self, release_info: ReleaseInfo) -> None:
|
20
|
+
assert release_info.version is not None
|
21
|
+
|
22
|
+
if not self.changelog_file.exists():
|
23
|
+
self.changelog_file.write_text(f"CHANGELOG\n{CHANGELOG_HEADER}\n\n")
|
24
|
+
|
25
|
+
current_date = date.today().strftime("%Y-%m-%d")
|
26
|
+
|
27
|
+
old_changelog_data = ""
|
28
|
+
header = ""
|
29
|
+
|
30
|
+
lines = self.changelog_file.read_text().splitlines()
|
31
|
+
|
32
|
+
for index, line in enumerate(lines):
|
33
|
+
if CHANGELOG_HEADER != line.strip():
|
34
|
+
continue
|
35
|
+
|
36
|
+
old_changelog_data = lines[index + 1 :]
|
37
|
+
header = lines[: index + 1]
|
38
|
+
break
|
39
|
+
|
40
|
+
with self.changelog_file.open("w") as f:
|
41
|
+
f.write("\n".join(header))
|
42
|
+
f.write("\n")
|
43
|
+
|
44
|
+
new_version_header = f"{release_info.version} - {current_date}"
|
45
|
+
|
46
|
+
f.write(f"\n{new_version_header}\n")
|
47
|
+
f.write(f"{VERSION_HEADER * len(new_version_header)}\n\n")
|
48
|
+
f.write(release_info.release_notes)
|
49
|
+
f.write("\n")
|
50
|
+
f.write("\n".join(old_changelog_data))
|
autopub/types.py
ADDED
@@ -1,20 +1,19 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: autopub
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0a1
|
4
4
|
Summary: Automatic package release upon pull request merge
|
5
5
|
Home-page: https://github.com/autopub/autopub
|
6
6
|
License: AGPL-3.0
|
7
7
|
Keywords: automatic,packaging,publish,release,version
|
8
8
|
Author: Justin Mayer
|
9
9
|
Author-email: entroP@gmail.com
|
10
|
-
Requires-Python: >=3.
|
10
|
+
Requires-Python: >=3.8,<4.0
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
12
12
|
Classifier: Environment :: Console
|
13
13
|
Classifier: Intended Audience :: Developers
|
14
14
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
15
15
|
Classifier: Operating System :: OS Independent
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
18
17
|
Classifier: Programming Language :: Python :: 3.8
|
19
18
|
Classifier: Programming Language :: Python :: 3.9
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
@@ -27,8 +26,12 @@ Requires-Dist: build (>=0.10.0,<0.11.0)
|
|
27
26
|
Requires-Dist: dunamai (>=1.17.0,<2.0.0)
|
28
27
|
Requires-Dist: githubrelease (>=1.5.9,<2.0.0) ; extra == "github"
|
29
28
|
Requires-Dist: httpx (==0.16.1) ; extra == "github"
|
29
|
+
Requires-Dist: python-frontmatter (>=1.0.0,<2.0.0)
|
30
|
+
Requires-Dist: rich (>=12.5.1,<13.0.0)
|
31
|
+
Requires-Dist: time-machine (>=2.13.0,<3.0.0)
|
30
32
|
Requires-Dist: tomlkit (>=0.5,<2.0)
|
31
33
|
Requires-Dist: twine (>=4.0.2,<5.0.0)
|
34
|
+
Requires-Dist: typer (>=0.9.0,<0.10.0)
|
32
35
|
Project-URL: Issue Tracker, https://github.com/autopub/autopub/issues
|
33
36
|
Project-URL: Repository, https://github.com/autopub/autopub
|
34
37
|
Description-Content-Type: text/markdown
|
@@ -87,3 +90,4 @@ For systems such as Travis CI in which only one deployment step is permitted, th
|
|
87
90
|
[Travis CI]: https://travis-ci.org
|
88
91
|
[build]: https://pypa-build.readthedocs.io
|
89
92
|
[Twine]: https://twine.readthedocs.io/
|
93
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
autopub/__init__.py,sha256=zcNp8aJE_jo-D5xvDuWNZ-ZK6c67NqbTaKtwfGJWtZs,5576
|
2
|
+
autopub/cli/__init__.py,sha256=XG1QVlKPv7svwFOcuosBzXr9AIy9xKDTuOPfI14dtBs,2980
|
3
|
+
autopub/cli/plugins.py,sha256=x80BsIpY81o6rv3MJTH2oTAZkqnAYp3MkjUtpPfxGfI,1021
|
4
|
+
autopub/exceptions.py,sha256=JYn8sIYWCedhtO3XfftZ0M5M_rAPxiGQ4MGbWUaTYwE,1243
|
5
|
+
autopub/plugins/__init__.py,sha256=HtnCcxvZqTUC79vs5aVpNHenRCNuTPpz1gAu2OYkCzg,1153
|
6
|
+
autopub/plugins/bump_version.py,sha256=p-_q80pJDCUy-fKhp-9_9HWTWC2pKe2lFJnimuHHH7w,1338
|
7
|
+
autopub/plugins/pdm.py,sha256=Pczye06fKg8_HMJDkEfMXQyvao9rZ7sqzTHFd6lLEpU,532
|
8
|
+
autopub/plugins/poetry.py,sha256=d2LvW9RI7ZB3reBOXbcp1mqWmzQ06Uyg_T-MxTvlSBg,517
|
9
|
+
autopub/plugins/update_changelog.py,sha256=MmDBGFs6v8zPn3NCfUumpF8YODhg5x2AdjyTzgq5bYQ,1456
|
10
|
+
autopub/types.py,sha256=FcRH4l27nrkQFUQrbAKxNzPPJfzg_0DExZYsCu9Jdzk,249
|
11
|
+
autopub-1.0.0a1.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
12
|
+
autopub-1.0.0a1.dist-info/METADATA,sha256=hJImNEz5eaUoQoG1Ziu44EuCP5Wwjo-s9NBUiFWLiMk,3757
|
13
|
+
autopub-1.0.0a1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
14
|
+
autopub-1.0.0a1.dist-info/entry_points.txt,sha256=oeTav5NgCxif6mcZ_HeVGgGv5LzS4DwdI01nr4bO1IM,43
|
15
|
+
autopub-1.0.0a1.dist-info/RECORD,,
|
autopub/autopub.py
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
import argparse
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
|
5
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
6
|
-
|
7
|
-
from build_release import build_release
|
8
|
-
from check_release import check_release
|
9
|
-
from commit_release import git_commit_and_push
|
10
|
-
from create_github_release import create_github_release
|
11
|
-
from deploy_release import deploy_release
|
12
|
-
from prepare_release import prepare_release
|
13
|
-
from publish_release import publish_release
|
14
|
-
|
15
|
-
|
16
|
-
def check(arguments):
|
17
|
-
check_release()
|
18
|
-
|
19
|
-
|
20
|
-
def prepare(arguments):
|
21
|
-
prepare_release()
|
22
|
-
|
23
|
-
|
24
|
-
def build(arguments):
|
25
|
-
build_release()
|
26
|
-
|
27
|
-
|
28
|
-
def commit(arguments):
|
29
|
-
git_commit_and_push()
|
30
|
-
|
31
|
-
|
32
|
-
def githubrelease(arguments):
|
33
|
-
create_github_release()
|
34
|
-
|
35
|
-
|
36
|
-
def publish(arguments):
|
37
|
-
publish_release()
|
38
|
-
|
39
|
-
|
40
|
-
def deploy(arguments):
|
41
|
-
deploy_release()
|
42
|
-
|
43
|
-
|
44
|
-
def parse_arguments():
|
45
|
-
try:
|
46
|
-
version = __import__("pkg_resources").get_distribution("autopub").version
|
47
|
-
except Exception:
|
48
|
-
version = "unknown"
|
49
|
-
|
50
|
-
parser = argparse.ArgumentParser()
|
51
|
-
parser.add_argument("--version", action="version", version=version)
|
52
|
-
|
53
|
-
subparsers = parser.add_subparsers()
|
54
|
-
|
55
|
-
check_parser = subparsers.add_parser("check")
|
56
|
-
check_parser.set_defaults(func=check)
|
57
|
-
|
58
|
-
prepare_parser = subparsers.add_parser("prepare")
|
59
|
-
prepare_parser.set_defaults(func=prepare)
|
60
|
-
|
61
|
-
build_parser = subparsers.add_parser("build")
|
62
|
-
build_parser.set_defaults(func=build)
|
63
|
-
|
64
|
-
commit_parser = subparsers.add_parser("commit")
|
65
|
-
commit_parser.set_defaults(func=commit)
|
66
|
-
|
67
|
-
githubrelease_parser = subparsers.add_parser("githubrelease")
|
68
|
-
githubrelease_parser.set_defaults(func=githubrelease)
|
69
|
-
|
70
|
-
publish_parser = subparsers.add_parser("publish")
|
71
|
-
publish_parser.set_defaults(func=publish)
|
72
|
-
|
73
|
-
deploy_parser = subparsers.add_parser("deploy")
|
74
|
-
deploy_parser.set_defaults(func=deploy)
|
75
|
-
|
76
|
-
arguments = parser.parse_args()
|
77
|
-
|
78
|
-
return arguments
|
79
|
-
|
80
|
-
|
81
|
-
def main():
|
82
|
-
arguments = parse_arguments()
|
83
|
-
arguments.func(arguments)
|
autopub/base.py
DELETED
@@ -1,160 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import re
|
3
|
-
import subprocess
|
4
|
-
import sys
|
5
|
-
from pathlib import Path
|
6
|
-
|
7
|
-
from tomlkit import parse
|
8
|
-
|
9
|
-
|
10
|
-
def dict_get(_dict, keys, default=None):
|
11
|
-
"""Query nested dictionary with list of keys, returning None if not found."""
|
12
|
-
for key in keys:
|
13
|
-
if isinstance(_dict, dict):
|
14
|
-
_dict = _dict.get(key, default)
|
15
|
-
else:
|
16
|
-
return default
|
17
|
-
return _dict
|
18
|
-
|
19
|
-
|
20
|
-
# Determine CI/CD environment
|
21
|
-
|
22
|
-
if os.environ.get("CIRCLECI"):
|
23
|
-
CI_SYSTEM = "circleci"
|
24
|
-
CIRCLE_PROJECT_USERNAME = os.environ.get("CIRCLE_PROJECT_USERNAME")
|
25
|
-
CIRCLE_PROJECT_REPONAME = os.environ.get("CIRCLE_PROJECT_REPONAME")
|
26
|
-
REPO_SLUG = f"{CIRCLE_PROJECT_USERNAME}/{CIRCLE_PROJECT_REPONAME}"
|
27
|
-
elif os.environ.get("GITHUB_ACTIONS") == "true":
|
28
|
-
CI_SYSTEM = "github"
|
29
|
-
REPO_SLUG = os.environ.get("GITHUB_REPOSITORY")
|
30
|
-
elif os.environ.get("TRAVIS"):
|
31
|
-
CI_SYSTEM = "travis"
|
32
|
-
REPO_SLUG = os.environ.get("TRAVIS_REPO_SLUG")
|
33
|
-
else:
|
34
|
-
CI_SYSTEM = os.environ.get("CI_SYSTEM", None)
|
35
|
-
REPO_SLUG = os.environ.get("REPO_SLUG", None)
|
36
|
-
|
37
|
-
# Project root and file name configuration
|
38
|
-
|
39
|
-
PROJECT_ROOT = os.environ.get("PROJECT_ROOT")
|
40
|
-
PYPROJECT_FILE_NAME = os.environ.get("PYPROJECT_FILE_NAME", "pyproject.toml")
|
41
|
-
|
42
|
-
if PROJECT_ROOT:
|
43
|
-
ROOT = Path(PROJECT_ROOT)
|
44
|
-
else:
|
45
|
-
ROOT = Path(
|
46
|
-
subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
|
47
|
-
.decode("ascii")
|
48
|
-
.strip()
|
49
|
-
)
|
50
|
-
|
51
|
-
PYPROJECT_FILE = ROOT / PYPROJECT_FILE_NAME
|
52
|
-
|
53
|
-
# Retrieve configuration from pyproject file
|
54
|
-
|
55
|
-
if os.path.exists(PYPROJECT_FILE):
|
56
|
-
config = parse(open(PYPROJECT_FILE).read())
|
57
|
-
else:
|
58
|
-
print(f"Could not find pyproject file at: {PYPROJECT_FILE}")
|
59
|
-
sys.exit(1)
|
60
|
-
|
61
|
-
PROJECT_NAME = dict_get(config, ["tool", "autopub", "project-name"])
|
62
|
-
if not PROJECT_NAME:
|
63
|
-
PROJECT_NAME = dict_get(config, ["tool", "poetry", "name"])
|
64
|
-
if not PROJECT_NAME:
|
65
|
-
PROJECT_NAME = dict_get(config, ["project", "name"])
|
66
|
-
if not PROJECT_NAME:
|
67
|
-
print(
|
68
|
-
"Could not determine project name. Under the pyproject file's "
|
69
|
-
'[tool.autopub] header, add:\nproject-name = "YourProjectName"'
|
70
|
-
)
|
71
|
-
sys.exit(1)
|
72
|
-
|
73
|
-
RELEASE_FILE_NAME = dict_get(
|
74
|
-
config, ["tool", "autopub", "release-file"], default="RELEASE.md"
|
75
|
-
)
|
76
|
-
RELEASE_FILE = ROOT / RELEASE_FILE_NAME
|
77
|
-
|
78
|
-
CHANGELOG_FILE_NAME = dict_get(
|
79
|
-
config, ["tool", "autopub", "changelog-file"], default="CHANGELOG.md"
|
80
|
-
)
|
81
|
-
CHANGELOG_FILE = ROOT / CHANGELOG_FILE_NAME
|
82
|
-
|
83
|
-
CHANGELOG_HEADER = dict_get(
|
84
|
-
config, ["tool", "autopub", "changelog-header"], default="========="
|
85
|
-
)
|
86
|
-
|
87
|
-
VERSION_HEADER = dict_get(config, ["tool", "autopub", "version-header"], default="-")
|
88
|
-
VERSION_STRINGS = dict_get(config, ["tool", "autopub", "version-strings"], default=[])
|
89
|
-
|
90
|
-
TAG_PREFIX = dict_get(config, ["tool", "autopub", "tag-prefix"], default="")
|
91
|
-
|
92
|
-
PYPI_URL = dict_get(config, ["tool", "autopub", "pypi-url"])
|
93
|
-
|
94
|
-
# Git configuration
|
95
|
-
|
96
|
-
GIT_USERNAME = dict_get(config, ["tool", "autopub", "git-username"])
|
97
|
-
GIT_EMAIL = dict_get(config, ["tool", "autopub", "git-email"])
|
98
|
-
|
99
|
-
# GitHub
|
100
|
-
|
101
|
-
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", None)
|
102
|
-
APPEND_GITHUB_CONTRIBUTOR = dict_get(
|
103
|
-
config, ["tool", "autopub", "append-github-contributor"], False
|
104
|
-
)
|
105
|
-
|
106
|
-
|
107
|
-
def run_process(popenargs, encoding="utf-8", env=None):
|
108
|
-
if env is not None:
|
109
|
-
env = {**os.environ, **env}
|
110
|
-
try:
|
111
|
-
return subprocess.check_output(popenargs, encoding=encoding, env=env).strip()
|
112
|
-
except subprocess.CalledProcessError as e:
|
113
|
-
print(e.output, file=sys.stderr)
|
114
|
-
sys.exit(1)
|
115
|
-
|
116
|
-
|
117
|
-
def git(popenargs):
|
118
|
-
# Do not decode ASCII for commit messages so emoji are preserved
|
119
|
-
return subprocess.check_output(["git", *popenargs])
|
120
|
-
|
121
|
-
|
122
|
-
def check_exit_code(popenargs):
|
123
|
-
return subprocess.call(popenargs, shell=True)
|
124
|
-
|
125
|
-
|
126
|
-
def get_project_version():
|
127
|
-
# Backwards compat: Try poetry first and then fall "back" to standards
|
128
|
-
version = dict_get(config, ["tool", "poetry", "version"])
|
129
|
-
if version is None:
|
130
|
-
return dict_get(config, ["project", "version"])
|
131
|
-
else:
|
132
|
-
return version
|
133
|
-
|
134
|
-
|
135
|
-
def get_release_info():
|
136
|
-
RELEASE_TYPE_REGEX = re.compile(r"^[Rr]elease [Tt]ype: (major|minor|patch)$")
|
137
|
-
|
138
|
-
with open(RELEASE_FILE, "r") as f:
|
139
|
-
line = f.readline()
|
140
|
-
match = RELEASE_TYPE_REGEX.match(line)
|
141
|
-
|
142
|
-
if not match:
|
143
|
-
print(
|
144
|
-
"The RELEASE file should start with 'Release type' and "
|
145
|
-
"specify one of the following values: major, minor, or patch."
|
146
|
-
)
|
147
|
-
sys.exit(1)
|
148
|
-
|
149
|
-
type_ = match.group(1)
|
150
|
-
changelog = "".join([l for l in f.readlines()]).strip()
|
151
|
-
|
152
|
-
return type_, changelog
|
153
|
-
|
154
|
-
|
155
|
-
def configure_git():
|
156
|
-
if not GIT_USERNAME or not GIT_EMAIL:
|
157
|
-
print("git-username and git-email must be defined in the pyproject file")
|
158
|
-
sys.exit(1)
|
159
|
-
git(["config", "user.name", GIT_USERNAME])
|
160
|
-
git(["config", "user.email", GIT_EMAIL])
|
autopub/build_release.py
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
5
|
-
|
6
|
-
from base import git, run_process
|
7
|
-
|
8
|
-
|
9
|
-
def build_release():
|
10
|
-
env = None
|
11
|
-
if "SOURCE_DATE_EPOCH" not in os.environ:
|
12
|
-
ctime = git(["log", "-1", "--pretty=%ct"]).decode().strip()
|
13
|
-
env = {"SOURCE_DATE_EPOCH": ctime}
|
14
|
-
run_process([sys.executable, "-m", "build"], env=env)
|
autopub/check_release.py
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
5
|
-
|
6
|
-
from base import CI_SYSTEM, RELEASE_FILE, run_process
|
7
|
-
|
8
|
-
|
9
|
-
def check_release():
|
10
|
-
needs_release = os.path.exists(RELEASE_FILE)
|
11
|
-
if not needs_release:
|
12
|
-
print("Not releasing a new version because there is no RELEASE file.")
|
13
|
-
if CI_SYSTEM == "circleci":
|
14
|
-
run_process(["circleci", "step", "halt"])
|
15
|
-
elif CI_SYSTEM == "travis":
|
16
|
-
sys.exit(1)
|
17
|
-
if CI_SYSTEM == "github":
|
18
|
-
with open(os.path.expandvars("$GITHUB_OUTPUT"), "a") as f:
|
19
|
-
f.write("autopub_release={}\n".format("true" if needs_release else "false"))
|
autopub/commit_release.py
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
5
|
-
|
6
|
-
from base import (
|
7
|
-
get_project_version,
|
8
|
-
git,
|
9
|
-
configure_git,
|
10
|
-
PROJECT_NAME,
|
11
|
-
PYPROJECT_FILE_NAME,
|
12
|
-
CHANGELOG_FILE_NAME,
|
13
|
-
RELEASE_FILE_NAME,
|
14
|
-
VERSION_STRINGS,
|
15
|
-
)
|
16
|
-
|
17
|
-
|
18
|
-
def git_commit_and_push():
|
19
|
-
configure_git()
|
20
|
-
|
21
|
-
version = get_project_version()
|
22
|
-
|
23
|
-
git(["add", PYPROJECT_FILE_NAME])
|
24
|
-
git(["add", CHANGELOG_FILE_NAME])
|
25
|
-
|
26
|
-
if VERSION_STRINGS:
|
27
|
-
for version_file in VERSION_STRINGS:
|
28
|
-
git(["add", version_file])
|
29
|
-
|
30
|
-
git(["rm", "--cached", RELEASE_FILE_NAME])
|
31
|
-
|
32
|
-
git(["commit", "-m", f"Release {PROJECT_NAME} {version}"])
|
33
|
-
git(["push", "origin", "HEAD"])
|
autopub/create_github_release.py
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
import time
|
4
|
-
|
5
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
6
|
-
|
7
|
-
from base import (
|
8
|
-
run_process,
|
9
|
-
check_exit_code,
|
10
|
-
get_project_version,
|
11
|
-
configure_git,
|
12
|
-
PROJECT_NAME,
|
13
|
-
REPO_SLUG,
|
14
|
-
TAG_PREFIX,
|
15
|
-
get_release_info,
|
16
|
-
)
|
17
|
-
|
18
|
-
|
19
|
-
def create_github_release():
|
20
|
-
try:
|
21
|
-
from github_release import gh_release_create, gh_asset_upload
|
22
|
-
except ModuleNotFoundError:
|
23
|
-
print("Cannot create GitHub release due to missing dependency: github_release")
|
24
|
-
sys.exit(1)
|
25
|
-
|
26
|
-
configure_git()
|
27
|
-
version = get_project_version()
|
28
|
-
tag = f"{TAG_PREFIX}{version}"
|
29
|
-
|
30
|
-
if not version:
|
31
|
-
print("Unable to determine the current version")
|
32
|
-
sys.exit(1)
|
33
|
-
|
34
|
-
tag_exists = (
|
35
|
-
check_exit_code([f'git show-ref --tags --quiet --verify -- "refs/tags/{tag}"'])
|
36
|
-
== 0
|
37
|
-
)
|
38
|
-
|
39
|
-
if not tag_exists:
|
40
|
-
run_process(["git", "tag", tag])
|
41
|
-
run_process(["git", "push", "--tags"])
|
42
|
-
|
43
|
-
_, changelog = get_release_info()
|
44
|
-
|
45
|
-
gh_release_create(
|
46
|
-
REPO_SLUG,
|
47
|
-
tag,
|
48
|
-
publish=True,
|
49
|
-
name=f"{PROJECT_NAME} {version}",
|
50
|
-
body=changelog,
|
51
|
-
)
|
52
|
-
|
53
|
-
# give some time to the API to get updated
|
54
|
-
# not sure if this is the proper way to fix the issue
|
55
|
-
# ideally the githubrelease package shouldn't need
|
56
|
-
# to do another API call to get the release since it
|
57
|
-
# should be returned by the create API.
|
58
|
-
# anyway, this fix might be good enough for the time being
|
59
|
-
|
60
|
-
time.sleep(2)
|
61
|
-
|
62
|
-
gh_asset_upload(
|
63
|
-
REPO_SLUG,
|
64
|
-
tag,
|
65
|
-
pattern="dist/*",
|
66
|
-
)
|
autopub/deploy_release.py
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sys
|
3
|
-
|
4
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
5
|
-
|
6
|
-
from build_release import build_release
|
7
|
-
from create_github_release import create_github_release
|
8
|
-
from commit_release import git_commit_and_push
|
9
|
-
from prepare_release import prepare_release
|
10
|
-
from publish_release import publish_release
|
11
|
-
|
12
|
-
|
13
|
-
def deploy_release():
|
14
|
-
prepare_release()
|
15
|
-
build_release()
|
16
|
-
git_commit_and_push()
|
17
|
-
create_github_release()
|
18
|
-
publish_release()
|
autopub/github_contributor.py
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import subprocess
|
3
|
-
import sys
|
4
|
-
|
5
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
6
|
-
|
7
|
-
from base import GITHUB_TOKEN, REPO_SLUG, APPEND_GITHUB_CONTRIBUTOR
|
8
|
-
|
9
|
-
|
10
|
-
def append_github_contributor(file):
|
11
|
-
if not APPEND_GITHUB_CONTRIBUTOR:
|
12
|
-
return
|
13
|
-
|
14
|
-
try:
|
15
|
-
import httpx
|
16
|
-
except ModuleNotFoundError:
|
17
|
-
print("Cannot append the GitHub contributor due to missing dependency: httpx")
|
18
|
-
sys.exit(1)
|
19
|
-
|
20
|
-
org, repo = REPO_SLUG.split("/")
|
21
|
-
current_commit = (
|
22
|
-
subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()
|
23
|
-
)
|
24
|
-
|
25
|
-
response = httpx.post(
|
26
|
-
"https://api.github.com/graphql",
|
27
|
-
json={
|
28
|
-
"query": """query Contributor(
|
29
|
-
$owner: String!
|
30
|
-
$name: String!
|
31
|
-
$commit: GitObjectID!
|
32
|
-
) {
|
33
|
-
repository(owner: $owner, name: $name) {
|
34
|
-
object(oid: $commit) {
|
35
|
-
__typename
|
36
|
-
... on Commit {
|
37
|
-
associatedPullRequests(first: 1) {
|
38
|
-
nodes {
|
39
|
-
number
|
40
|
-
author {
|
41
|
-
__typename
|
42
|
-
login
|
43
|
-
... on User {
|
44
|
-
name
|
45
|
-
}
|
46
|
-
}
|
47
|
-
}
|
48
|
-
}
|
49
|
-
}
|
50
|
-
}
|
51
|
-
}
|
52
|
-
}""",
|
53
|
-
"variables": {"owner": org, "name": repo, "commit": current_commit},
|
54
|
-
},
|
55
|
-
headers={
|
56
|
-
"Content-Type": "application/json",
|
57
|
-
"Authorization": f"Bearer {GITHUB_TOKEN}",
|
58
|
-
},
|
59
|
-
)
|
60
|
-
|
61
|
-
payload = response.json()
|
62
|
-
commit = payload["data"]["repository"]["object"]
|
63
|
-
|
64
|
-
if not commit:
|
65
|
-
return
|
66
|
-
|
67
|
-
prs = commit["associatedPullRequests"]["nodes"]
|
68
|
-
|
69
|
-
if not prs:
|
70
|
-
return
|
71
|
-
|
72
|
-
pr = prs[0]
|
73
|
-
|
74
|
-
pr_number = pr["number"]
|
75
|
-
pr_author_username = pr["author"]["login"]
|
76
|
-
pr_author_fullname = pr["author"].get("name", "")
|
77
|
-
|
78
|
-
file.write("\n")
|
79
|
-
file.write("\n")
|
80
|
-
file.write(
|
81
|
-
f"Contributed by [{pr_author_fullname or pr_author_username}](https://github.com/{pr_author_username}) via [PR #{pr_number}](https://github.com/{REPO_SLUG}/pull/{pr_number}/)"
|
82
|
-
)
|
83
|
-
file.write("\n")
|
autopub/prepare_release.py
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import re
|
3
|
-
import sys
|
4
|
-
|
5
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
6
|
-
|
7
|
-
from datetime import datetime
|
8
|
-
|
9
|
-
import tomlkit
|
10
|
-
from base import (
|
11
|
-
CHANGELOG_FILE,
|
12
|
-
CHANGELOG_HEADER,
|
13
|
-
PYPROJECT_FILE,
|
14
|
-
ROOT,
|
15
|
-
VERSION_HEADER,
|
16
|
-
VERSION_STRINGS,
|
17
|
-
configure_git,
|
18
|
-
dict_get,
|
19
|
-
get_project_version,
|
20
|
-
get_release_info,
|
21
|
-
)
|
22
|
-
from dunamai import Version
|
23
|
-
from github_contributor import append_github_contributor
|
24
|
-
|
25
|
-
|
26
|
-
def update_version_strings(file_path, new_version):
|
27
|
-
version_regex = re.compile(r"(^_*?version_*?\s*=\s*['\"])(\d+\.\d+\.\d+)", re.M)
|
28
|
-
with open(file_path, "r+") as f:
|
29
|
-
content = f.read()
|
30
|
-
f.seek(0)
|
31
|
-
f.write(
|
32
|
-
re.sub(
|
33
|
-
version_regex,
|
34
|
-
lambda match: "{}{}".format(match.group(1), new_version),
|
35
|
-
content,
|
36
|
-
)
|
37
|
-
)
|
38
|
-
f.truncate()
|
39
|
-
|
40
|
-
|
41
|
-
def prepare_release():
|
42
|
-
configure_git()
|
43
|
-
|
44
|
-
type_, release_changelog = get_release_info()
|
45
|
-
|
46
|
-
version = Version(get_project_version())
|
47
|
-
new_version = version.bump({"major": 0, "minor": 1, "patch": 2}[type_]).serialize()
|
48
|
-
|
49
|
-
with open(PYPROJECT_FILE, "r") as f:
|
50
|
-
config = tomlkit.load(f)
|
51
|
-
|
52
|
-
poetry = dict_get(config, ["tool", "poetry", "version"])
|
53
|
-
if poetry:
|
54
|
-
config["tool"]["poetry"]["version"] = new_version
|
55
|
-
else:
|
56
|
-
config["project"]["version"] = new_version
|
57
|
-
|
58
|
-
with open(PYPROJECT_FILE, "w") as f:
|
59
|
-
config = tomlkit.dump(config, f)
|
60
|
-
|
61
|
-
if VERSION_STRINGS:
|
62
|
-
for version_file in VERSION_STRINGS:
|
63
|
-
file_path = ROOT / version_file
|
64
|
-
update_version_strings(file_path, new_version)
|
65
|
-
|
66
|
-
current_date = datetime.utcnow().strftime("%Y-%m-%d")
|
67
|
-
|
68
|
-
old_changelog_data = ""
|
69
|
-
header = ""
|
70
|
-
|
71
|
-
if not CHANGELOG_FILE.is_file():
|
72
|
-
with open(CHANGELOG_FILE, "a+") as f:
|
73
|
-
f.write(f"CHANGELOG\n{CHANGELOG_HEADER}\n\n")
|
74
|
-
|
75
|
-
with open(CHANGELOG_FILE, "r") as f:
|
76
|
-
lines = f.readlines()
|
77
|
-
|
78
|
-
for index, line in enumerate(lines):
|
79
|
-
if CHANGELOG_HEADER != line.strip():
|
80
|
-
continue
|
81
|
-
|
82
|
-
old_changelog_data = lines[index + 1 :]
|
83
|
-
header = lines[: index + 1]
|
84
|
-
break
|
85
|
-
|
86
|
-
with open(CHANGELOG_FILE, "w") as f:
|
87
|
-
f.write("".join(header))
|
88
|
-
|
89
|
-
new_version_header = f"{new_version} - {current_date}"
|
90
|
-
|
91
|
-
f.write(f"\n{new_version_header}\n")
|
92
|
-
f.write(f"{VERSION_HEADER * len(new_version_header)}\n\n")
|
93
|
-
|
94
|
-
f.write(release_changelog)
|
95
|
-
append_github_contributor(f)
|
96
|
-
f.write("\n")
|
97
|
-
|
98
|
-
f.write("".join(old_changelog_data))
|
autopub/publish_release.py
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
import glob
|
2
|
-
import os
|
3
|
-
import sys
|
4
|
-
|
5
|
-
sys.path.append(os.path.dirname(__file__)) # noqa
|
6
|
-
|
7
|
-
from base import PYPI_URL, run_process
|
8
|
-
|
9
|
-
|
10
|
-
def publish_release():
|
11
|
-
env = None
|
12
|
-
if PYPI_URL:
|
13
|
-
env = {"TWINE_REPOSITORY_URL": PYPI_URL}
|
14
|
-
dists = glob.glob("dist/*")
|
15
|
-
run_process(
|
16
|
-
[sys.executable, "-m", "twine", "upload", "--non-interactive", *dists], env=env
|
17
|
-
)
|
autopub-0.3.0.dist-info/RECORD
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
autopub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
autopub/autopub.py,sha256=uY-CGv6NIamoZYdv3U5oA4GDJZ-MBSmn2v9X5YRjn6w,1908
|
3
|
-
autopub/base.py,sha256=DUldASRwL8F3nWY5jXnzW0bY_4RIClvOVIK94EBxJyw,4794
|
4
|
-
autopub/build_release.py,sha256=UM9AbDHJWn4FpkeTJfm6PcAOMu1ma_xArTKk9202H4w,361
|
5
|
-
autopub/check_release.py,sha256=XcoX067xrJ1B1A7z0Y50Lj9zt4ELLEKjWRDf318y9fg,641
|
6
|
-
autopub/commit_release.py,sha256=Tz-TToqEO9AaWz5HUCNrQV3mlusdyI3HaxmwvHsPIT4,680
|
7
|
-
autopub/create_github_release.py,sha256=6jjp-7cU4ERZjNVpp8tR7U6k-hUmB7VCzC9JiBfKu70,1558
|
8
|
-
autopub/deploy_release.py,sha256=J0hkMhLaO-ktKMjNa-Zu1pVngx_fD_nM_dzC3UTWJM4,447
|
9
|
-
autopub/github_contributor.py,sha256=z49DxFGuvjMimUbKBP9BtsYmNLGuEW22tsPZkL_qL-0,2466
|
10
|
-
autopub/prepare_release.py,sha256=Ku4ZDXeTRYpNwqAY02GYzTB2umsPlZUFs5DZUzA6jho,2537
|
11
|
-
autopub/publish_release.py,sha256=xxHI2SiXIFrJPRUP5EVUWrsozJIW8HiIgSLDgzt6s2U,374
|
12
|
-
autopub-0.3.0.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
13
|
-
autopub-0.3.0.dist-info/METADATA,sha256=dWx1JeRanG58-PZTxBGRqH7Mrvr_QeNHIuVd_TzL9UA,3629
|
14
|
-
autopub-0.3.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
15
|
-
autopub-0.3.0.dist-info/entry_points.txt,sha256=dneC-sBNh-ntAab9sb46B2D6zW7GGvXHHBtIGyknjnA,48
|
16
|
-
autopub-0.3.0.dist-info/RECORD,,
|
File without changes
|