afterpython 0.3.18__tar.gz → 0.4.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.
- {afterpython-0.3.18 → afterpython-0.4.0}/PKG-INFO +21 -18
- {afterpython-0.3.18 → afterpython-0.4.0}/README.md +4 -4
- afterpython-0.4.0/pyproject.toml +73 -0
- afterpython-0.3.18/pyproject.toml → afterpython-0.4.0/pyproject.toml.orig +23 -28
- afterpython-0.4.0/src/afterpython/_typing.py +4 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/marimo_notebook.py +7 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/build.py +3 -3
- afterpython-0.4.0/src/afterpython/cli/commands/bump.py +333 -0
- afterpython-0.4.0/src/afterpython/cli/commands/changelog.py +45 -0
- afterpython-0.4.0/src/afterpython/cli/commands/check.py +43 -0
- afterpython-0.4.0/src/afterpython/cli/commands/commit_msg.py +115 -0
- afterpython-0.4.0/src/afterpython/cli/commands/init.py +344 -0
- afterpython-0.3.18/src/afterpython/cli/commands/commitizen.py → afterpython-0.4.0/src/afterpython/cli/commands/lint.py +7 -10
- afterpython-0.3.18/src/afterpython/cli/commands/pre_commit.py → afterpython-0.4.0/src/afterpython/cli/commands/prek.py +8 -8
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/release.py +39 -10
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/start.py +6 -4
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/update.py +8 -7
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/main.py +9 -9
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/pcu.py +49 -43
- afterpython-0.4.0/src/afterpython/templates/afterpython-template.toml +33 -0
- afterpython-0.4.0/src/afterpython/templates/ci-workflow-template.yml +251 -0
- afterpython-0.4.0/src/afterpython/templates/cliff-template.toml +111 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/templates/deploy-workflow-template.yml +13 -17
- afterpython-0.4.0/src/afterpython/templates/pre-commit-config-template.yaml +68 -0
- afterpython-0.4.0/src/afterpython/templates/release-workflow-template.yml +107 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/templates/ruff-template.toml +8 -1
- afterpython-0.4.0/src/afterpython/templates/ty-template.toml +19 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/_afterpython.py +31 -21
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/_git.py +56 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/branch_rules.py +3 -1
- afterpython-0.4.0/src/afterpython/tools/prek.py +94 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/utils.py +1 -1
- afterpython-0.3.18/src/afterpython/_typing.py +0 -4
- afterpython-0.3.18/src/afterpython/cli/commands/bump.py +0 -118
- afterpython-0.3.18/src/afterpython/cli/commands/check.py +0 -36
- afterpython-0.3.18/src/afterpython/cli/commands/commit.py +0 -52
- afterpython-0.3.18/src/afterpython/cli/commands/init.py +0 -161
- afterpython-0.3.18/src/afterpython/templates/ci-workflow-template.yml +0 -220
- afterpython-0.3.18/src/afterpython/templates/cz-template.toml +0 -7
- afterpython-0.3.18/src/afterpython/templates/pre-commit-config-template.yaml +0 -49
- afterpython-0.3.18/src/afterpython/templates/release-workflow-template.yml +0 -56
- afterpython-0.3.18/src/afterpython/tools/commitizen.py +0 -13
- afterpython-0.3.18/src/afterpython/tools/pre_commit.py +0 -37
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/__init__.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/_io/toml.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/_io/yaml.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/_paths.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/__init__.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/build_pdoc.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/content_json.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/faq_json.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/index_md.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/jupyter_notebook.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/llms_txt.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/markdown.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/metadata.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/builders/url_md.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/__init__.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/add.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/clean.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/dev.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/format.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/init_branch_rules.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/install.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/lock.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/preview.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/remove.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/serve.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/cli/commands/sync.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/const.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/main.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/py.typed +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/templates/dependabot-template.yml +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/templates/pdoc/frame.html.jinja2 +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/github_actions.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/myst.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/tools/pyproject.py +0 -0
- {afterpython-0.3.18 → afterpython-0.4.0}/src/afterpython/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: afterpython
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI tool to streamline Python package maintenance and generate a dedicated project website
|
|
5
5
|
Keywords: package-maintenance,project-website,static-site-generator,developer-tools,documentation,cli-tool,ci/cd,mystmd,semantic-release,python-packaging
|
|
6
6
|
Author: Stephen Yau
|
|
@@ -11,27 +11,30 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: Topic :: Software Development :: Build Tools
|
|
12
12
|
Classifier: Topic :: Software Development :: Documentation
|
|
13
13
|
Classifier: Typing :: Typed
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: click>=8.5.0
|
|
18
18
|
Requires-Dist: trogon>=0.6.0
|
|
19
|
-
Requires-Dist: httpx2>=2.
|
|
20
|
-
Requires-Dist: tomlkit>=0.15.
|
|
19
|
+
Requires-Dist: httpx2>=2.13.1
|
|
20
|
+
Requires-Dist: tomlkit>=0.15.1
|
|
21
21
|
Requires-Dist: ruamel-yaml>=0.19.1
|
|
22
|
-
Requires-Dist: gitpython>=3.
|
|
23
|
-
Requires-Dist: python-dotenv>=1.2.
|
|
24
|
-
Requires-Dist: pyproject-metadata>=0.
|
|
25
|
-
Requires-Dist: jupytext>=1.19.
|
|
26
|
-
Requires-Dist: mystmd>=1.
|
|
22
|
+
Requires-Dist: gitpython>=3.2.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.2.3
|
|
24
|
+
Requires-Dist: pyproject-metadata>=0.12.1
|
|
25
|
+
Requires-Dist: jupytext>=1.19.5
|
|
26
|
+
Requires-Dist: mystmd>=1.11.0
|
|
27
27
|
Requires-Dist: pdoc>=16.0.0
|
|
28
|
-
Requires-Dist: ruff>=0.
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-
|
|
28
|
+
Requires-Dist: ruff>=0.16.9
|
|
29
|
+
Requires-Dist: prek>=0.5.4
|
|
30
|
+
Requires-Dist: ty>=0.0.84
|
|
31
|
+
Requires-Dist: git-cliff>=2.14.2
|
|
32
|
+
Requires-Dist: marimo[recommended,lsp]>=0.25.0 ; extra == 'marimo'
|
|
33
|
+
Requires-Python: >=3.12
|
|
32
34
|
Project-URL: homepage, https://afterpython.afterpython.org
|
|
33
35
|
Project-URL: repository, https://github.com/AfterPythonOrg/afterpython
|
|
34
36
|
Project-URL: documentation, https://afterpython.afterpython.org/doc
|
|
37
|
+
Provides-Extra: marimo
|
|
35
38
|
Description-Content-Type: text/markdown
|
|
36
39
|
|
|
37
40
|
# AfterPython: Python Package Maintenance Toolkit and Project Website Generator
|
|
@@ -59,10 +62,10 @@ Description-Content-Type: text/markdown
|
|
|
59
62
|
[Scikit-Learn]: https://scikit-learn.org
|
|
60
63
|
[WebLLM]: https://webllm.mlc.ai/
|
|
61
64
|
[project-website-template]: https://github.com/AfterPythonOrg/project-website-template
|
|
62
|
-
[commitizen]: https://github.com/commitizen-tools/commitizen
|
|
63
65
|
[uv]: https://docs.astral.sh/uv/
|
|
64
66
|
[ruff]: https://docs.astral.sh/ruff/
|
|
65
|
-
|
|
67
|
+
[ty]: https://docs.astral.sh/ty/
|
|
68
|
+
[git-cliff]: https://git-cliff.org/
|
|
66
69
|
|
|
67
70
|
## Problem
|
|
68
71
|
Going from **writing Python code to publishing and maintaining a package** is **time-consuming**.
|
|
@@ -125,10 +128,10 @@ ap tui
|
|
|
125
128
|
- [pre-commit]
|
|
126
129
|
- [GitHub Actions]
|
|
127
130
|
- [pdoc]
|
|
128
|
-
- [commitizen]
|
|
129
131
|
- [uv]
|
|
130
132
|
- [ruff]
|
|
133
|
+
- [ty]
|
|
131
134
|
- [pagefind]
|
|
132
135
|
- [WebLLM]
|
|
133
|
-
|
|
136
|
+
- [git-cliff]
|
|
134
137
|
<!-- - [pixi] -->
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
[Scikit-Learn]: https://scikit-learn.org
|
|
24
24
|
[WebLLM]: https://webllm.mlc.ai/
|
|
25
25
|
[project-website-template]: https://github.com/AfterPythonOrg/project-website-template
|
|
26
|
-
[commitizen]: https://github.com/commitizen-tools/commitizen
|
|
27
26
|
[uv]: https://docs.astral.sh/uv/
|
|
28
27
|
[ruff]: https://docs.astral.sh/ruff/
|
|
29
|
-
|
|
28
|
+
[ty]: https://docs.astral.sh/ty/
|
|
29
|
+
[git-cliff]: https://git-cliff.org/
|
|
30
30
|
|
|
31
31
|
## Problem
|
|
32
32
|
Going from **writing Python code to publishing and maintaining a package** is **time-consuming**.
|
|
@@ -89,10 +89,10 @@ ap tui
|
|
|
89
89
|
- [pre-commit]
|
|
90
90
|
- [GitHub Actions]
|
|
91
91
|
- [pdoc]
|
|
92
|
-
- [commitizen]
|
|
93
92
|
- [uv]
|
|
94
93
|
- [ruff]
|
|
94
|
+
- [ty]
|
|
95
95
|
- [pagefind]
|
|
96
96
|
- [WebLLM]
|
|
97
|
-
|
|
97
|
+
- [git-cliff]
|
|
98
98
|
<!-- - [pixi] -->
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "afterpython"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "CLI tool to streamline Python package maintenance and generate a dedicated project website"
|
|
5
|
+
license = "Apache-2.0"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
keywords = [
|
|
8
|
+
"package-maintenance",
|
|
9
|
+
"project-website",
|
|
10
|
+
"static-site-generator",
|
|
11
|
+
"developer-tools",
|
|
12
|
+
"documentation",
|
|
13
|
+
"cli-tool",
|
|
14
|
+
"ci/cd",
|
|
15
|
+
"mystmd",
|
|
16
|
+
"semantic-release",
|
|
17
|
+
"python-packaging",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Topic :: Software Development :: Build Tools",
|
|
23
|
+
"Topic :: Software Development :: Documentation",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
]
|
|
29
|
+
requires-python = ">=3.12"
|
|
30
|
+
dependencies = [
|
|
31
|
+
"click>=8.5.0",
|
|
32
|
+
"trogon>=0.6.0",
|
|
33
|
+
"httpx2>=2.13.1",
|
|
34
|
+
"tomlkit>=0.15.1",
|
|
35
|
+
"ruamel-yaml>=0.19.1",
|
|
36
|
+
"gitpython>=3.2.0",
|
|
37
|
+
"python-dotenv>=1.2.3",
|
|
38
|
+
"pyproject-metadata>=0.12.1",
|
|
39
|
+
"jupytext>=1.19.5",
|
|
40
|
+
"mystmd>=1.11.0",
|
|
41
|
+
"pdoc>=16.0.0",
|
|
42
|
+
"ruff>=0.16.9",
|
|
43
|
+
"prek>=0.5.4",
|
|
44
|
+
"ty>=0.0.84",
|
|
45
|
+
"git-cliff>=2.14.2",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[[project.authors]]
|
|
49
|
+
name = "Stephen Yau"
|
|
50
|
+
email = "softwareentrepreneer+afterpython@gmail.com"
|
|
51
|
+
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
marimo = ["marimo[recommended,lsp]>=0.25.0"]
|
|
54
|
+
|
|
55
|
+
[project.urls]
|
|
56
|
+
homepage = "https://afterpython.afterpython.org"
|
|
57
|
+
repository = "https://github.com/AfterPythonOrg/afterpython"
|
|
58
|
+
documentation = "https://afterpython.afterpython.org/doc"
|
|
59
|
+
|
|
60
|
+
[project.scripts]
|
|
61
|
+
ap = "afterpython.main:run_cli"
|
|
62
|
+
pcu = "afterpython.main:run_pcu"
|
|
63
|
+
|
|
64
|
+
[build-system]
|
|
65
|
+
requires = ["uv_build>=0.12.21,<0.13.0"]
|
|
66
|
+
build-backend = "uv_build"
|
|
67
|
+
|
|
68
|
+
[tool.uv.build-backend]
|
|
69
|
+
module-name = "afterpython"
|
|
70
|
+
module-root = "src"
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
extend = "afterpython/ruff.toml"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "afterpython"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "CLI tool to streamline Python package maintenance and generate a dedicated project website"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
authors = [
|
|
@@ -25,32 +25,33 @@ classifiers = [
|
|
|
25
25
|
"Topic :: Software Development :: Build Tools",
|
|
26
26
|
"Topic :: Software Development :: Documentation",
|
|
27
27
|
"Typing :: Typed",
|
|
28
|
-
"Programming Language :: Python :: 3.11",
|
|
29
28
|
"Programming Language :: Python :: 3.12",
|
|
30
29
|
"Programming Language :: Python :: 3.13",
|
|
31
|
-
|
|
30
|
+
"Programming Language :: Python :: 3.14",
|
|
31
|
+
# "Programming Language :: Python :: 3.15",
|
|
32
32
|
]
|
|
33
|
-
requires-python = ">=3.
|
|
33
|
+
requires-python = ">=3.12"
|
|
34
34
|
dependencies = [
|
|
35
|
-
"click>=8.
|
|
35
|
+
"click>=8.5.0",
|
|
36
36
|
"trogon>=0.6.0",
|
|
37
|
-
"httpx2>=2.
|
|
38
|
-
"tomlkit>=0.15.
|
|
37
|
+
"httpx2>=2.13.1",
|
|
38
|
+
"tomlkit>=0.15.1",
|
|
39
39
|
"ruamel-yaml>=0.19.1",
|
|
40
|
-
"gitpython>=3.
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
# "jinja2>=3.1.6",
|
|
46
|
-
"mystmd>=1.10.1",
|
|
40
|
+
"gitpython>=3.2.0",
|
|
41
|
+
"python-dotenv>=1.2.3",
|
|
42
|
+
"pyproject-metadata>=0.12.1",
|
|
43
|
+
"jupytext>=1.19.5",
|
|
44
|
+
"mystmd>=1.11.0",
|
|
47
45
|
"pdoc>=16.0.0",
|
|
48
|
-
"ruff>=0.
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
46
|
+
"ruff>=0.16.9",
|
|
47
|
+
"prek>=0.5.4",
|
|
48
|
+
"ty>=0.0.84",
|
|
49
|
+
"git-cliff>=2.14.2",
|
|
52
50
|
]
|
|
53
51
|
|
|
52
|
+
[project.optional-dependencies]
|
|
53
|
+
marimo = ["marimo[recommended,lsp]>=0.25.0"]
|
|
54
|
+
|
|
54
55
|
[project.urls]
|
|
55
56
|
homepage = "https://afterpython.afterpython.org"
|
|
56
57
|
repository = "https://github.com/AfterPythonOrg/afterpython"
|
|
@@ -60,22 +61,16 @@ documentation = "https://afterpython.afterpython.org/doc"
|
|
|
60
61
|
ap = "afterpython.main:run_cli"
|
|
61
62
|
pcu = "afterpython.main:run_pcu"
|
|
62
63
|
|
|
63
|
-
# [tool.uv]
|
|
64
|
-
# override-dependencies = ["platformdirs>=4.5.0", "nodeenv>=1.9.1"]
|
|
65
|
-
|
|
66
64
|
[build-system]
|
|
67
|
-
requires = ["uv_build>=0.
|
|
65
|
+
requires = ["uv_build>=0.12.21,<0.13.0"]
|
|
68
66
|
build-backend = "uv_build"
|
|
69
67
|
|
|
70
68
|
[tool.uv]
|
|
71
|
-
exclude-newer = "14 days"
|
|
69
|
+
# exclude-newer = "14 days"
|
|
72
70
|
|
|
73
71
|
[tool.uv.build-backend]
|
|
74
72
|
module-name = "afterpython"
|
|
75
73
|
module-root = "src"
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[tool.pyright]
|
|
81
|
-
typeCheckingMode = "basic"
|
|
75
|
+
[tool.ruff]
|
|
76
|
+
extend = "afterpython/ruff.toml"
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
+
import shutil
|
|
5
6
|
import subprocess
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Literal
|
|
@@ -48,6 +49,12 @@ def _export_marimo(
|
|
|
48
49
|
execute: bool = False,
|
|
49
50
|
):
|
|
50
51
|
"""Run `marimo export` to produce HTML at `output_html`."""
|
|
52
|
+
# marimo is an optional dependency (the `marimo` extra)
|
|
53
|
+
if shutil.which("marimo") is None:
|
|
54
|
+
raise click.ClickException(
|
|
55
|
+
f"marimo is required to build {source.name} (a marimo notebook), "
|
|
56
|
+
'install it with: uv add --dev "afterpython[marimo]"'
|
|
57
|
+
)
|
|
51
58
|
output_html.parent.mkdir(parents=True, exist_ok=True)
|
|
52
59
|
subcommand = "html-wasm" if mode == "wasm" else "html"
|
|
53
60
|
cmd = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Callable
|
|
3
|
+
from collections.abc import Callable, Iterable
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
@@ -147,7 +147,7 @@ def postbuild(dev_build: bool = False):
|
|
|
147
147
|
source: Path,
|
|
148
148
|
destination: Path,
|
|
149
149
|
is_copy: bool = True,
|
|
150
|
-
ignore_copy: Callable[[str, list[str]],
|
|
150
|
+
ignore_copy: Callable[[str, list[str]], Iterable[str]] | None = None,
|
|
151
151
|
):
|
|
152
152
|
"""
|
|
153
153
|
Move or copy files from source to destination.
|
|
@@ -156,7 +156,7 @@ def postbuild(dev_build: bool = False):
|
|
|
156
156
|
source: Source path
|
|
157
157
|
destination: Destination path
|
|
158
158
|
is_copy: If True, copy files (merge with existing). If False, move files (replace destination)
|
|
159
|
-
ignore_copy: Optional ignore function for copytree, takes (directory, contents) and returns
|
|
159
|
+
ignore_copy: Optional ignore function for copytree, takes (directory, contents) and returns the names to ignore
|
|
160
160
|
"""
|
|
161
161
|
if not source.exists():
|
|
162
162
|
return
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from packaging.version import Version
|
|
7
|
+
|
|
8
|
+
# uv's --bump names for PEP 440 pre-release markers
|
|
9
|
+
PRE_KINDS = {"a": "alpha", "b": "beta", "rc": "rc"}
|
|
10
|
+
CHANGELOG = "CHANGELOG.md"
|
|
11
|
+
# git-cliff ignores pre-release and dev tags (e.g. v0.4.0rc1, v0.4.0.dev2),
|
|
12
|
+
# so a stable release's section has every commit since the previous stable release
|
|
13
|
+
PRE_RELEASE_TAGS = "(a|b|rc|dev)[0-9]+"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _uv_version_args(
|
|
17
|
+
version: Version, release: bool, pre: bool, level: str
|
|
18
|
+
) -> list[str]:
|
|
19
|
+
"""Arguments for `uv version`, either `--bump ...` or an exact version"""
|
|
20
|
+
is_stable = not version.is_prerelease # dev versions count as pre-releases too
|
|
21
|
+
if not is_stable and level != "patch":
|
|
22
|
+
raise click.ClickException(
|
|
23
|
+
f"--{level} only applies to stable versions, current version is {version}"
|
|
24
|
+
)
|
|
25
|
+
if release:
|
|
26
|
+
# e.g. 0.1.0rc0 -> 0.1.0, 0.3.19 -> 0.3.20
|
|
27
|
+
return ["--bump", "stable" if not is_stable else level]
|
|
28
|
+
if is_stable:
|
|
29
|
+
# e.g. 0.3.19 -> 0.3.20 (or 0.3.20rc0 with --pre)
|
|
30
|
+
return ["--bump", level, *(["--bump", "rc=0"] if pre else [])]
|
|
31
|
+
if version.is_devrelease:
|
|
32
|
+
if not pre:
|
|
33
|
+
# e.g. 0.1.0.dev3 -> 0.1.0.dev4
|
|
34
|
+
return ["--bump", "dev"]
|
|
35
|
+
if version.pre is None:
|
|
36
|
+
# e.g. 0.1.0.dev3 -> 0.1.0rc0
|
|
37
|
+
return ["--bump", "rc=0"]
|
|
38
|
+
# e.g. 0.1.0rc0.dev2 -> 0.1.0rc0 (uv's --bump rc would skip to rc1)
|
|
39
|
+
kind, number = version.pre
|
|
40
|
+
return [str(Version(f"{version.base_version}{kind}{number}"))]
|
|
41
|
+
# e.g. 0.1.0a1 -> 0.1.0a2, 0.1.0rc0 -> 0.1.0rc1
|
|
42
|
+
assert version.pre is not None # not stable and not dev, so it's a pre-release
|
|
43
|
+
return ["--bump", PRE_KINDS[version.pre[0]]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _git(*args: str) -> subprocess.CompletedProcess:
|
|
47
|
+
import afterpython as ap
|
|
48
|
+
|
|
49
|
+
return subprocess.run(
|
|
50
|
+
["git", *args],
|
|
51
|
+
cwd=ap.paths.user_path,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
check=False,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _restore(files: list[str], new_files: list[str]):
|
|
59
|
+
"""Undo the version bump in the working tree and index"""
|
|
60
|
+
import afterpython as ap
|
|
61
|
+
|
|
62
|
+
_git("restore", "--source=HEAD", "--staged", "--worktree", "--", *files)
|
|
63
|
+
for file in new_files:
|
|
64
|
+
# --force, a hook (e.g. end-of-file-fixer) may have changed the file after it was staged
|
|
65
|
+
_git("rm", "--cached", "--force", "--quiet", "--ignore-unmatch", "--", file)
|
|
66
|
+
(ap.paths.user_path / file).unlink(missing_ok=True)
|
|
67
|
+
click.echo(f"↩️ Restored {', '.join(files + new_files)}", err=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _write_changelog(tag: str):
|
|
71
|
+
"""Write the release section for tag to CHANGELOG.md with git-cliff"""
|
|
72
|
+
import afterpython as ap
|
|
73
|
+
|
|
74
|
+
cmd = [
|
|
75
|
+
"git-cliff",
|
|
76
|
+
"--config",
|
|
77
|
+
str(ap.paths.afterpython_path / "cliff.toml"),
|
|
78
|
+
"--tag",
|
|
79
|
+
tag,
|
|
80
|
+
"--ignore-tags",
|
|
81
|
+
PRE_RELEASE_TAGS,
|
|
82
|
+
]
|
|
83
|
+
if (ap.paths.user_path / CHANGELOG).exists():
|
|
84
|
+
# only add the new section on top, keeping manual edits to older ones;
|
|
85
|
+
# a range from the previous stable tag, since --unreleased would drop
|
|
86
|
+
# the commits before an ignored pre-release tag (e.g. v0.4.1rc0)
|
|
87
|
+
previous = _git(
|
|
88
|
+
"describe",
|
|
89
|
+
"--tags",
|
|
90
|
+
"--abbrev=0",
|
|
91
|
+
"--match",
|
|
92
|
+
"v*",
|
|
93
|
+
*(f"--exclude=*{kind}[0-9]*" for kind in ("a", "b", "rc", "dev")),
|
|
94
|
+
).stdout.strip()
|
|
95
|
+
cmd += [f"{previous}..HEAD" if previous else "--unreleased"]
|
|
96
|
+
cmd += ["--prepend", CHANGELOG]
|
|
97
|
+
else:
|
|
98
|
+
# git-cliff can't prepend to a missing file, so create it from the full history
|
|
99
|
+
cmd += ["--output", CHANGELOG]
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
cmd, cwd=ap.paths.user_path, capture_output=True, text=True, check=False
|
|
102
|
+
)
|
|
103
|
+
if result.returncode != 0:
|
|
104
|
+
raise click.ClickException(f"git-cliff failed:\n{result.stderr.strip()}")
|
|
105
|
+
# the blank line after each release also ends up at the end of the file,
|
|
106
|
+
# end it with a single newline as end-of-file-fixer wants
|
|
107
|
+
path = ap.paths.user_path / CHANGELOG
|
|
108
|
+
path.write_text(path.read_text().rstrip("\n") + "\n")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _is_interactive() -> bool:
|
|
112
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _edit_changelog():
|
|
116
|
+
"""Open CHANGELOG.md in git's editor, the bump continues once it's closed"""
|
|
117
|
+
import afterpython as ap
|
|
118
|
+
|
|
119
|
+
result = _git("var", "GIT_EDITOR")
|
|
120
|
+
if result.returncode != 0:
|
|
121
|
+
raise click.ClickException(
|
|
122
|
+
f"Failed to find git's editor:\n{result.stderr.strip()}"
|
|
123
|
+
)
|
|
124
|
+
editor = result.stdout.strip()
|
|
125
|
+
click.echo(
|
|
126
|
+
f"✏️ Opening {CHANGELOG} in {editor}, save and close it to commit the bump "
|
|
127
|
+
"(to abort, press Ctrl+C or exit the editor with an error, e.g. :cq in vim)"
|
|
128
|
+
)
|
|
129
|
+
# raises ClickException if the editor exits with an error, which rolls back the bump
|
|
130
|
+
click.edit(filename=str(ap.paths.user_path / CHANGELOG), editor=editor)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@click.command()
|
|
134
|
+
@click.option(
|
|
135
|
+
"--pre",
|
|
136
|
+
is_flag=True,
|
|
137
|
+
help="bump to pre-release version (e.g., 0.1.0.dev3 -> 0.1.0rc0, 0.1.0a1 -> 0.1.0a2)",
|
|
138
|
+
)
|
|
139
|
+
@click.option(
|
|
140
|
+
"--release",
|
|
141
|
+
is_flag=True,
|
|
142
|
+
help="bump to stable version (e.g., 0.1.0rc0 -> 0.1.0, 0.3.19 -> 0.3.20) and push it with its tag to trigger the release workflow (default branch only)",
|
|
143
|
+
)
|
|
144
|
+
@click.option(
|
|
145
|
+
"--minor",
|
|
146
|
+
is_flag=True,
|
|
147
|
+
help="bump the minor version instead of patch (stable versions only, e.g., 0.3.19 -> 0.4.0)",
|
|
148
|
+
)
|
|
149
|
+
@click.option(
|
|
150
|
+
"--major",
|
|
151
|
+
is_flag=True,
|
|
152
|
+
help="bump the major version instead of patch (stable versions only, e.g., 0.3.19 -> 1.0.0)",
|
|
153
|
+
)
|
|
154
|
+
@click.option(
|
|
155
|
+
"--no-edit",
|
|
156
|
+
is_flag=True,
|
|
157
|
+
help="don't open CHANGELOG.md in git's editor before committing a stable bump",
|
|
158
|
+
)
|
|
159
|
+
@click.option(
|
|
160
|
+
"--dry-run", is_flag=True, help="show the new version without changing anything"
|
|
161
|
+
)
|
|
162
|
+
def bump(
|
|
163
|
+
release: bool, pre: bool, minor: bool, major: bool, no_edit: bool, dry_run: bool
|
|
164
|
+
):
|
|
165
|
+
"""Bump project version, commit it and tag it.
|
|
166
|
+
|
|
167
|
+
\b
|
|
168
|
+
By default, stays within current release phase:
|
|
169
|
+
- Dev releases (e.g., 0.1.0.dev3) -> increment dev number (0.1.0.dev4)
|
|
170
|
+
- Pre-releases (e.g., 0.1.0a1) -> increment pre-release (0.1.0a2)
|
|
171
|
+
- Stable releases (e.g., 0.3.19) -> increment patch (0.3.20), or --minor/--major
|
|
172
|
+
|
|
173
|
+
Use --pre to transition from dev to pre-release, or pre-release to next pre-release.
|
|
174
|
+
Stable bumps also add the new release section to CHANGELOG.md with git-cliff
|
|
175
|
+
(if afterpython/cliff.toml exists, see `ap init cliff`) and open it in git's editor,
|
|
176
|
+
the bump is committed once you close it (use --no-edit to skip).
|
|
177
|
+
Use --release to bump to a stable version and push it with its tag (triggers the release workflow),
|
|
178
|
+
only allowed on the default branch (e.g. main).
|
|
179
|
+
"""
|
|
180
|
+
import afterpython as ap
|
|
181
|
+
from afterpython.tools._git import check_release_branch
|
|
182
|
+
from afterpython.tools.pyproject import read_metadata
|
|
183
|
+
from afterpython.utils import has_pixi
|
|
184
|
+
|
|
185
|
+
if release and pre:
|
|
186
|
+
raise click.ClickException("Only one of --release or --pre can be specified")
|
|
187
|
+
if minor and major:
|
|
188
|
+
raise click.ClickException("Only one of --minor or --major can be specified")
|
|
189
|
+
level = "minor" if minor else "major" if major else "patch"
|
|
190
|
+
|
|
191
|
+
# before changing anything, so a wrong branch doesn't leave a bump behind
|
|
192
|
+
if release:
|
|
193
|
+
check_release_branch()
|
|
194
|
+
|
|
195
|
+
version = read_metadata().version
|
|
196
|
+
if version is None:
|
|
197
|
+
raise click.ClickException("Unable to read version from pyproject.toml")
|
|
198
|
+
uv_args = _uv_version_args(version, release, pre, level)
|
|
199
|
+
|
|
200
|
+
user_path = ap.paths.user_path
|
|
201
|
+
# without uv.lock, --frozen stops uv from creating one; with it, uv updates it
|
|
202
|
+
has_uv_lock = (user_path / "uv.lock").exists()
|
|
203
|
+
uv_lock_arg = "--no-sync" if has_uv_lock else "--frozen"
|
|
204
|
+
result = subprocess.run(
|
|
205
|
+
["uv", "version", *uv_args, "--dry-run", "--short", "--frozen"],
|
|
206
|
+
cwd=user_path,
|
|
207
|
+
capture_output=True,
|
|
208
|
+
text=True,
|
|
209
|
+
check=False,
|
|
210
|
+
)
|
|
211
|
+
if result.returncode != 0:
|
|
212
|
+
raise click.ClickException(result.stderr.strip())
|
|
213
|
+
new_version = result.stdout.strip()
|
|
214
|
+
tag = f"v{new_version}"
|
|
215
|
+
|
|
216
|
+
uses_pixi = (user_path / "pixi.lock").exists()
|
|
217
|
+
if uses_pixi and not has_pixi():
|
|
218
|
+
click.echo("⚠️ pixi.lock found but pixi is not installed, skip updating it")
|
|
219
|
+
uses_pixi = False
|
|
220
|
+
candidates = [
|
|
221
|
+
"pyproject.toml",
|
|
222
|
+
*(["uv.lock"] if has_uv_lock else []),
|
|
223
|
+
*(["pixi.lock"] if uses_pixi else []),
|
|
224
|
+
]
|
|
225
|
+
# only stable bumps write the changelog, and only when afterpython/cliff.toml was set up
|
|
226
|
+
writes_changelog = (
|
|
227
|
+
not Version(new_version).is_prerelease
|
|
228
|
+
and (ap.paths.afterpython_path / "cliff.toml").exists()
|
|
229
|
+
)
|
|
230
|
+
if writes_changelog:
|
|
231
|
+
candidates.append(CHANGELOG)
|
|
232
|
+
# no editor in CI or tests
|
|
233
|
+
edits_changelog = writes_changelog and not no_edit and _is_interactive()
|
|
234
|
+
files = _git("ls-files", "--", *candidates).stdout.split()
|
|
235
|
+
if "pyproject.toml" not in files:
|
|
236
|
+
raise click.ClickException("pyproject.toml is not tracked by git")
|
|
237
|
+
# CHANGELOG.md created by this bump, removed again if the bump fails
|
|
238
|
+
new_files: list[str] = []
|
|
239
|
+
if writes_changelog and CHANGELOG not in files:
|
|
240
|
+
if (user_path / CHANGELOG).exists():
|
|
241
|
+
raise click.ClickException(
|
|
242
|
+
f"{CHANGELOG} exists but is not tracked by git, commit or remove it first"
|
|
243
|
+
)
|
|
244
|
+
new_files.append(CHANGELOG)
|
|
245
|
+
|
|
246
|
+
# the bump commit only contains the version change
|
|
247
|
+
dirty = _git("status", "--porcelain", "--", *files).stdout.strip()
|
|
248
|
+
if dirty:
|
|
249
|
+
raise click.ClickException(
|
|
250
|
+
f"Commit or stash your changes to these files first:\n{dirty}"
|
|
251
|
+
)
|
|
252
|
+
if _git("rev-parse", "--quiet", "--verify", f"refs/tags/{tag}").returncode == 0:
|
|
253
|
+
raise click.ClickException(f"Tag {tag} already exists")
|
|
254
|
+
# otherwise uv would re-lock other packages into the bump commit
|
|
255
|
+
if has_uv_lock and (
|
|
256
|
+
subprocess.run(
|
|
257
|
+
["uv", "lock", "--check"], cwd=user_path, capture_output=True, check=False
|
|
258
|
+
).returncode
|
|
259
|
+
!= 0
|
|
260
|
+
):
|
|
261
|
+
raise click.ClickException(
|
|
262
|
+
"uv.lock is out of date, run `uv lock` and commit it first"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if dry_run:
|
|
266
|
+
click.echo(f"{version} → {new_version}")
|
|
267
|
+
if writes_changelog:
|
|
268
|
+
click.echo(f"Would write the {new_version} section to {CHANGELOG}")
|
|
269
|
+
if edits_changelog:
|
|
270
|
+
click.echo(f"Would open {CHANGELOG} in git's editor before committing")
|
|
271
|
+
click.echo(f"Would commit {', '.join(files + new_files)} and create tag {tag}")
|
|
272
|
+
if release:
|
|
273
|
+
click.echo(f"Would push the current branch and tag {tag} to origin")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
result = subprocess.run(
|
|
278
|
+
["uv", "version", *uv_args, uv_lock_arg], cwd=user_path, check=False
|
|
279
|
+
)
|
|
280
|
+
if result.returncode != 0:
|
|
281
|
+
raise click.ClickException("uv version failed")
|
|
282
|
+
if read_metadata().version != Version(new_version):
|
|
283
|
+
raise click.ClickException(
|
|
284
|
+
f"Expected version {new_version} in pyproject.toml after the bump"
|
|
285
|
+
)
|
|
286
|
+
# refresh the pixi env; older pixi versions also record the editable version in pixi.lock
|
|
287
|
+
if uses_pixi:
|
|
288
|
+
result = subprocess.run(["pixi", "install"], cwd=user_path, check=False)
|
|
289
|
+
if result.returncode != 0:
|
|
290
|
+
raise click.ClickException("pixi install failed")
|
|
291
|
+
if writes_changelog:
|
|
292
|
+
_write_changelog(tag)
|
|
293
|
+
click.echo(f"📝 Wrote the {new_version} section to {CHANGELOG}")
|
|
294
|
+
if edits_changelog:
|
|
295
|
+
_edit_changelog()
|
|
296
|
+
if new_files:
|
|
297
|
+
# `git commit -- <files>` only takes files git already knows about
|
|
298
|
+
result = _git("add", "--", *new_files)
|
|
299
|
+
if result.returncode != 0:
|
|
300
|
+
raise click.ClickException(f"git add failed:\n{result.stderr.strip()}")
|
|
301
|
+
|
|
302
|
+
# skip the commit message check, "bump" is not one of the commit types
|
|
303
|
+
env = os.environ.copy()
|
|
304
|
+
env["SKIP"] = ",".join(filter(None, [env.get("SKIP"), "ap-commit"]))
|
|
305
|
+
message = f"bump: version {version} → {new_version}"
|
|
306
|
+
# `git commit -- <files>` commits only these files, leaving anything else staged as is
|
|
307
|
+
result = subprocess.run(
|
|
308
|
+
["git", "commit", "-m", message, "--", *files, *new_files],
|
|
309
|
+
cwd=user_path,
|
|
310
|
+
env=env,
|
|
311
|
+
check=False,
|
|
312
|
+
)
|
|
313
|
+
if result.returncode != 0:
|
|
314
|
+
raise click.ClickException("git commit failed")
|
|
315
|
+
except (click.ClickException, KeyboardInterrupt):
|
|
316
|
+
_restore(files, new_files)
|
|
317
|
+
raise
|
|
318
|
+
|
|
319
|
+
result = _git("tag", tag)
|
|
320
|
+
if result.returncode != 0:
|
|
321
|
+
raise click.ClickException(
|
|
322
|
+
f"Committed the bump but failed to create tag {tag}:\n{result.stderr.strip()}"
|
|
323
|
+
)
|
|
324
|
+
click.echo(f"✅ Bumped {version} → {new_version} and tagged {tag}")
|
|
325
|
+
|
|
326
|
+
if release:
|
|
327
|
+
# ap release pushes the branch and the tag, which triggers the release workflow
|
|
328
|
+
click.echo()
|
|
329
|
+
result = subprocess.run(["ap", "release"], cwd=user_path, check=False)
|
|
330
|
+
if result.returncode != 0:
|
|
331
|
+
raise click.ClickException(
|
|
332
|
+
f"Bumped and tagged {tag} but didn't push it, retry with: ap release"
|
|
333
|
+
)
|