autopub 0.3.0__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
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.
- 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
|