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.
Files changed (55) hide show
  1. afterpython/{templates/pre-commit-config-template.yaml → .pre-commit-config.yaml} +1 -1
  2. afterpython/_io/toml.py +3 -5
  3. afterpython/_paths.py +1 -1
  4. afterpython/_typing.py +2 -1
  5. afterpython/builders/__init__.py +3 -12
  6. afterpython/builders/metadata.py +3 -7
  7. afterpython/builders/molab.py +99 -0
  8. afterpython/cli/__init__.py +1 -0
  9. afterpython/cli/commands/build.py +68 -97
  10. afterpython/cli/commands/bump.py +0 -30
  11. afterpython/cli/commands/check.py +1 -1
  12. afterpython/cli/commands/clean.py +3 -4
  13. afterpython/cli/commands/commit.py +3 -21
  14. afterpython/cli/commands/commitizen.py +1 -1
  15. afterpython/cli/commands/dev.py +9 -13
  16. afterpython/cli/commands/format.py +1 -1
  17. afterpython/cli/commands/init.py +39 -48
  18. afterpython/cli/commands/install.py +2 -8
  19. afterpython/cli/commands/pre_commit.py +1 -1
  20. afterpython/cli/commands/preview.py +1 -1
  21. afterpython/cli/commands/start.py +4 -7
  22. afterpython/cli/commands/sync.py +21 -53
  23. afterpython/cli/commands/update.py +8 -31
  24. afterpython/cli/main.py +14 -75
  25. afterpython/const.py +1 -0
  26. afterpython/cz.toml +27 -0
  27. afterpython/pcu.py +1 -3
  28. afterpython/templates/deploy-workflow-template.yml +5 -5
  29. afterpython/templates/release-workflow-template.yml +3 -3
  30. afterpython/templates/ruff-template.toml +1 -1
  31. afterpython/tools/_afterpython.py +10 -73
  32. afterpython/tools/_git.py +2 -28
  33. afterpython/tools/commitizen.py +45 -5
  34. afterpython/tools/github_actions.py +53 -23
  35. afterpython/tools/myst.py +5 -46
  36. afterpython/tools/pre_commit.py +31 -6
  37. afterpython/tools/pyproject.py +2 -40
  38. afterpython/utils.py +0 -82
  39. {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/METADATA +10 -34
  40. afterpython-0.1.0.dev6.dist-info/RECORD +47 -0
  41. {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/WHEEL +1 -1
  42. afterpython/builders/content_json.py +0 -131
  43. afterpython/builders/index_md.py +0 -94
  44. afterpython/builders/jupyter_notebook.py +0 -293
  45. afterpython/builders/markdown.py +0 -271
  46. afterpython/cli/commands/add.py +0 -67
  47. afterpython/cli/commands/init_branch_rules.py +0 -9
  48. afterpython/cli/commands/lock.py +0 -23
  49. afterpython/cli/commands/remove.py +0 -67
  50. afterpython/templates/ci-workflow-template.yml +0 -197
  51. afterpython/templates/cz-template.toml +0 -7
  52. afterpython/templates/dependabot-template.yml +0 -30
  53. afterpython/tools/branch_rules.py +0 -106
  54. afterpython-0.1.0.dist-info/RECORD +0 -57
  55. {afterpython-0.1.0.dist-info → afterpython-0.1.0.dev6.dist-info}/entry_points.txt +0 -0
@@ -23,7 +23,7 @@ repos:
23
23
  stages:
24
24
  - pre-commit
25
25
  - repo: https://github.com/astral-sh/ruff-pre-commit
26
- rev: v0.14.8
26
+ rev: v0.14.5
27
27
  hooks:
28
28
  - id: ruff-check
29
29
  stages:
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 AoT, Array, InlineTable, Table
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 array, inline_table
61
+ from tomlkit import inline_table, array
64
62
 
65
63
  if value is None:
66
64
  return None
afterpython/_paths.py CHANGED
@@ -1,5 +1,5 @@
1
- from dataclasses import dataclass, field
2
1
  from pathlib import Path
2
+ from dataclasses import dataclass, field
3
3
 
4
4
 
5
5
  @dataclass
afterpython/_typing.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Literal, TypeAlias
1
+ from typing import TypeAlias, Literal
2
+
2
3
 
3
4
  NodeEnv: TypeAlias = dict[str, str]
4
5
  tContentType = Literal["doc", "blog", "tutorial", "example", "guide"]
@@ -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
- "build_content_json",
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
  )
@@ -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
- click.echo("Completed path conversion in metadata.json")
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"[![Open in molab]({_get_molab_badge()})]({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)
@@ -1,3 +1,4 @@
1
1
  from afterpython.cli.main import afterpython_group
2
2
 
3
+
3
4
  __all__ = ["afterpython_group"]
@@ -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
- create_placeholder_index_md_files,
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.get("website", {})).get("url", ""))
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 _clean_up_builds():
75
- click.echo("Cleaning up builds...")
70
+ def _clean_build_directory():
71
+ print("Cleaning up build directory...")
76
72
  build_path = ap.paths.build_path
77
- website_build_path = ap.paths.website_path / "build"
78
- # clean up content builds in _website/static/{content_type}
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
- _clean_up_builds()
78
+ _clean_build_directory()
90
79
 
91
- delete_placeholder_index_md_files()
92
- create_placeholder_index_md_files()
93
- build_metadata()
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(dev_build: bool = False):
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=True)
137
- # for production build, copy afterpython/_build/{content_type}/html to afterpython/_website/static/{content_type}
138
- if not dev_build:
139
- _move_files(
140
- afterpython_content_build / "html",
141
- website_static / content_type,
142
- is_copy=True,
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 (e.g. metadata.json, blog.json etc.) from afterpython/_build to afterpython/_website/static/
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: click.Context, execute: bool):
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
- find_node_env,
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
- if not content_path.exists():
219
- click.echo(f"{content_path} does not exist, skip building {content_type}/")
220
- continue
190
+ click.echo("Building metadata.json...")
191
+ build_metadata()
221
192
 
222
- if not myst_yml_path.exists():
223
- click.echo(
224
- f"No myst.yml found in {content_path}, skip building {content_type}/"
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
- continue
218
+ if result.returncode != 0:
219
+ raise Exit(result.returncode)
227
220
 
228
- if not has_content_for_myst(content_path):
229
- click.echo(f"Skipping {content_type}/ (no content files found)")
230
- continue
221
+ postbuild()
231
222
 
232
- click.echo(f"Building {content_type}/...")
233
- # NOTE: needs to set BASE_URL (used by MyST) so that the project website can link to the content pages correctly at e.g. localhost:5173/doc
234
- base_url = f"{base_path}/{content_type}"
235
- build_env = {**node_env, "BASE_URL": base_url, "BASE_PATH": base_path}
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)
@@ -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
- """Run ruff linter (uses afterpython/ruff.toml if available)"""
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="Also clean AfterPython build directories (_build/ and _website/build/)",
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 artifacts from content types and website"""
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
- @click.option(
22
- "--no-pc",
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 CLI (uses afterpython/cz.toml)"""
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
@@ -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.cli.commands.build import postbuild, prebuild
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
- """Run ruff formatter"""
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