afterpython 0.1.0__py3-none-any.whl → 0.1.0.dev6__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.
- afterpython/{templates/pre-commit-config-template.yaml → .pre-commit-config.yaml} +1 -1
- afterpython/_io/toml.py +3 -5
- afterpython/_paths.py +1 -1
- afterpython/_typing.py +2 -1
- afterpython/builders/__init__.py +3 -12
- afterpython/builders/metadata.py +3 -7
- afterpython/builders/molab.py +99 -0
- afterpython/cli/__init__.py +1 -0
- afterpython/cli/commands/build.py +68 -97
- afterpython/cli/commands/bump.py +0 -30
- afterpython/cli/commands/check.py +1 -1
- afterpython/cli/commands/clean.py +3 -4
- afterpython/cli/commands/commit.py +3 -21
- afterpython/cli/commands/commitizen.py +1 -1
- afterpython/cli/commands/dev.py +9 -13
- afterpython/cli/commands/format.py +1 -1
- afterpython/cli/commands/init.py +39 -48
- afterpython/cli/commands/install.py +2 -8
- afterpython/cli/commands/pre_commit.py +1 -1
- afterpython/cli/commands/preview.py +1 -1
- afterpython/cli/commands/start.py +4 -7
- afterpython/cli/commands/sync.py +21 -53
- afterpython/cli/commands/update.py +8 -31
- afterpython/cli/main.py +14 -75
- afterpython/const.py +1 -0
- afterpython/cz.toml +27 -0
- afterpython/pcu.py +1 -3
- afterpython/templates/deploy-workflow-template.yml +5 -5
- afterpython/templates/release-workflow-template.yml +3 -3
- afterpython/templates/ruff-template.toml +1 -1
- afterpython/tools/_afterpython.py +10 -73
- afterpython/tools/_git.py +2 -28
- afterpython/tools/commitizen.py +45 -5
- afterpython/tools/github_actions.py +53 -23
- afterpython/tools/myst.py +5 -46
- afterpython/tools/pre_commit.py +31 -6
- afterpython/tools/pyproject.py +2 -40
- afterpython/utils.py +0 -82
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/METADATA +10 -34
- afterpython-0.1.0.dev6.dist-info/RECORD +47 -0
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/WHEEL +1 -1
- afterpython/builders/content_json.py +0 -131
- afterpython/builders/index_md.py +0 -94
- afterpython/builders/jupyter_notebook.py +0 -293
- afterpython/builders/markdown.py +0 -271
- afterpython/cli/commands/add.py +0 -67
- afterpython/cli/commands/init_branch_rules.py +0 -9
- afterpython/cli/commands/lock.py +0 -23
- afterpython/cli/commands/remove.py +0 -67
- afterpython/templates/ci-workflow-template.yml +0 -197
- afterpython/templates/cz-template.toml +0 -7
- afterpython/templates/dependabot-template.yml +0 -30
- afterpython/tools/branch_rules.py +0 -106
- afterpython-0.1.0.dist-info/RECORD +0 -57
- {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/entry_points.txt +0 -0
afterpython/_io/toml.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
5
|
from tomlkit.toml_document import TOMLDocument
|
|
6
|
+
from pathlib import Path
|
|
9
7
|
|
|
10
8
|
import tomlkit
|
|
11
9
|
|
|
@@ -18,7 +16,7 @@ def _from_tomlkit(value):
|
|
|
18
16
|
- tomlkit containers → plain Python containers
|
|
19
17
|
- tomlkit primitives → plain Python primitives
|
|
20
18
|
"""
|
|
21
|
-
from tomlkit.items import
|
|
19
|
+
from tomlkit.items import Array, InlineTable, Table, AoT
|
|
22
20
|
|
|
23
21
|
if value is None:
|
|
24
22
|
return None
|
|
@@ -60,7 +58,7 @@ def _to_tomlkit(value):
|
|
|
60
58
|
- List of dicts → array of inline tables: [{key = val}]
|
|
61
59
|
- Nested dicts → nested tables with proper structure
|
|
62
60
|
"""
|
|
63
|
-
from tomlkit import
|
|
61
|
+
from tomlkit import inline_table, array
|
|
64
62
|
|
|
65
63
|
if value is None:
|
|
66
64
|
return None
|
afterpython/_paths.py
CHANGED
afterpython/_typing.py
CHANGED
afterpython/builders/__init__.py
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
from afterpython.builders.content_json import build_content_json
|
|
2
|
-
from afterpython.builders.index_md import (
|
|
3
|
-
create_placeholder_index_md_files,
|
|
4
|
-
delete_placeholder_index_md_files,
|
|
5
|
-
)
|
|
6
|
-
from afterpython.builders.jupyter_notebook import build_jupyter_notebooks
|
|
7
|
-
from afterpython.builders.markdown import build_markdown
|
|
8
1
|
from afterpython.builders.metadata import build_metadata
|
|
2
|
+
from afterpython.builders.molab import add_molab_badge_to_jupyter_notebooks
|
|
3
|
+
|
|
9
4
|
|
|
10
5
|
__all__ = (
|
|
11
|
-
"
|
|
12
|
-
"build_jupyter_notebooks",
|
|
13
|
-
"build_markdown",
|
|
6
|
+
"add_molab_badge_to_jupyter_notebooks",
|
|
14
7
|
"build_metadata",
|
|
15
|
-
"create_placeholder_index_md_files",
|
|
16
|
-
"delete_placeholder_index_md_files",
|
|
17
8
|
)
|
afterpython/builders/metadata.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
@@ -7,10 +6,9 @@ if TYPE_CHECKING:
|
|
|
7
6
|
|
|
8
7
|
import json
|
|
9
8
|
|
|
10
|
-
import click
|
|
11
|
-
|
|
12
9
|
import afterpython as ap
|
|
13
10
|
|
|
11
|
+
|
|
14
12
|
build_path = ap.paths.build_path
|
|
15
13
|
|
|
16
14
|
|
|
@@ -25,22 +23,20 @@ def convert_paths():
|
|
|
25
23
|
markdown_text = metadata["description"]
|
|
26
24
|
|
|
27
25
|
# Replace with the correct paths
|
|
28
|
-
updated_markdown = markdown_text.replace("afterpython/static/", "/")
|
|
26
|
+
updated_markdown = markdown_text.replace("./afterpython/static/", "/")
|
|
29
27
|
|
|
30
28
|
# Write back to metadata.json
|
|
31
29
|
metadata["description"] = updated_markdown
|
|
32
30
|
with open(build_path / "metadata.json", "w") as f:
|
|
33
31
|
json.dump(metadata, f, indent=2)
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
print("Completed path conversion in metadata.json")
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
def build_metadata():
|
|
39
37
|
"""Build metadata.json using pyproject.toml"""
|
|
40
38
|
from afterpython.tools.pyproject import read_metadata
|
|
41
39
|
|
|
42
|
-
click.echo("Building metadata.json...")
|
|
43
|
-
|
|
44
40
|
metadata: StandardMetadata = read_metadata()
|
|
45
41
|
|
|
46
42
|
# Write to metadata.json
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import tContentType
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from pyproject_metadata import StandardMetadata
|
|
11
|
+
|
|
12
|
+
import afterpython as ap
|
|
13
|
+
from afterpython.tools.pyproject import read_metadata
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_molab_badge() -> str:
|
|
18
|
+
return "https://marimo.io/molab-shield.svg"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _create_molab_url(github_url: str, content_path: Path):
|
|
22
|
+
"""Create a molab URL for a given content type and notebook path.
|
|
23
|
+
Args:
|
|
24
|
+
github_url: str, e.g. "https://github.com/AfterPythonOrg/afterpython"
|
|
25
|
+
content_path: str, e.g. "tutorial/test.ipynb"
|
|
26
|
+
"""
|
|
27
|
+
github_url = github_url.replace("https://github.com/", "github/")
|
|
28
|
+
return f"https://molab.marimo.io/{github_url}/blob/main/afterpython/{content_path.as_posix()}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_molab_badge_to_jupyter_notebooks(content_type: tContentType):
|
|
32
|
+
"""Add a badge markdown cell to the top of Jupyter notebooks in the given content type directory, e.g. tutorial/, blog/, etc.
|
|
33
|
+
# NOTE
|
|
34
|
+
Note that the jupyter notebooks need to exist in the github repository first.
|
|
35
|
+
If you have renamed a notebook, you need to push the changes to the github repository first for the badge to work.
|
|
36
|
+
"""
|
|
37
|
+
assert content_type.lower() in CONTENT_TYPES, (
|
|
38
|
+
f"Invalid content type: {content_type}"
|
|
39
|
+
)
|
|
40
|
+
metadata: StandardMetadata = read_metadata()
|
|
41
|
+
|
|
42
|
+
if "repository" not in metadata.urls:
|
|
43
|
+
return "Repository URL not found in [project.urls] in pyproject.toml, cannot add molab badge"
|
|
44
|
+
else:
|
|
45
|
+
github_url = metadata.urls["repository"]
|
|
46
|
+
|
|
47
|
+
path = ap.paths.afterpython_path / content_type.lower()
|
|
48
|
+
|
|
49
|
+
# iterate over all files in the path
|
|
50
|
+
for notebook_path in path.rglob("*.ipynb"):
|
|
51
|
+
# Skip files in _build directory
|
|
52
|
+
if "_build" in notebook_path.parts:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Get path relative to afterpython/ (includes tutorial/ in the path)
|
|
56
|
+
content_path = notebook_path.relative_to(ap.paths.afterpython_path)
|
|
57
|
+
molab_url = _create_molab_url(github_url, content_path)
|
|
58
|
+
badge_md = f"[})]({molab_url})"
|
|
59
|
+
|
|
60
|
+
# Read the notebook
|
|
61
|
+
try:
|
|
62
|
+
# Read the notebook
|
|
63
|
+
with open(notebook_path) as f:
|
|
64
|
+
notebook = json.load(f)
|
|
65
|
+
except json.JSONDecodeError:
|
|
66
|
+
print(f"✗ Skipping {content_path} - invalid or empty JSON")
|
|
67
|
+
continue
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"✗ Error reading {content_path}: {e}")
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
# Validate notebook structure
|
|
73
|
+
if "cells" not in notebook:
|
|
74
|
+
print(f"✗ Skipping {content_path} - not a valid Jupyter notebook")
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Create a markdown cell with the badge
|
|
78
|
+
badge_cell = {
|
|
79
|
+
"cell_type": "markdown",
|
|
80
|
+
"id": "molab-badge-cell", # Unique ID for the badge cell
|
|
81
|
+
"metadata": {
|
|
82
|
+
"tags": ["molab", "hide-input"] # Optional: use MyST tags
|
|
83
|
+
},
|
|
84
|
+
"source": [badge_md],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Check if first cell is already a badge cell (avoid duplicates)
|
|
88
|
+
if notebook["cells"] and notebook["cells"][0].get("id") == "molab-badge-cell":
|
|
89
|
+
# Update existing badge
|
|
90
|
+
notebook["cells"][0] = badge_cell
|
|
91
|
+
print(f"✓ Updated molab badge in: {content_path}")
|
|
92
|
+
else:
|
|
93
|
+
# Insert at the beginning
|
|
94
|
+
notebook["cells"].insert(0, badge_cell)
|
|
95
|
+
print(f"✓ Added molab badge to: {content_path}")
|
|
96
|
+
|
|
97
|
+
# Write back
|
|
98
|
+
with open(notebook_path, "w") as f:
|
|
99
|
+
json.dump(notebook, f, indent=1)
|
afterpython/cli/__init__.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Callable
|
|
4
2
|
from typing import TYPE_CHECKING
|
|
3
|
+
from collections.abc import Callable
|
|
5
4
|
|
|
6
5
|
if TYPE_CHECKING:
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
6
|
from afterpython._typing import NodeEnv
|
|
7
|
+
from pathlib import Path
|
|
10
8
|
|
|
9
|
+
import os
|
|
11
10
|
import shutil
|
|
12
11
|
import subprocess
|
|
13
12
|
|
|
@@ -15,15 +14,12 @@ import click
|
|
|
15
14
|
from click.exceptions import Exit
|
|
16
15
|
|
|
17
16
|
import afterpython as ap
|
|
17
|
+
from afterpython.utils import find_node_env
|
|
18
|
+
from afterpython.const import CONTENT_TYPES
|
|
18
19
|
from afterpython.builders import (
|
|
19
|
-
build_content_json,
|
|
20
|
-
build_jupyter_notebooks,
|
|
21
|
-
build_markdown,
|
|
22
20
|
build_metadata,
|
|
23
|
-
|
|
24
|
-
delete_placeholder_index_md_files,
|
|
21
|
+
add_molab_badge_to_jupyter_notebooks,
|
|
25
22
|
)
|
|
26
|
-
from afterpython.const import CONTENT_TYPES
|
|
27
23
|
|
|
28
24
|
|
|
29
25
|
def determine_base_path() -> str:
|
|
@@ -33,13 +29,13 @@ def determine_base_path() -> str:
|
|
|
33
29
|
Empty string if custom domain is configured (no github.io in URL)
|
|
34
30
|
/repo-name if using default GitHub Pages URL (contains github.io)
|
|
35
31
|
"""
|
|
36
|
-
from afterpython._io.toml import _from_tomlkit
|
|
37
32
|
from afterpython.tools._afterpython import read_afterpython
|
|
38
33
|
from afterpython.tools.pyproject import read_metadata
|
|
34
|
+
from afterpython._io.toml import _from_tomlkit
|
|
39
35
|
|
|
40
36
|
# Read website URL from afterpython.toml
|
|
41
37
|
afterpython = read_afterpython()
|
|
42
|
-
website_url = str(_from_tomlkit(afterpython
|
|
38
|
+
website_url = str(_from_tomlkit(afterpython["website"]).get("url", ""))
|
|
43
39
|
|
|
44
40
|
# If custom domain (no github.io), no BASE_PATH needed
|
|
45
41
|
if "github.io" not in website_url:
|
|
@@ -71,31 +67,22 @@ def prebuild():
|
|
|
71
67
|
"Run 'ap init' first to set up your project."
|
|
72
68
|
)
|
|
73
69
|
|
|
74
|
-
def
|
|
75
|
-
|
|
70
|
+
def _clean_build_directory():
|
|
71
|
+
print("Cleaning up build directory...")
|
|
76
72
|
build_path = ap.paths.build_path
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
website_static_path = ap.paths.website_path / "static"
|
|
80
|
-
content_build_paths = [
|
|
81
|
-
website_static_path / content_type for content_type in CONTENT_TYPES
|
|
82
|
-
]
|
|
83
|
-
for path in [build_path, website_build_path, *content_build_paths]:
|
|
84
|
-
if path.exists():
|
|
85
|
-
shutil.rmtree(path)
|
|
73
|
+
if build_path.exists():
|
|
74
|
+
shutil.rmtree(build_path)
|
|
86
75
|
build_path.mkdir(parents=True, exist_ok=True)
|
|
87
76
|
|
|
88
77
|
_check_initialized()
|
|
89
|
-
|
|
78
|
+
_clean_build_directory()
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
build_markdown()
|
|
95
|
-
build_jupyter_notebooks()
|
|
80
|
+
if os.getenv("AP_MOLAB_BADGE", "0") == "1":
|
|
81
|
+
for content_type in CONTENT_TYPES:
|
|
82
|
+
add_molab_badge_to_jupyter_notebooks(content_type)
|
|
96
83
|
|
|
97
84
|
|
|
98
|
-
def postbuild(
|
|
85
|
+
def postbuild():
|
|
99
86
|
def _move_files(
|
|
100
87
|
source: Path,
|
|
101
88
|
destination: Path,
|
|
@@ -123,9 +110,6 @@ def postbuild(dev_build: bool = False):
|
|
|
123
110
|
shutil.move(str(source), str(destination))
|
|
124
111
|
print(f"Moved: {source} to {destination}")
|
|
125
112
|
|
|
126
|
-
build_content_json()
|
|
127
|
-
delete_placeholder_index_md_files()
|
|
128
|
-
|
|
129
113
|
website_static = ap.paths.website_path / "static"
|
|
130
114
|
website_static.mkdir(parents=True, exist_ok=True)
|
|
131
115
|
|
|
@@ -133,24 +117,15 @@ def postbuild(dev_build: bool = False):
|
|
|
133
117
|
myst_build = ap.paths.afterpython_path / content_type / "_build"
|
|
134
118
|
afterpython_content_build = ap.paths.build_path / content_type
|
|
135
119
|
# Move myst builds from afterpython/{content_type}/_build to afterpython/_build/{content_type}
|
|
136
|
-
_move_files(myst_build, afterpython_content_build, is_copy=
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
# for dev build, copy the files (mainly images, e.g. thumbnails) in site/public/ to afterpython/_website/static/{content_type}/build
|
|
145
|
-
else:
|
|
146
|
-
_move_files(
|
|
147
|
-
afterpython_content_build / "site" / "public",
|
|
148
|
-
# add build/ to mimic the structure of the production build
|
|
149
|
-
website_static / content_type / "build",
|
|
150
|
-
is_copy=True,
|
|
151
|
-
)
|
|
120
|
+
_move_files(myst_build, afterpython_content_build, is_copy=False)
|
|
121
|
+
# Copy afterpython/_build/{content_type}/html to afterpython/_website/static/{content_type}
|
|
122
|
+
_move_files(
|
|
123
|
+
afterpython_content_build / "html",
|
|
124
|
+
website_static / content_type,
|
|
125
|
+
is_copy=True,
|
|
126
|
+
)
|
|
152
127
|
|
|
153
|
-
# Copy all files
|
|
128
|
+
# Copy all files from afterpython/_build to afterpython/_website/static/
|
|
154
129
|
_move_files(
|
|
155
130
|
ap.paths.build_path,
|
|
156
131
|
website_static,
|
|
@@ -173,10 +148,16 @@ def postbuild(dev_build: bool = False):
|
|
|
173
148
|
),
|
|
174
149
|
)
|
|
175
150
|
@click.pass_context
|
|
151
|
+
@click.option(
|
|
152
|
+
"--dev",
|
|
153
|
+
is_flag=True,
|
|
154
|
+
hidden=True, # Internal flag used by `ap dev`, not exposed to users
|
|
155
|
+
help="Development build - only build metadata for the landing page, skip content and production builds",
|
|
156
|
+
)
|
|
176
157
|
@click.option(
|
|
177
158
|
"--execute", is_flag=True, help="Execute Jupyter notebooks for all content types"
|
|
178
159
|
)
|
|
179
|
-
def build(ctx:
|
|
160
|
+
def build(ctx, dev: bool, execute: bool):
|
|
180
161
|
"""Build the project website and all contents for production.
|
|
181
162
|
|
|
182
163
|
This command builds MyST content (doc/blog/tutorial/example/guide) and the SvelteKit website.
|
|
@@ -186,11 +167,8 @@ def build(ctx: click.Context, execute: bool):
|
|
|
186
167
|
|
|
187
168
|
Use --execute to execute Jupyter notebooks for all content types.
|
|
188
169
|
"""
|
|
189
|
-
from afterpython.utils import
|
|
190
|
-
|
|
191
|
-
handle_passthrough_help,
|
|
192
|
-
has_content_for_myst,
|
|
193
|
-
)
|
|
170
|
+
from afterpython.utils import has_content_for_myst
|
|
171
|
+
from afterpython.utils import handle_passthrough_help
|
|
194
172
|
|
|
195
173
|
# Show both our options and myst's help and exit
|
|
196
174
|
handle_passthrough_help(
|
|
@@ -208,53 +186,46 @@ def build(ctx: click.Context, execute: bool):
|
|
|
208
186
|
click.echo(f"Using BASE_PATH: {base_path}")
|
|
209
187
|
else:
|
|
210
188
|
click.echo("Using BASE_PATH: (empty - custom domain)")
|
|
211
|
-
node_env: NodeEnv = find_node_env()
|
|
212
|
-
|
|
213
|
-
# myst's production build
|
|
214
|
-
for content_type in CONTENT_TYPES:
|
|
215
|
-
content_path = ap.paths.afterpython_path / content_type
|
|
216
|
-
myst_yml_path = content_path / "myst.yml"
|
|
217
189
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
continue
|
|
190
|
+
click.echo("Building metadata.json...")
|
|
191
|
+
build_metadata()
|
|
221
192
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
193
|
+
# myst's production build
|
|
194
|
+
if not dev:
|
|
195
|
+
node_env: NodeEnv = find_node_env()
|
|
196
|
+
for content_type in CONTENT_TYPES:
|
|
197
|
+
content_path = paths.afterpython_path / content_type
|
|
198
|
+
if not has_content_for_myst(content_path):
|
|
199
|
+
click.echo(f"Skipping {content_type}/ (no content files found)")
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
click.echo(f"Building {content_type}/...")
|
|
203
|
+
# NOTE: needs to set BASE_URL so that the project website can link to the content pages correctly at e.g. localhost:5173/doc
|
|
204
|
+
base_url = f"{base_path}/{content_type}"
|
|
205
|
+
build_env = {**node_env, "BASE_URL": base_url, "BASE_PATH": base_path}
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
[
|
|
208
|
+
"myst",
|
|
209
|
+
"build",
|
|
210
|
+
"--html",
|
|
211
|
+
*(["--execute"] if execute else []),
|
|
212
|
+
*ctx.args,
|
|
213
|
+
],
|
|
214
|
+
cwd=content_path,
|
|
215
|
+
env=build_env,
|
|
216
|
+
check=False,
|
|
225
217
|
)
|
|
226
|
-
|
|
218
|
+
if result.returncode != 0:
|
|
219
|
+
raise Exit(result.returncode)
|
|
227
220
|
|
|
228
|
-
|
|
229
|
-
click.echo(f"Skipping {content_type}/ (no content files found)")
|
|
230
|
-
continue
|
|
221
|
+
postbuild()
|
|
231
222
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
223
|
+
# website's production build
|
|
224
|
+
if not dev:
|
|
225
|
+
click.echo("Building project website...")
|
|
226
|
+
website_env = {**node_env, "BASE_PATH": base_path}
|
|
236
227
|
result = subprocess.run(
|
|
237
|
-
[
|
|
238
|
-
"myst",
|
|
239
|
-
"build",
|
|
240
|
-
"--html",
|
|
241
|
-
*(["--execute"] if execute else []),
|
|
242
|
-
*ctx.args,
|
|
243
|
-
],
|
|
244
|
-
cwd=content_path,
|
|
245
|
-
env=build_env,
|
|
246
|
-
check=False,
|
|
228
|
+
["pnpm", "build"], cwd=paths.website_path, env=website_env, check=False
|
|
247
229
|
)
|
|
248
|
-
|
|
249
230
|
if result.returncode != 0:
|
|
250
231
|
raise Exit(result.returncode)
|
|
251
|
-
|
|
252
|
-
postbuild(dev_build=False)
|
|
253
|
-
|
|
254
|
-
click.echo("Building project website...")
|
|
255
|
-
website_env = {**node_env, "BASE_PATH": base_path}
|
|
256
|
-
result = subprocess.run(
|
|
257
|
-
["pnpm", "build"], cwd=paths.website_path, env=website_env, check=False
|
|
258
|
-
)
|
|
259
|
-
if result.returncode != 0:
|
|
260
|
-
raise Exit(result.returncode)
|
afterpython/cli/commands/bump.py
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
|
-
from click.exceptions import Exit
|
|
5
|
-
|
|
6
|
-
from afterpython.utils import has_pixi
|
|
7
4
|
|
|
8
5
|
|
|
9
6
|
@click.command(
|
|
@@ -89,30 +86,3 @@ def bump(ctx, release: bool, pre: bool):
|
|
|
89
86
|
args = ["--prerelease", prerelease_type, *ctx.args]
|
|
90
87
|
|
|
91
88
|
subprocess.run(["ap", "cz", "bump", *args])
|
|
92
|
-
|
|
93
|
-
# Update pixi.lock after version bump (editable install requires lock refresh)
|
|
94
|
-
if has_pixi():
|
|
95
|
-
result = subprocess.run(["pixi", "install"], check=False)
|
|
96
|
-
if result.returncode != 0:
|
|
97
|
-
raise Exit(result.returncode)
|
|
98
|
-
|
|
99
|
-
# Check if pixi.lock was modified
|
|
100
|
-
status = subprocess.run(
|
|
101
|
-
["git", "diff", "--name-only", "pixi.lock"],
|
|
102
|
-
capture_output=True,
|
|
103
|
-
text=True,
|
|
104
|
-
check=False,
|
|
105
|
-
)
|
|
106
|
-
if status.stdout.strip():
|
|
107
|
-
# Stage and commit pixi.lock
|
|
108
|
-
subprocess.run(["git", "add", "pixi.lock"], check=True)
|
|
109
|
-
subprocess.run(
|
|
110
|
-
[
|
|
111
|
-
"git",
|
|
112
|
-
"commit",
|
|
113
|
-
"-m",
|
|
114
|
-
"build: update pixi.lock after version bump",
|
|
115
|
-
],
|
|
116
|
-
check=True,
|
|
117
|
-
)
|
|
118
|
-
click.echo("✓ Committed pixi.lock update")
|
|
@@ -13,7 +13,7 @@ from click.exceptions import Exit
|
|
|
13
13
|
)
|
|
14
14
|
@click.pass_context
|
|
15
15
|
def check(ctx):
|
|
16
|
-
"""
|
|
16
|
+
"""Simple wrapper for ruff check for convenience, always use the afterpython/ruff.toml if it exists"""
|
|
17
17
|
from afterpython.utils import handle_passthrough_help
|
|
18
18
|
|
|
19
19
|
# Show both our options and ruff's help and exit
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
@@ -11,8 +10,8 @@ import subprocess
|
|
|
11
10
|
import click
|
|
12
11
|
from click.exceptions import Exit
|
|
13
12
|
|
|
14
|
-
from afterpython.const import CONTENT_TYPES
|
|
15
13
|
from afterpython.utils import find_node_env, has_content_for_myst
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
@click.command(
|
|
@@ -26,10 +25,10 @@ from afterpython.utils import find_node_env, has_content_for_myst
|
|
|
26
25
|
@click.option(
|
|
27
26
|
"--all",
|
|
28
27
|
is_flag=True,
|
|
29
|
-
help="
|
|
28
|
+
help='--all passed to "myst clean", also clean afterpython build directories (_build/ and _website/build/)',
|
|
30
29
|
)
|
|
31
30
|
def clean(ctx, all: bool):
|
|
32
|
-
"""Clean build
|
|
31
|
+
"""Clean the build directory"""
|
|
33
32
|
from afterpython.utils import handle_passthrough_help
|
|
34
33
|
|
|
35
34
|
# Show both our options and myst's help and exit
|
|
@@ -13,19 +13,10 @@ import click
|
|
|
13
13
|
)
|
|
14
14
|
@click.pass_context
|
|
15
15
|
@click.option(
|
|
16
|
-
"--no-cz",
|
|
17
|
-
"--no-commitizen",
|
|
18
|
-
is_flag=True,
|
|
19
|
-
help="Skip 'commitizen' and 'commitizen-branch' pre-commit hooks",
|
|
16
|
+
"--no-cz", "--no-commitizen", is_flag=True, help="Skip running 'cz commit'"
|
|
20
17
|
)
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"--no-pre-commit",
|
|
24
|
-
is_flag=True,
|
|
25
|
-
help="Skip running pre-commit checks before commit",
|
|
26
|
-
)
|
|
27
|
-
def commit(ctx, no_cz: bool, no_pc: bool):
|
|
28
|
-
"""Create a conventional commit with interactive prompts"""
|
|
18
|
+
def commit(ctx, no_cz: bool):
|
|
19
|
+
"""Run 'cz commit'"""
|
|
29
20
|
from afterpython.utils import handle_passthrough_help
|
|
30
21
|
|
|
31
22
|
# Show both our options and commitizen's help and exit
|
|
@@ -35,15 +26,6 @@ def commit(ctx, no_cz: bool, no_pc: bool):
|
|
|
35
26
|
show_underlying=True,
|
|
36
27
|
)
|
|
37
28
|
|
|
38
|
-
# Run pre-commit checks first (unless --no-pc is set)
|
|
39
|
-
if not no_pc:
|
|
40
|
-
result = subprocess.run(["ap", "pc", "run"])
|
|
41
|
-
if result.returncode != 0:
|
|
42
|
-
click.echo(
|
|
43
|
-
"❌ Pre-commit checks failed. Please fix the issues and try again."
|
|
44
|
-
)
|
|
45
|
-
ctx.exit(1)
|
|
46
|
-
|
|
47
29
|
if not no_cz:
|
|
48
30
|
subprocess.run(["ap", "cz", "commit", *ctx.args])
|
|
49
31
|
else:
|
|
@@ -13,7 +13,7 @@ from click.exceptions import Exit
|
|
|
13
13
|
)
|
|
14
14
|
@click.pass_context
|
|
15
15
|
def commitizen(ctx):
|
|
16
|
-
"""Run commitizen
|
|
16
|
+
"""Run commitizen"""
|
|
17
17
|
from afterpython.utils import handle_passthrough_help
|
|
18
18
|
|
|
19
19
|
# Show both our options and commitizen's help and exit
|
afterpython/cli/commands/dev.py
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
2
|
from typing import TYPE_CHECKING
|
|
4
3
|
|
|
5
4
|
if TYPE_CHECKING:
|
|
6
5
|
from afterpython._typing import NodeEnv
|
|
7
6
|
|
|
8
|
-
import subprocess
|
|
9
7
|
import time
|
|
8
|
+
import subprocess
|
|
10
9
|
|
|
11
10
|
import click
|
|
12
11
|
from click.exceptions import Exit
|
|
13
12
|
|
|
14
|
-
from afterpython.
|
|
13
|
+
from afterpython.utils import find_node_env, find_available_port
|
|
15
14
|
from afterpython.const import CONTENT_TYPES
|
|
16
|
-
from afterpython.utils import find_available_port, find_node_env
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
@click.command(
|
|
@@ -108,6 +106,9 @@ def dev(
|
|
|
108
106
|
|
|
109
107
|
paths = ctx.obj["paths"]
|
|
110
108
|
|
|
109
|
+
# OPTIMIZE: should implement incremental build?
|
|
110
|
+
subprocess.run(["ap", "build", "--dev"])
|
|
111
|
+
|
|
111
112
|
def cleanup_processes():
|
|
112
113
|
"""Clean up all MyST server processes"""
|
|
113
114
|
click.echo("\nShutting down MyST servers...")
|
|
@@ -138,14 +139,11 @@ def dev(
|
|
|
138
139
|
enabled_content_types = {ct for ct, flag in content_flags.items() if flag}
|
|
139
140
|
|
|
140
141
|
try:
|
|
141
|
-
prebuild()
|
|
142
|
-
|
|
143
|
-
# Clear .env.development before writing new ports
|
|
144
|
-
env_file = paths.website_path / ".env.development"
|
|
145
|
-
env_file.write_text("") # Clear existing content
|
|
146
|
-
|
|
147
|
-
# myst development servers
|
|
148
142
|
if enabled_content_types:
|
|
143
|
+
# Clear .env.development before writing new ports
|
|
144
|
+
env_file = paths.website_path / ".env.development"
|
|
145
|
+
env_file.write_text("") # Clear existing content
|
|
146
|
+
|
|
149
147
|
next_port = 3000
|
|
150
148
|
for content_type in CONTENT_TYPES:
|
|
151
149
|
# Skip content types that are not enabled
|
|
@@ -187,8 +185,6 @@ def dev(
|
|
|
187
185
|
# causing "address already in use" errors.
|
|
188
186
|
time.sleep(3)
|
|
189
187
|
|
|
190
|
-
postbuild(dev_build=True)
|
|
191
|
-
|
|
192
188
|
if not no_website:
|
|
193
189
|
node_env: NodeEnv = find_node_env()
|
|
194
190
|
click.echo("Running the web dev server...")
|
|
@@ -13,7 +13,7 @@ from click.exceptions import Exit
|
|
|
13
13
|
)
|
|
14
14
|
@click.pass_context
|
|
15
15
|
def format(ctx):
|
|
16
|
-
"""
|
|
16
|
+
"""Simple wrapper for ruff format for convenience"""
|
|
17
17
|
from afterpython.utils import handle_passthrough_help
|
|
18
18
|
|
|
19
19
|
# Show both our options and ruff's help and exit
|