afterpython 0.1.0.dev7__tar.gz → 0.1.0.dev9__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.
Files changed (55) hide show
  1. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9}/PKG-INFO +18 -8
  2. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9}/README.md +12 -2
  3. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9}/pyproject.toml +9 -39
  4. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/_io/toml.py +5 -3
  5. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/_typing.py +1 -2
  6. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/builders/__init__.py +0 -1
  7. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/builders/molab.py +2 -1
  8. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/__init__.py +0 -1
  9. afterpython-0.1.0.dev9/src/afterpython/cli/commands/add.py +67 -0
  10. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/build.py +10 -9
  11. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/clean.py +2 -1
  12. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/commit.py +16 -1
  13. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/dev.py +3 -2
  14. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/init.py +26 -12
  15. afterpython-0.1.0.dev9/src/afterpython/cli/commands/init_branch_rules.py +9 -0
  16. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/install.py +8 -2
  17. afterpython-0.1.0.dev9/src/afterpython/cli/commands/lock.py +23 -0
  18. afterpython-0.1.0.dev9/src/afterpython/cli/commands/remove.py +67 -0
  19. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/start.py +6 -3
  20. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/update.py +26 -3
  21. afterpython-0.1.0.dev9/src/afterpython/cli/main.py +132 -0
  22. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/const.py +0 -1
  23. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/pcu.py +3 -1
  24. afterpython-0.1.0.dev9/src/afterpython/templates/ci-workflow-template.yml +197 -0
  25. afterpython-0.1.0.dev9/src/afterpython/templates/dependabot-template.yml +30 -0
  26. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/templates/deploy-workflow-template.yml +5 -5
  27. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/templates/release-workflow-template.yml +3 -3
  28. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/templates/ruff-template.toml +1 -1
  29. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/_afterpython.py +2 -1
  30. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/_git.py +28 -2
  31. afterpython-0.1.0.dev9/src/afterpython/tools/branch_rules.py +106 -0
  32. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/commitizen.py +1 -2
  33. afterpython-0.1.0.dev9/src/afterpython/tools/github_actions.py +48 -0
  34. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/myst.py +4 -2
  35. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/pre_commit.py +1 -3
  36. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/tools/pyproject.py +4 -2
  37. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/utils.py +9 -0
  38. afterpython-0.1.0.dev7/afterpython/.pre-commit-config.yaml +0 -48
  39. afterpython-0.1.0.dev7/afterpython/cli/main.py +0 -71
  40. afterpython-0.1.0.dev7/afterpython/cz.toml +0 -27
  41. afterpython-0.1.0.dev7/afterpython/tools/github_actions.py +0 -78
  42. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/__init__.py +0 -0
  43. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/_io/yaml.py +0 -0
  44. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/_paths.py +1 -1
  45. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/builders/metadata.py +1 -1
  46. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/bump.py +0 -0
  47. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/check.py +0 -0
  48. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/commitizen.py +0 -0
  49. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/format.py +0 -0
  50. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/pre_commit.py +0 -0
  51. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/preview.py +0 -0
  52. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/release.py +0 -0
  53. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/cli/commands/sync.py +3 -3
  54. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/main.py +0 -0
  55. {afterpython-0.1.0.dev7 → afterpython-0.1.0.dev9/src}/afterpython/version.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.1.0.dev7
4
- Summary: CLI tool for AfterPython
3
+ Version: 0.1.0.dev9
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
7
7
  Author-email: Stephen Yau <softwareentrepreneer+afterpython@gmail.com>
@@ -10,10 +10,10 @@ Classifier: Operating System :: OS Independent
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Topic :: Software Development :: Build Tools
12
12
  Classifier: Topic :: Software Development :: Documentation
13
+ Classifier: Typing :: Typed
13
14
  Classifier: Programming Language :: Python :: 3.11
14
15
  Classifier: Programming Language :: Python :: 3.12
15
16
  Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
17
  Requires-Dist: click>=8.3.1
18
18
  Requires-Dist: trogon>=0.6.0
19
19
  Requires-Dist: httpx>=0.28.1
@@ -22,11 +22,11 @@ Requires-Dist: ruamel-yaml>=0.18.16
22
22
  Requires-Dist: gitpython>=3.1.45
23
23
  Requires-Dist: pygithub>=2.8.1
24
24
  Requires-Dist: python-dotenv>=1.2.1
25
- Requires-Dist: pyproject-metadata>=0.9.1
25
+ Requires-Dist: pyproject-metadata>=0.10.0
26
26
  Requires-Dist: mystmd>=1.6.6
27
27
  Requires-Dist: pdoc>=16.0.0
28
- Requires-Dist: ruff>=0.14.5
29
- Requires-Dist: pre-commit>=4.4.0
28
+ Requires-Dist: ruff>=0.14.6
29
+ Requires-Dist: pre-commit>=4.5.0
30
30
  Requires-Dist: commitizen>=4.10.0
31
31
  Requires-Python: >=3.11
32
32
  Project-URL: documentation, https://afterpython.afterpython.org/doc
@@ -76,8 +76,9 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
76
76
 
77
77
  ## Core Features
78
78
  - [x] Write content directly in [MyST Markdown] or [Jupyter Notebook]
79
- - [x] Go from writing to website deployment in minutes — no need to learn any of the underlying tools
80
- - [x] Centralize all your content in a modern, unified project website — from documentation to blog posts
79
+ - [x] Go from writing to **website deployment in minutes** — no need to learn any of the underlying tools
80
+ - [x] Centralize all your content in a modern, **unified project website** — from documentation to blog posts
81
+ - [x] Zero-config orchestration — Pre-configured modern tooling with sane defaults (see [Tech Stack](#tech-stack)), so you can start maintaining packages immediately **without learning each tool**
81
82
  - [ ] Export content as PDF — for example, combine all blog posts into a single PDF file
82
83
  - [ ] **⚡ Full-text search** across **ALL** your content in your website — docs, blogs, tutorials, everything
83
84
  - [ ] **🤖 Embedded AI Chatbot** that answers questions directly using an in-browser LLM — at no cost
@@ -91,16 +92,20 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
91
92
  <img alt="Project Website" src="afterpython/static/website-light.png">
92
93
  </picture>
93
94
 
95
+ You can create your own website too and deploy it to GitHub Pages in **less than a minute**! See [Quickstart](https://afterpython.afterpython.org/doc/quickstart).
96
+
94
97
  ---
95
98
  ## Installation
96
99
  ```bash
97
100
  # install afterpython as a dev dependency
98
101
  uv add --dev afterpython
102
+
99
103
  # initialize afterpython
100
104
  ap init
101
105
  ```
102
106
 
103
107
 
108
+ ---
104
109
  ## Tech Stack
105
110
  - [MyST]
106
111
  - [project-website-template]
@@ -114,3 +119,8 @@ ap init
114
119
  - [WebLLM]
115
120
  <!-- - ty -->
116
121
  <!-- - [pixi] -->
122
+
123
+
124
+ ---
125
+ ## *Our Mission*
126
+ > Only by making package maintenance and documentation easy can we encourage more Python developers to publish their own packages and grow the ecosystem even further.
@@ -40,8 +40,9 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
40
40
 
41
41
  ## Core Features
42
42
  - [x] Write content directly in [MyST Markdown] or [Jupyter Notebook]
43
- - [x] Go from writing to website deployment in minutes — no need to learn any of the underlying tools
44
- - [x] Centralize all your content in a modern, unified project website — from documentation to blog posts
43
+ - [x] Go from writing to **website deployment in minutes** — no need to learn any of the underlying tools
44
+ - [x] Centralize all your content in a modern, **unified project website** — from documentation to blog posts
45
+ - [x] Zero-config orchestration — Pre-configured modern tooling with sane defaults (see [Tech Stack](#tech-stack)), so you can start maintaining packages immediately **without learning each tool**
45
46
  - [ ] Export content as PDF — for example, combine all blog posts into a single PDF file
46
47
  - [ ] **⚡ Full-text search** across **ALL** your content in your website — docs, blogs, tutorials, everything
47
48
  - [ ] **🤖 Embedded AI Chatbot** that answers questions directly using an in-browser LLM — at no cost
@@ -55,16 +56,20 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
55
56
  <img alt="Project Website" src="afterpython/static/website-light.png">
56
57
  </picture>
57
58
 
59
+ You can create your own website too and deploy it to GitHub Pages in **less than a minute**! See [Quickstart](https://afterpython.afterpython.org/doc/quickstart).
60
+
58
61
  ---
59
62
  ## Installation
60
63
  ```bash
61
64
  # install afterpython as a dev dependency
62
65
  uv add --dev afterpython
66
+
63
67
  # initialize afterpython
64
68
  ap init
65
69
  ```
66
70
 
67
71
 
72
+ ---
68
73
  ## Tech Stack
69
74
  - [MyST]
70
75
  - [project-website-template]
@@ -78,3 +83,8 @@ ap init
78
83
  - [WebLLM]
79
84
  <!-- - ty -->
80
85
  <!-- - [pixi] -->
86
+
87
+
88
+ ---
89
+ ## *Our Mission*
90
+ > Only by making package maintenance and documentation easy can we encourage more Python developers to publish their own packages and grow the ecosystem even further.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "afterpython"
3
- version = "0.1.0.dev7"
4
- description = "CLI tool for AfterPython"
3
+ version = "0.1.0.dev9"
4
+ description = "CLI tool to streamline Python package maintenance and generate a dedicated project website"
5
5
  license = "Apache-2.0"
6
6
  authors = [
7
7
  { name = "Stephen Yau", email = "softwareentrepreneer+afterpython@gmail.com" },
@@ -24,10 +24,11 @@ classifiers = [
24
24
  "Intended Audience :: Developers",
25
25
  "Topic :: Software Development :: Build Tools",
26
26
  "Topic :: Software Development :: Documentation",
27
+ "Typing :: Typed",
27
28
  "Programming Language :: Python :: 3.11",
28
29
  "Programming Language :: Python :: 3.12",
29
30
  "Programming Language :: Python :: 3.13",
30
- "Programming Language :: Python :: 3.14",
31
+ # "Programming Language :: Python :: 3.14",
31
32
  ]
32
33
  requires-python = ">=3.11"
33
34
  dependencies = [
@@ -39,13 +40,13 @@ dependencies = [
39
40
  "gitpython>=3.1.45",
40
41
  "pygithub>=2.8.1",
41
42
  "python-dotenv>=1.2.1",
42
- "pyproject-metadata>=0.9.1",
43
+ "pyproject-metadata>=0.10.0",
43
44
  # "jupytext>=1.18.1",
44
45
  # "jinja2>=3.1.6",
45
46
  "mystmd>=1.6.6",
46
47
  "pdoc>=16.0.0",
47
- "ruff>=0.14.5",
48
- "pre-commit>=4.4.0",
48
+ "ruff>=0.14.6",
49
+ "pre-commit>=4.5.0",
49
50
  "commitizen>=4.10.0",
50
51
  # "python-semantic-release>=10.5.2",
51
52
  ]
@@ -64,40 +65,9 @@ pcu = "afterpython.main:run_pcu"
64
65
  override-dependencies = ["platformdirs>=4.5.0", "nodeenv>=1.9.1"]
65
66
 
66
67
  [build-system]
67
- requires = ["uv_build>=0.9.10,<0.10.0"]
68
+ requires = ["uv_build>=0.9.13,<0.10.0"]
68
69
  build-backend = "uv_build"
69
70
 
70
- [dependency-groups]
71
- dev = ["notebook>=7.4.7"]
72
-
73
71
  [tool.uv.build-backend]
74
72
  module-name = "afterpython"
75
- module-root = ""
76
- source-exclude = [
77
- "afterpython/doc",
78
- "afterpython/blog",
79
- "afterpython/tutorial",
80
- "afterpython/example",
81
- "afterpython/guide",
82
- "afterpython/static",
83
- "afterpython/_website",
84
- "afterpython/_build",
85
- "afterpython/ruff.toml",
86
- "afterpython/afterpython.toml",
87
- "afterpython/authors.yml",
88
- "afterpython/faq.yml",
89
- ]
90
- wheel-exclude = [
91
- "afterpython/doc",
92
- "afterpython/blog",
93
- "afterpython/tutorial",
94
- "afterpython/example",
95
- "afterpython/guide",
96
- "afterpython/static",
97
- "afterpython/_website",
98
- "afterpython/_build",
99
- "afterpython/ruff.toml",
100
- "afterpython/afterpython.toml",
101
- "afterpython/authors.yml",
102
- "afterpython/faq.yml",
103
- ]
73
+ module-root = "src"
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
5
- from tomlkit.toml_document import TOMLDocument
6
6
  from pathlib import Path
7
7
 
8
+ from tomlkit.toml_document import TOMLDocument
9
+
8
10
  import tomlkit
9
11
 
10
12
 
@@ -16,7 +18,7 @@ def _from_tomlkit(value):
16
18
  - tomlkit containers → plain Python containers
17
19
  - tomlkit primitives → plain Python primitives
18
20
  """
19
- from tomlkit.items import Array, InlineTable, Table, AoT
21
+ from tomlkit.items import AoT, Array, InlineTable, Table
20
22
 
21
23
  if value is None:
22
24
  return None
@@ -58,7 +60,7 @@ def _to_tomlkit(value):
58
60
  - List of dicts → array of inline tables: [{key = val}]
59
61
  - Nested dicts → nested tables with proper structure
60
62
  """
61
- from tomlkit import inline_table, array
63
+ from tomlkit import array, inline_table
62
64
 
63
65
  if value is None:
64
66
  return None
@@ -1,5 +1,4 @@
1
- from typing import TypeAlias, Literal
2
-
1
+ from typing import Literal, TypeAlias
3
2
 
4
3
  NodeEnv: TypeAlias = dict[str, str]
5
4
  tContentType = Literal["doc", "blog", "tutorial", "example", "guide"]
@@ -1,7 +1,6 @@
1
1
  from afterpython.builders.metadata import build_metadata
2
2
  from afterpython.builders.molab import add_molab_badge_to_jupyter_notebooks
3
3
 
4
-
5
4
  __all__ = (
6
5
  "add_molab_badge_to_jupyter_notebooks",
7
6
  "build_metadata",
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
@@ -10,8 +11,8 @@ from pathlib import Path
10
11
  from pyproject_metadata import StandardMetadata
11
12
 
12
13
  import afterpython as ap
13
- from afterpython.tools.pyproject import read_metadata
14
14
  from afterpython.const import CONTENT_TYPES
15
+ from afterpython.tools.pyproject import read_metadata
15
16
 
16
17
 
17
18
  def _get_molab_badge() -> str:
@@ -1,4 +1,3 @@
1
1
  from afterpython.cli.main import afterpython_group
2
2
 
3
-
4
3
  __all__ = ["afterpython_group"]
@@ -0,0 +1,67 @@
1
+ import subprocess
2
+
3
+ import click
4
+ from click.exceptions import Exit
5
+
6
+ from afterpython.utils import has_pixi, has_uv
7
+
8
+
9
+ @click.command()
10
+ @click.option(
11
+ "--optional",
12
+ type=str,
13
+ default=None,
14
+ required=False,
15
+ help="Add to an optional dependency group (pixi: mapped to 'optional' feature)",
16
+ )
17
+ @click.option(
18
+ "--group",
19
+ type=str,
20
+ default=None,
21
+ required=False,
22
+ help="Add to a dependency group (pixi: mapped to same-named feature)",
23
+ )
24
+ @click.argument("lib", type=str, required=True)
25
+ def add(optional: str | None, group: str | None, lib: str):
26
+ """Add a new dependency to the project (manages both uv and pixi if present)"""
27
+ if not has_uv():
28
+ click.echo("uv not found. Please install uv first.")
29
+ return
30
+
31
+ if optional and group:
32
+ click.echo("Error: Cannot specify both --optional and --group")
33
+ raise Exit(1)
34
+
35
+ result = subprocess.run(
36
+ [
37
+ "uv",
38
+ "add",
39
+ *(["--optional", optional] if optional else []),
40
+ *(["--group", group] if group else []),
41
+ lib,
42
+ ],
43
+ check=False,
44
+ )
45
+ if result.returncode != 0:
46
+ raise Exit(result.returncode)
47
+
48
+ if has_pixi():
49
+ # NOTE: pixi doesn't support --optional, so we need to use --feature instead
50
+ if optional:
51
+ feature_name = "optional"
52
+ elif group:
53
+ feature_name = group
54
+ else:
55
+ feature_name = None
56
+ result = subprocess.run(
57
+ [
58
+ "pixi",
59
+ "add",
60
+ "--pypi",
61
+ *(["--feature", feature_name] if feature_name else []),
62
+ lib,
63
+ ],
64
+ check=False,
65
+ )
66
+ if result.returncode != 0:
67
+ raise Exit(result.returncode)
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING
2
+
3
3
  from collections.abc import Callable
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  if TYPE_CHECKING:
6
- from afterpython._typing import NodeEnv
7
7
  from pathlib import Path
8
8
 
9
+ from afterpython._typing import NodeEnv
10
+
9
11
  import os
10
12
  import shutil
11
13
  import subprocess
@@ -14,12 +16,12 @@ import click
14
16
  from click.exceptions import Exit
15
17
 
16
18
  import afterpython as ap
17
- from afterpython.utils import find_node_env
18
- from afterpython.const import CONTENT_TYPES
19
19
  from afterpython.builders import (
20
- build_metadata,
21
20
  add_molab_badge_to_jupyter_notebooks,
21
+ build_metadata,
22
22
  )
23
+ from afterpython.const import CONTENT_TYPES
24
+ from afterpython.utils import find_node_env
23
25
 
24
26
 
25
27
  def determine_base_path() -> str:
@@ -29,9 +31,9 @@ def determine_base_path() -> str:
29
31
  Empty string if custom domain is configured (no github.io in URL)
30
32
  /repo-name if using default GitHub Pages URL (contains github.io)
31
33
  """
34
+ from afterpython._io.toml import _from_tomlkit
32
35
  from afterpython.tools._afterpython import read_afterpython
33
36
  from afterpython.tools.pyproject import read_metadata
34
- from afterpython._io.toml import _from_tomlkit
35
37
 
36
38
  # Read website URL from afterpython.toml
37
39
  afterpython = read_afterpython()
@@ -77,7 +79,7 @@ def prebuild():
77
79
  _check_initialized()
78
80
  _clean_build_directory()
79
81
 
80
- if os.getenv("AP_MOLAB_BADGE", "0") == "1":
82
+ if os.getenv("AP_MOLAB_BADGE", "1") == "1":
81
83
  for content_type in CONTENT_TYPES:
82
84
  add_molab_badge_to_jupyter_notebooks(content_type)
83
85
 
@@ -167,8 +169,7 @@ def build(ctx, dev: bool, execute: bool):
167
169
 
168
170
  Use --execute to execute Jupyter notebooks for all content types.
169
171
  """
170
- from afterpython.utils import has_content_for_myst
171
- from afterpython.utils import handle_passthrough_help
172
+ from afterpython.utils import handle_passthrough_help, has_content_for_myst
172
173
 
173
174
  # Show both our options and myst's help and exit
174
175
  handle_passthrough_help(
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
@@ -10,8 +11,8 @@ import subprocess
10
11
  import click
11
12
  from click.exceptions import Exit
12
13
 
13
- from afterpython.utils import find_node_env, has_content_for_myst
14
14
  from afterpython.const import CONTENT_TYPES
15
+ from afterpython.utils import find_node_env, has_content_for_myst
15
16
 
16
17
 
17
18
  @click.command(
@@ -15,7 +15,13 @@ import click
15
15
  @click.option(
16
16
  "--no-cz", "--no-commitizen", is_flag=True, help="Skip running 'cz commit'"
17
17
  )
18
- def commit(ctx, no_cz: bool):
18
+ @click.option(
19
+ "--no-pc",
20
+ "--no-pre-commit",
21
+ is_flag=True,
22
+ help="Skip running pre-commit checks before commit",
23
+ )
24
+ def commit(ctx, no_cz: bool, no_pc: bool):
19
25
  """Run 'cz commit'"""
20
26
  from afterpython.utils import handle_passthrough_help
21
27
 
@@ -26,6 +32,15 @@ def commit(ctx, no_cz: bool):
26
32
  show_underlying=True,
27
33
  )
28
34
 
35
+ # Run pre-commit checks first (unless --no-pc is set)
36
+ if not no_pc:
37
+ result = subprocess.run(["ap", "pc", "run"])
38
+ if result.returncode != 0:
39
+ click.echo(
40
+ "❌ Pre-commit checks failed. Please fix the issues and try again."
41
+ )
42
+ ctx.exit(1)
43
+
29
44
  if not no_cz:
30
45
  subprocess.run(["ap", "cz", "commit", *ctx.args])
31
46
  else:
@@ -1,17 +1,18 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
5
6
  from afterpython._typing import NodeEnv
6
7
 
7
- import time
8
8
  import subprocess
9
+ import time
9
10
 
10
11
  import click
11
12
  from click.exceptions import Exit
12
13
 
13
- from afterpython.utils import find_node_env, find_available_port
14
14
  from afterpython.const import CONTENT_TYPES
15
+ from afterpython.utils import find_available_port, find_node_env
15
16
 
16
17
 
17
18
  @click.command(
@@ -22,7 +22,7 @@ def init_ruff_toml():
22
22
  "repos": [
23
23
  {
24
24
  "repo": "https://github.com/astral-sh/ruff-pre-commit",
25
- "rev": "v0.14.5",
25
+ "rev": "v0.14.6",
26
26
  "hooks": [
27
27
  {
28
28
  "id": "ruff-check",
@@ -47,18 +47,24 @@ def init_website():
47
47
 
48
48
 
49
49
  @click.command()
50
+ @click.option(
51
+ "--yes",
52
+ "-y",
53
+ is_flag=True,
54
+ help="Automatically answer yes to all prompts",
55
+ )
50
56
  @click.pass_context
51
- def init(ctx):
57
+ def init(ctx, yes):
52
58
  """Initialize afterpython with MyST Markdown (by default) and project website template"""
53
- from afterpython.tools.pyproject import init_pyproject
54
59
  from afterpython.tools._afterpython import init_afterpython
55
- from afterpython.tools.myst import init_myst
56
- from afterpython.tools.pre_commit import init_pre_commit
57
60
  from afterpython.tools.commitizen import init_commitizen
58
61
  from afterpython.tools.github_actions import (
59
- init_release_workflow,
60
- init_deploy_workflow,
62
+ create_dependabot,
63
+ create_workflow,
61
64
  )
65
+ from afterpython.tools.myst import init_myst
66
+ from afterpython.tools.pre_commit import init_pre_commit
67
+ from afterpython.tools.pyproject import init_pyproject
62
68
 
63
69
  paths = ctx.obj["paths"]
64
70
  click.echo("Initializing afterpython...")
@@ -79,20 +85,28 @@ def init(ctx):
79
85
 
80
86
  init_website()
81
87
 
82
- if click.confirm(
88
+ create_workflow("deploy")
89
+ create_workflow("ci")
90
+
91
+ if yes or click.confirm(
83
92
  f"\nCreate .pre-commit-config.yaml in {afterpython_path}?", default=True
84
93
  ):
85
94
  init_pre_commit()
86
95
 
87
- if click.confirm(f"\nCreate ruff.toml in {afterpython_path}?", default=True):
96
+ if yes or click.confirm(f"\nCreate ruff.toml in {afterpython_path}?", default=True):
88
97
  init_ruff_toml()
89
98
 
90
- if click.confirm(
99
+ if yes or click.confirm(
91
100
  f"\nCreate commitizen configuration (cz.toml) in {afterpython_path} "
92
101
  f"and release workflow in .github/workflows/release.yml?",
93
102
  default=True,
94
103
  ):
95
104
  init_commitizen()
96
- init_release_workflow()
105
+ create_workflow("release")
97
106
 
98
- init_deploy_workflow()
107
+ if yes or click.confirm(
108
+ "\nCreate Dependabot configuration (.github/dependabot.yml) "
109
+ "to auto-update GitHub Actions versions?",
110
+ default=True,
111
+ ):
112
+ create_dependabot()
@@ -0,0 +1,9 @@
1
+ import click
2
+
3
+
4
+ @click.command()
5
+ def init_branch_rules():
6
+ """Create default branch protection rules for the current repository"""
7
+ from afterpython.tools.branch_rules import create_default_branch_rules
8
+
9
+ create_default_branch_rules()
@@ -3,7 +3,7 @@ import subprocess
3
3
  import click
4
4
  from click.exceptions import Exit
5
5
 
6
- from afterpython.utils import has_uv
6
+ from afterpython.utils import has_pixi, has_uv
7
7
 
8
8
 
9
9
  @click.command(
@@ -13,10 +13,16 @@ from afterpython.utils import has_uv
13
13
  )
14
14
  )
15
15
  def install():
16
- """Run 'uv sync --all-extras --all-groups' to install all dependencies"""
16
+ """Install all dependencies (runs 'uv sync --all-extras --all-groups' and 'pixi install' if present)"""
17
17
  if not has_uv():
18
18
  click.echo("uv not found. Please install uv first.")
19
19
  return
20
+
20
21
  result = subprocess.run(["uv", "sync", "--all-extras", "--all-groups"], check=False)
21
22
  if result.returncode != 0:
22
23
  raise Exit(result.returncode)
24
+
25
+ if has_pixi():
26
+ result = subprocess.run(["pixi", "install"], check=False)
27
+ if result.returncode != 0:
28
+ raise Exit(result.returncode)
@@ -0,0 +1,23 @@
1
+ import subprocess
2
+
3
+ import click
4
+ from click.exceptions import Exit
5
+
6
+ from afterpython.utils import has_pixi, has_uv
7
+
8
+
9
+ @click.command()
10
+ def lock():
11
+ """Lock the dependencies (runs 'uv lock' and 'pixi lock' if present)"""
12
+ if not has_uv():
13
+ click.echo("uv not found. Please install uv first.")
14
+ return
15
+
16
+ result = subprocess.run(["uv", "lock"], check=False)
17
+ if result.returncode != 0:
18
+ raise Exit(result.returncode)
19
+
20
+ if has_pixi():
21
+ result = subprocess.run(["pixi", "lock"], check=False)
22
+ if result.returncode != 0:
23
+ raise Exit(result.returncode)
@@ -0,0 +1,67 @@
1
+ import subprocess
2
+
3
+ import click
4
+ from click.exceptions import Exit
5
+
6
+ from afterpython.utils import has_pixi, has_uv
7
+
8
+
9
+ @click.command()
10
+ @click.option(
11
+ "--optional",
12
+ type=str,
13
+ default=None,
14
+ required=False,
15
+ help="Remove from an optional dependency group (pixi: mapped to 'optional' feature)",
16
+ )
17
+ @click.option(
18
+ "--group",
19
+ type=str,
20
+ default=None,
21
+ required=False,
22
+ help="Remove from a dependency group (pixi: mapped to same-named feature)",
23
+ )
24
+ @click.argument("lib", type=str, required=True)
25
+ def remove(optional: str | None, group: str | None, lib: str):
26
+ """Remove a dependency from the project (manages both uv and pixi if present)"""
27
+ if not has_uv():
28
+ click.echo("uv not found. Please install uv first.")
29
+ return
30
+
31
+ if optional and group:
32
+ click.echo("Error: Cannot specify both --optional and --group")
33
+ raise Exit(1)
34
+
35
+ result = subprocess.run(
36
+ [
37
+ "uv",
38
+ "remove",
39
+ *(["--optional", optional] if optional else []),
40
+ *(["--group", group] if group else []),
41
+ lib,
42
+ ],
43
+ check=False,
44
+ )
45
+ if result.returncode != 0:
46
+ raise Exit(result.returncode)
47
+
48
+ if has_pixi():
49
+ # NOTE: pixi doesn't support --optional, so we need to use --feature instead
50
+ if optional:
51
+ feature_name = "optional"
52
+ elif group:
53
+ feature_name = group
54
+ else:
55
+ feature_name = None
56
+ result = subprocess.run(
57
+ [
58
+ "pixi",
59
+ "remove",
60
+ "--pypi",
61
+ *(["--feature", feature_name] if feature_name else []),
62
+ lib,
63
+ ],
64
+ check=False,
65
+ )
66
+ if result.returncode != 0:
67
+ raise Exit(result.returncode)