afterpython 0.0.1.dev2__py3-none-any.whl → 0.1.0.dev5__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/.pre-commit-config.yaml +48 -0
- afterpython/__init__.py +13 -0
- afterpython/_io/toml.py +103 -0
- afterpython/_io/yaml.py +26 -0
- afterpython/_paths.py +39 -0
- afterpython/_typing.py +5 -0
- afterpython/builders/__init__.py +8 -0
- afterpython/builders/metadata.py +46 -0
- afterpython/builders/molab.py +99 -0
- afterpython/cli/__init__.py +4 -0
- afterpython/cli/commands/build.py +190 -0
- afterpython/cli/commands/bump.py +88 -0
- afterpython/cli/commands/check.py +36 -0
- afterpython/cli/commands/clean.py +84 -0
- afterpython/cli/commands/commit.py +34 -0
- afterpython/cli/commands/commitizen.py +32 -0
- afterpython/cli/commands/dev.py +146 -0
- afterpython/cli/commands/format.py +28 -0
- afterpython/cli/commands/init.py +98 -0
- afterpython/cli/commands/install.py +22 -0
- afterpython/cli/commands/pre_commit.py +66 -0
- afterpython/cli/commands/preview.py +22 -0
- afterpython/cli/commands/release.py +67 -0
- afterpython/cli/commands/start.py +140 -0
- afterpython/cli/commands/sync.py +108 -0
- afterpython/cli/commands/update.py +153 -0
- afterpython/cli/main.py +71 -0
- afterpython/const.py +5 -0
- afterpython/cz.toml +27 -0
- afterpython/main.py +25 -0
- afterpython/pcu.py +164 -0
- afterpython/templates/deploy-workflow-template.yml +90 -0
- afterpython/templates/release-workflow-template.yml +56 -0
- afterpython/templates/ruff-template.toml +32 -0
- afterpython/tools/_afterpython.py +65 -0
- afterpython/tools/_git.py +111 -0
- afterpython/tools/commitizen.py +53 -0
- afterpython/tools/github_actions.py +78 -0
- afterpython/tools/myst.py +164 -0
- afterpython/tools/pre_commit.py +62 -0
- afterpython/tools/pyproject.py +72 -0
- afterpython/utils.py +215 -0
- afterpython/version.py +39 -0
- afterpython-0.1.0.dev5.dist-info/METADATA +105 -0
- afterpython-0.1.0.dev5.dist-info/RECORD +47 -0
- afterpython-0.1.0.dev5.dist-info/WHEEL +4 -0
- afterpython-0.1.0.dev5.dist-info/entry_points.txt +4 -0
- afterpython/README.py +0 -158
- afterpython/images/favicon.ico +0 -0
- afterpython/images/logo-trace.svg +0 -3
- afterpython/images/logo.png +0 -0
- afterpython/images/logo.svg +0 -2
- afterpython-0.0.1.dev2.dist-info/LICENSE +0 -201
- afterpython-0.0.1.dev2.dist-info/METADATA +0 -20
- afterpython-0.0.1.dev2.dist-info/RECORD +0 -9
- afterpython-0.0.1.dev2.dist-info/WHEEL +0 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
add_help_option=False, # disable click's --help option so that ruff check --help can work
|
|
9
|
+
context_settings=dict(
|
|
10
|
+
ignore_unknown_options=True,
|
|
11
|
+
allow_extra_args=True,
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def check(ctx):
|
|
16
|
+
"""Simple wrapper for ruff check for convenience, always use the afterpython/ruff.toml if it exists"""
|
|
17
|
+
from afterpython.utils import handle_passthrough_help
|
|
18
|
+
|
|
19
|
+
# Show both our options and ruff's help and exit
|
|
20
|
+
handle_passthrough_help(
|
|
21
|
+
ctx,
|
|
22
|
+
["ruff", "check"],
|
|
23
|
+
show_underlying=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
paths = ctx.obj["paths"]
|
|
27
|
+
ruff_toml = paths.afterpython_path / "ruff.toml"
|
|
28
|
+
if ruff_toml.exists():
|
|
29
|
+
click.echo(f"Using ruff configuration from {ruff_toml}")
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
["ruff", "check", "--config", str(ruff_toml), *ctx.args], check=False
|
|
32
|
+
)
|
|
33
|
+
else:
|
|
34
|
+
result = subprocess.run(["ruff", "check", *ctx.args], check=False)
|
|
35
|
+
if result.returncode != 0:
|
|
36
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import NodeEnv
|
|
6
|
+
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from click.exceptions import Exit
|
|
12
|
+
|
|
13
|
+
from afterpython.utils import find_node_env, has_content_for_myst
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command(
|
|
18
|
+
add_help_option=False, # disable click's --help option so that ap clean --help can work
|
|
19
|
+
context_settings=dict(
|
|
20
|
+
ignore_unknown_options=True,
|
|
21
|
+
allow_extra_args=True,
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@click.option(
|
|
26
|
+
"--all",
|
|
27
|
+
is_flag=True,
|
|
28
|
+
help='--all passed to "myst clean", also clean afterpython build directories (_build/ and _website/build/)',
|
|
29
|
+
)
|
|
30
|
+
def clean(ctx, all: bool):
|
|
31
|
+
"""Clean the build directory"""
|
|
32
|
+
from afterpython.utils import handle_passthrough_help
|
|
33
|
+
|
|
34
|
+
# Show both our options and myst's help and exit
|
|
35
|
+
handle_passthrough_help(
|
|
36
|
+
ctx,
|
|
37
|
+
["myst", "clean"],
|
|
38
|
+
show_underlying=True,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
paths = ctx.obj["paths"]
|
|
42
|
+
node_env: NodeEnv = find_node_env()
|
|
43
|
+
|
|
44
|
+
# Clean MyST builds for each content type
|
|
45
|
+
for content_type in CONTENT_TYPES:
|
|
46
|
+
content_path = paths.afterpython_path / content_type
|
|
47
|
+
|
|
48
|
+
# Skip if no content exists
|
|
49
|
+
if not has_content_for_myst(content_path):
|
|
50
|
+
click.echo(f"Skipping {content_type}/ (no content found)")
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
click.echo(f"Cleaning {content_type}/...")
|
|
54
|
+
# Pass through any extra args to myst clean (e.g., --cache, --templates)
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
["myst", "clean", *ctx.args, *(["--all"] if all else [])],
|
|
57
|
+
cwd=content_path,
|
|
58
|
+
env=node_env,
|
|
59
|
+
check=False,
|
|
60
|
+
)
|
|
61
|
+
if result.returncode != 0:
|
|
62
|
+
raise Exit(result.returncode)
|
|
63
|
+
|
|
64
|
+
# Clean afterpython's build directories if --all flag is used
|
|
65
|
+
if all:
|
|
66
|
+
click.echo("Cleaning afterpython build directories...")
|
|
67
|
+
|
|
68
|
+
# Clean _build/
|
|
69
|
+
build_path = paths.build_path
|
|
70
|
+
if build_path.exists():
|
|
71
|
+
shutil.rmtree(build_path)
|
|
72
|
+
click.echo(f" Removed: {build_path}")
|
|
73
|
+
|
|
74
|
+
# Clean _website/build/
|
|
75
|
+
website_build = paths.website_path / "build"
|
|
76
|
+
if website_build.exists():
|
|
77
|
+
shutil.rmtree(website_build)
|
|
78
|
+
click.echo(f" Removed: {website_build}")
|
|
79
|
+
|
|
80
|
+
click.echo("Done cleaning all build directories!")
|
|
81
|
+
else:
|
|
82
|
+
click.echo(
|
|
83
|
+
"Done! Use 'ap clean --all' to also remove afterpython build directories."
|
|
84
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
add_help_option=False, # disable click's --help option so that cz --help can work
|
|
9
|
+
context_settings=dict(
|
|
10
|
+
ignore_unknown_options=True,
|
|
11
|
+
allow_extra_args=True,
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
@click.option(
|
|
16
|
+
"--no-cz", "--no-commitizen", is_flag=True, help="Skip running 'cz commit'"
|
|
17
|
+
)
|
|
18
|
+
def commit(ctx, no_cz: bool):
|
|
19
|
+
"""Run 'cz commit'"""
|
|
20
|
+
from afterpython.utils import handle_passthrough_help
|
|
21
|
+
|
|
22
|
+
# Show both our options and commitizen's help and exit
|
|
23
|
+
handle_passthrough_help(
|
|
24
|
+
ctx,
|
|
25
|
+
["cz", "commit"],
|
|
26
|
+
show_underlying=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if not no_cz:
|
|
30
|
+
subprocess.run(["ap", "cz", "commit", *ctx.args])
|
|
31
|
+
else:
|
|
32
|
+
env = os.environ.copy()
|
|
33
|
+
env["SKIP"] = "commitizen,commitizen-branch"
|
|
34
|
+
subprocess.run(["git", "commit", *ctx.args], env=env)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
add_help_option=False, # disable click's --help option so that cz --help can work
|
|
9
|
+
context_settings=dict(
|
|
10
|
+
ignore_unknown_options=True,
|
|
11
|
+
allow_extra_args=True,
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def commitizen(ctx):
|
|
16
|
+
"""Run commitizen"""
|
|
17
|
+
from afterpython.utils import handle_passthrough_help
|
|
18
|
+
|
|
19
|
+
# Show both our options and commitizen's help and exit
|
|
20
|
+
handle_passthrough_help(
|
|
21
|
+
ctx,
|
|
22
|
+
["cz"],
|
|
23
|
+
show_underlying=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
paths = ctx.obj["paths"]
|
|
27
|
+
cz_toml_path = paths.afterpython_path / "cz.toml"
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
["cz", "--config", str(cz_toml_path), *ctx.args], check=False
|
|
30
|
+
)
|
|
31
|
+
if result.returncode != 0:
|
|
32
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from afterpython._typing import NodeEnv
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
from click.exceptions import Exit
|
|
12
|
+
|
|
13
|
+
from afterpython.utils import find_node_env, find_available_port
|
|
14
|
+
from afterpython.const import CONTENT_TYPES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command(
|
|
18
|
+
add_help_option=False, # disable click's --help option so that ap dev --help can work
|
|
19
|
+
context_settings=dict(
|
|
20
|
+
ignore_unknown_options=True,
|
|
21
|
+
allow_extra_args=True,
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@click.option(
|
|
26
|
+
"--all",
|
|
27
|
+
is_flag=True,
|
|
28
|
+
help="Start the development server for all content types and the project website",
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--execute", is_flag=True, help="Execute Jupyter notebooks for all content types"
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--no-website",
|
|
35
|
+
is_flag=True,
|
|
36
|
+
help="Skip running the website dev server (pnpm dev). Useful when you want to run pnpm dev manually with custom options.",
|
|
37
|
+
)
|
|
38
|
+
def dev(ctx, all: bool, execute: bool, no_website: bool):
|
|
39
|
+
"""Run the development server for the project website.
|
|
40
|
+
|
|
41
|
+
If --all is enabled, starts MyST dev servers for all content types (doc/blog/tutorial/example/guide)
|
|
42
|
+
and the project website.
|
|
43
|
+
|
|
44
|
+
Any extra arguments are passed to the MyST servers (via 'ap doc/blog/tutorial/example/guide' commands).
|
|
45
|
+
See "myst start --help" for more details.
|
|
46
|
+
|
|
47
|
+
Use --execute to execute Jupyter notebooks for all content types.
|
|
48
|
+
|
|
49
|
+
Use --no-website to skip the automatic 'pnpm dev' command, allowing you to run it manually
|
|
50
|
+
with custom Vite options in the afterpython/_website directory.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from afterpython.utils import handle_passthrough_help
|
|
54
|
+
|
|
55
|
+
# Show both our options and myst's help and exit
|
|
56
|
+
handle_passthrough_help(
|
|
57
|
+
ctx,
|
|
58
|
+
["myst", "start"],
|
|
59
|
+
show_underlying=True,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Track all MyST processes for cleanup
|
|
63
|
+
myst_processes = []
|
|
64
|
+
|
|
65
|
+
paths = ctx.obj["paths"]
|
|
66
|
+
|
|
67
|
+
# OPTIMIZE: should implement incremental build?
|
|
68
|
+
subprocess.run(["ap", "build", "--dev"])
|
|
69
|
+
|
|
70
|
+
def cleanup_processes():
|
|
71
|
+
"""Clean up all MyST server processes"""
|
|
72
|
+
click.echo("\nShutting down MyST servers...")
|
|
73
|
+
for proc in myst_processes:
|
|
74
|
+
try:
|
|
75
|
+
proc.terminate()
|
|
76
|
+
proc.wait(timeout=5)
|
|
77
|
+
except subprocess.TimeoutExpired:
|
|
78
|
+
proc.kill()
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
if all:
|
|
84
|
+
# Clear .env.development before writing new ports
|
|
85
|
+
env_file = paths.website_path / ".env.development"
|
|
86
|
+
env_file.write_text("") # Clear existing content
|
|
87
|
+
|
|
88
|
+
next_port = 3000
|
|
89
|
+
for content_type in CONTENT_TYPES:
|
|
90
|
+
# Find available port for MyST server
|
|
91
|
+
myst_port = find_available_port(start_port=next_port)
|
|
92
|
+
next_port = myst_port + 1
|
|
93
|
+
click.echo(
|
|
94
|
+
click.style(
|
|
95
|
+
f"Starting MyST {content_type} server on port {myst_port}...",
|
|
96
|
+
fg="green",
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Append port to .env.development for SvelteKit
|
|
101
|
+
with open(env_file, "a") as f:
|
|
102
|
+
f.write(
|
|
103
|
+
f"PUBLIC_{content_type.upper()}_URL=http://localhost:{myst_port}\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
myst_process = subprocess.Popen(
|
|
107
|
+
[
|
|
108
|
+
"ap",
|
|
109
|
+
f"{content_type}",
|
|
110
|
+
"--port",
|
|
111
|
+
str(myst_port),
|
|
112
|
+
*(["--execute"] if execute else []),
|
|
113
|
+
*ctx.args,
|
|
114
|
+
],
|
|
115
|
+
# stdout=subprocess.DEVNULL, # Suppress output (optional)
|
|
116
|
+
# stderr=subprocess.DEVNULL, # Suppress errors (optional)
|
|
117
|
+
)
|
|
118
|
+
myst_processes.append(myst_process)
|
|
119
|
+
|
|
120
|
+
# NOTE: MyST internally uses additional ports beyond the one specified by --port.
|
|
121
|
+
# Without this delay, multiple MyST servers may attempt to bind to the same internal port,
|
|
122
|
+
# causing "address already in use" errors.
|
|
123
|
+
time.sleep(3)
|
|
124
|
+
|
|
125
|
+
if not no_website:
|
|
126
|
+
node_env: NodeEnv = find_node_env()
|
|
127
|
+
click.echo("Running the web dev server...")
|
|
128
|
+
result = subprocess.run(
|
|
129
|
+
["pnpm", "dev"], cwd=paths.website_path, env=node_env, check=False
|
|
130
|
+
)
|
|
131
|
+
if result.returncode != 0:
|
|
132
|
+
raise Exit(result.returncode)
|
|
133
|
+
else:
|
|
134
|
+
click.echo(
|
|
135
|
+
"Skipping website dev server (--no-website flag). Run 'pnpm dev' manually in afterpython/_website/ with your custom options."
|
|
136
|
+
)
|
|
137
|
+
if all:
|
|
138
|
+
# Keep the process running to maintain MyST servers
|
|
139
|
+
click.echo("Press Ctrl+C to stop MyST servers...")
|
|
140
|
+
while True:
|
|
141
|
+
time.sleep(1)
|
|
142
|
+
except KeyboardInterrupt:
|
|
143
|
+
# Handle Ctrl+C during subprocess.run
|
|
144
|
+
pass
|
|
145
|
+
finally:
|
|
146
|
+
cleanup_processes()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
add_help_option=False, # disable click's --help option so that ruff format --help can work
|
|
9
|
+
context_settings=dict(
|
|
10
|
+
ignore_unknown_options=True,
|
|
11
|
+
allow_extra_args=True,
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def format(ctx):
|
|
16
|
+
"""Simple wrapper for ruff format for convenience"""
|
|
17
|
+
from afterpython.utils import handle_passthrough_help
|
|
18
|
+
|
|
19
|
+
# Show both our options and ruff's help and exit
|
|
20
|
+
handle_passthrough_help(
|
|
21
|
+
ctx,
|
|
22
|
+
["ruff", "format"],
|
|
23
|
+
show_underlying=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
result = subprocess.run(["ruff", "format", *ctx.args], check=False)
|
|
27
|
+
if result.returncode != 0:
|
|
28
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
import afterpython as ap
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def init_ruff_toml():
|
|
10
|
+
from afterpython.tools.pre_commit import update_pre_commit
|
|
11
|
+
|
|
12
|
+
afterpython_path = ap.paths.afterpython_path
|
|
13
|
+
ruff_toml_path = afterpython_path / "ruff.toml"
|
|
14
|
+
if ruff_toml_path.exists():
|
|
15
|
+
click.echo(f"Ruff configuration file {ruff_toml_path} already exists")
|
|
16
|
+
return
|
|
17
|
+
ruff_template_path = ap.paths.templates_path / "ruff-template.toml"
|
|
18
|
+
shutil.copy(ruff_template_path, ruff_toml_path)
|
|
19
|
+
click.echo(f"Created {ruff_toml_path}")
|
|
20
|
+
# add ruff-pre-commit hook to .pre-commit-config.yaml
|
|
21
|
+
data_update = {
|
|
22
|
+
"repos": [
|
|
23
|
+
{
|
|
24
|
+
"repo": "https://github.com/astral-sh/ruff-pre-commit",
|
|
25
|
+
"rev": "v0.14.5",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"id": "ruff-check",
|
|
29
|
+
"stages": ["pre-commit"],
|
|
30
|
+
"args": ["--config", "./afterpython/ruff.toml"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "ruff-format",
|
|
34
|
+
"stages": ["pre-commit"],
|
|
35
|
+
"args": ["--config", "./afterpython/ruff.toml"],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
update_pre_commit(data_update)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init_website():
|
|
45
|
+
click.echo(f"Initializing project website template in {ap.paths.website_path}...")
|
|
46
|
+
subprocess.run(["ap", "update", "website"])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@click.command()
|
|
50
|
+
@click.pass_context
|
|
51
|
+
def init(ctx):
|
|
52
|
+
"""Initialize afterpython with MyST Markdown (by default) and project website template"""
|
|
53
|
+
from afterpython.tools.pyproject import init_pyproject
|
|
54
|
+
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
|
+
from afterpython.tools.commitizen import init_commitizen
|
|
58
|
+
from afterpython.tools.github_actions import (
|
|
59
|
+
init_release_workflow,
|
|
60
|
+
init_deploy_workflow,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
paths = ctx.obj["paths"]
|
|
64
|
+
click.echo("Initializing afterpython...")
|
|
65
|
+
afterpython_path = paths.afterpython_path
|
|
66
|
+
static_path = paths.static_path
|
|
67
|
+
|
|
68
|
+
afterpython_path.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
static_path.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
afterpython_path.joinpath("afterpython.toml").touch()
|
|
71
|
+
|
|
72
|
+
init_pyproject()
|
|
73
|
+
|
|
74
|
+
init_afterpython()
|
|
75
|
+
|
|
76
|
+
init_myst()
|
|
77
|
+
|
|
78
|
+
# TODO: init faq.yml
|
|
79
|
+
|
|
80
|
+
init_website()
|
|
81
|
+
|
|
82
|
+
if click.confirm(
|
|
83
|
+
f"\nCreate .pre-commit-config.yaml in {afterpython_path}?", default=True
|
|
84
|
+
):
|
|
85
|
+
init_pre_commit()
|
|
86
|
+
|
|
87
|
+
if click.confirm(f"\nCreate ruff.toml in {afterpython_path}?", default=True):
|
|
88
|
+
init_ruff_toml()
|
|
89
|
+
|
|
90
|
+
if click.confirm(
|
|
91
|
+
f"\nCreate commitizen configuration (cz.toml) in {afterpython_path} "
|
|
92
|
+
f"and release workflow in .github/workflows/release.yml?",
|
|
93
|
+
default=True,
|
|
94
|
+
):
|
|
95
|
+
init_commitizen()
|
|
96
|
+
init_release_workflow()
|
|
97
|
+
|
|
98
|
+
init_deploy_workflow()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
from afterpython.utils import has_uv
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command(
|
|
10
|
+
context_settings=dict(
|
|
11
|
+
ignore_unknown_options=True,
|
|
12
|
+
allow_extra_args=True,
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
def install():
|
|
16
|
+
"""Run 'uv sync --all-extras --all-groups' to install all dependencies"""
|
|
17
|
+
if not has_uv():
|
|
18
|
+
click.echo("uv not found. Please install uv first.")
|
|
19
|
+
return
|
|
20
|
+
result = subprocess.run(["uv", "sync", "--all-extras", "--all-groups"], check=False)
|
|
21
|
+
if result.returncode != 0:
|
|
22
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _command_supports_config(command: str) -> bool:
|
|
8
|
+
"""Check if a pre-commit command supports --config flag by checking its help text."""
|
|
9
|
+
try:
|
|
10
|
+
result = subprocess.run(
|
|
11
|
+
["pre-commit", command, "--help"],
|
|
12
|
+
capture_output=True,
|
|
13
|
+
text=True,
|
|
14
|
+
check=False,
|
|
15
|
+
)
|
|
16
|
+
return "--config" in result.stdout
|
|
17
|
+
except Exception:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@click.command(
|
|
22
|
+
add_help_option=False, # disable click's --help option so that pre-commit --help can work
|
|
23
|
+
context_settings=dict(
|
|
24
|
+
ignore_unknown_options=True,
|
|
25
|
+
allow_extra_args=True,
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
@click.pass_context
|
|
29
|
+
def pre_commit(ctx):
|
|
30
|
+
"""Run pre-commit, always use the afterpython/.pre-commit-config.yaml if it exists"""
|
|
31
|
+
from afterpython.utils import handle_passthrough_help
|
|
32
|
+
|
|
33
|
+
# Show both our options and pre-commit's help and exit
|
|
34
|
+
handle_passthrough_help(
|
|
35
|
+
ctx,
|
|
36
|
+
["pre-commit"],
|
|
37
|
+
show_underlying=True,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
args = ctx.args[:]
|
|
41
|
+
paths = ctx.obj["paths"]
|
|
42
|
+
|
|
43
|
+
# Find the first command in args (skip any flags that might come before the command)
|
|
44
|
+
command: str | None = None
|
|
45
|
+
command_idx: int | None = None
|
|
46
|
+
for i, arg in enumerate(args):
|
|
47
|
+
if not arg.startswith("-"):
|
|
48
|
+
command = arg
|
|
49
|
+
command_idx = i
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
# If we found a command and --config is not already specified
|
|
53
|
+
if (
|
|
54
|
+
command
|
|
55
|
+
and command_idx is not None
|
|
56
|
+
and "--config" not in args
|
|
57
|
+
and _command_supports_config(command)
|
|
58
|
+
):
|
|
59
|
+
pre_commit_path = paths.afterpython_path / ".pre-commit-config.yaml"
|
|
60
|
+
# Insert --config right after the command
|
|
61
|
+
args.insert(command_idx + 1, "--config")
|
|
62
|
+
args.insert(command_idx + 2, str(pre_commit_path))
|
|
63
|
+
|
|
64
|
+
result = subprocess.run(["pre-commit", *args], check=False)
|
|
65
|
+
if result.returncode != 0:
|
|
66
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click.exceptions import Exit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command()
|
|
8
|
+
@click.pass_context
|
|
9
|
+
def preview(ctx):
|
|
10
|
+
"""Preview the production build of the project website (including myst's builds)"""
|
|
11
|
+
from afterpython.utils import find_node_env
|
|
12
|
+
|
|
13
|
+
paths = ctx.obj["paths"]
|
|
14
|
+
node_env = find_node_env()
|
|
15
|
+
click.echo(
|
|
16
|
+
"Previewing the production build of the project website (including myst's builds)..."
|
|
17
|
+
)
|
|
18
|
+
result = subprocess.run(
|
|
19
|
+
["pnpm", "preview"], cwd=paths.website_path, env=node_env, check=False
|
|
20
|
+
)
|
|
21
|
+
if result.returncode != 0:
|
|
22
|
+
raise Exit(result.returncode)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
@click.option(
|
|
8
|
+
"--force",
|
|
9
|
+
"-f",
|
|
10
|
+
is_flag=True,
|
|
11
|
+
help="Force release even for dev versions (not recommended)",
|
|
12
|
+
)
|
|
13
|
+
def release(force: bool):
|
|
14
|
+
"""Manually trigger a release by pushing the current version tag.
|
|
15
|
+
|
|
16
|
+
This command is used to manually publish dev versions or pre-releases to PyPI
|
|
17
|
+
and create GitHub releases. It pushes the tag for the current version, which
|
|
18
|
+
triggers the GitHub Actions release workflow.
|
|
19
|
+
|
|
20
|
+
By default, releases are only allowed for non-dev versions (stable releases
|
|
21
|
+
and pre-releases like rc, alpha, beta). Use --force to release dev versions.
|
|
22
|
+
|
|
23
|
+
The release workflow will:
|
|
24
|
+
- Run tests in CI
|
|
25
|
+
- Publish to PyPI (if tests pass)
|
|
26
|
+
- Create a GitHub release (if tests pass)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
ap bump --pre # Bump to pre-release (e.g., 0.1.0rc1)
|
|
30
|
+
ap release # Push tag → triggers release workflow
|
|
31
|
+
|
|
32
|
+
ap bump # Bump dev version (e.g., 0.1.0.dev4)
|
|
33
|
+
ap release --force # Push tag → triggers release workflow (dev version)
|
|
34
|
+
"""
|
|
35
|
+
from afterpython.tools.pyproject import read_metadata
|
|
36
|
+
|
|
37
|
+
# Get current version from pyproject.toml
|
|
38
|
+
metadata = read_metadata()
|
|
39
|
+
version = metadata.version
|
|
40
|
+
|
|
41
|
+
if version is None:
|
|
42
|
+
raise click.ClickException("Unable to read version from pyproject.toml")
|
|
43
|
+
|
|
44
|
+
# Check if this is a dev version
|
|
45
|
+
if version.is_devrelease and not force:
|
|
46
|
+
raise click.ClickException(
|
|
47
|
+
f"Cannot release dev version '{version}' without --force flag.\n"
|
|
48
|
+
f"Dev versions are typically not published to PyPI.\n"
|
|
49
|
+
f"Use 'ap bump --release' for stable releases (auto-releases),\n"
|
|
50
|
+
f"or 'ap bump --pre' then 'ap release' for pre-releases,\n"
|
|
51
|
+
f"or 'ap release --force' to release this dev version anyway."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
tag = f"v{version}"
|
|
55
|
+
|
|
56
|
+
click.echo(f"🏷️ Pushing tag {tag} to trigger release workflow...")
|
|
57
|
+
result = subprocess.run(["git", "push", "origin", tag], capture_output=False)
|
|
58
|
+
|
|
59
|
+
if result.returncode != 0:
|
|
60
|
+
click.echo(f"\n❌ Failed to push tag (exit code {result.returncode})", err=True)
|
|
61
|
+
raise click.ClickException("Git push failed")
|
|
62
|
+
|
|
63
|
+
click.echo(f"✅ Tag {tag} pushed successfully")
|
|
64
|
+
click.echo("\n📋 Check GitHub Actions to see the release workflow progress.")
|
|
65
|
+
|
|
66
|
+
if version.is_devrelease:
|
|
67
|
+
click.echo(" ⚠️ Warning: Publishing a dev version to PyPI and GitHub")
|