afterpython 0.1.0.dev7__py3-none-any.whl → 0.1.0.dev8__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.
@@ -77,7 +77,7 @@ def prebuild():
77
77
  _check_initialized()
78
78
  _clean_build_directory()
79
79
 
80
- if os.getenv("AP_MOLAB_BADGE", "0") == "1":
80
+ if os.getenv("AP_MOLAB_BADGE", "1") == "1":
81
81
  for content_type in CONTENT_TYPES:
82
82
  add_molab_badge_to_jupyter_notebooks(content_type)
83
83
 
@@ -47,8 +47,14 @@ 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
59
  from afterpython.tools.pyproject import init_pyproject
54
60
  from afterpython.tools._afterpython import init_afterpython
@@ -56,8 +62,8 @@ def init(ctx):
56
62
  from afterpython.tools.pre_commit import init_pre_commit
57
63
  from afterpython.tools.commitizen import init_commitizen
58
64
  from afterpython.tools.github_actions import (
59
- init_release_workflow,
60
- init_deploy_workflow,
65
+ create_workflow,
66
+ create_dependabot,
61
67
  )
62
68
 
63
69
  paths = ctx.obj["paths"]
@@ -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()
afterpython/cli/main.py CHANGED
@@ -23,6 +23,7 @@ from afterpython.cli.commands.commitizen import commitizen
23
23
  from afterpython.cli.commands.commit import commit
24
24
  from afterpython.cli.commands.bump import bump
25
25
  from afterpython.cli.commands.release import release
26
+ from afterpython.cli.commands.init_branch_rules import init_branch_rules
26
27
 
27
28
 
28
29
  @tui(command="tui", help="Open terminal UI")
@@ -50,6 +51,7 @@ afterpython_group.add_command(build)
50
51
  afterpython_group.add_command(dev)
51
52
  afterpython_group.add_command(update)
52
53
  afterpython_group.add_command(check)
54
+ afterpython_group.add_command(check, name="lint")
53
55
  afterpython_group.add_command(format)
54
56
  afterpython_group.add_command(sync)
55
57
  afterpython_group.add_command(start)
@@ -69,3 +71,4 @@ afterpython_group.add_command(commitizen, name="cz")
69
71
  afterpython_group.add_command(commit)
70
72
  afterpython_group.add_command(bump)
71
73
  afterpython_group.add_command(release)
74
+ afterpython_group.add_command(init_branch_rules)
@@ -0,0 +1,197 @@
1
+ name: CI
2
+
3
+ # Trigger CI on pushes to main and all pull requests
4
+ # This ensures every code change is validated before merging
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ pull_request:
10
+ # Run on all PRs regardless of target branch
11
+ branches:
12
+ - '**'
13
+
14
+ jobs:
15
+ # Job 1: Lint and Format Check
16
+ # Runs ruff to check code quality and formatting
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v6
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v6
25
+ with:
26
+ python-version: '3.11'
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v7
30
+ with:
31
+ version: "latest"
32
+
33
+ - name: Install dependencies
34
+ # Install the package with uv in system mode
35
+ # This installs ruff and other tools defined in dependencies
36
+ run: uv pip install --system .
37
+
38
+ - name: Run ruff check
39
+ run: ruff check --config ./afterpython/ruff.toml .
40
+
41
+ - name: Run ruff format check
42
+ # --check flag ensures it only validates, doesn't reformat
43
+ run: ruff format --check --config ./afterpython/ruff.toml .
44
+
45
+ # Job 2: Detect Test Strategy
46
+ # Determines whether to use pixi or uv based on pixi.toml presence
47
+ detect-test-strategy:
48
+ runs-on: ubuntu-latest
49
+ outputs:
50
+ use_pixi: ${{ steps.check.outputs.use_pixi }}
51
+ steps:
52
+ - name: Checkout code
53
+ uses: actions/checkout@v6
54
+
55
+ - name: Check for pixi.toml
56
+ id: check
57
+ run: |
58
+ if [ -f "pixi.toml" ]; then
59
+ echo "use_pixi=true" >> $GITHUB_OUTPUT
60
+ else
61
+ echo "use_pixi=false" >> $GITHUB_OUTPUT
62
+ fi
63
+
64
+ # Job 3.1: Test Suite - UV Workflow
65
+ # Traditional Python testing with uv for projects without pixi
66
+ test-uv:
67
+ needs: detect-test-strategy
68
+ if: needs.detect-test-strategy.outputs.use_pixi == 'false'
69
+ runs-on: ubuntu-latest
70
+ strategy:
71
+ matrix:
72
+ # TODO: support python 3.14
73
+ python-version: ['3.11', '3.12', '3.13']
74
+ fail-fast: false
75
+
76
+ steps:
77
+ - name: Checkout code
78
+ uses: actions/checkout@v6
79
+
80
+ - name: Set up Python ${{ matrix.python-version }}
81
+ uses: actions/setup-python@v6
82
+ with:
83
+ python-version: ${{ matrix.python-version }}
84
+
85
+ - name: Install uv
86
+ uses: astral-sh/setup-uv@v7
87
+ with:
88
+ version: "latest"
89
+
90
+ - name: Install dependencies
91
+ run: |
92
+ # Install package, main dependencies, and test dependencies
93
+ # If no [dependency-groups] test exists, only the package and main dependencies are installed
94
+ uv pip install --system . --group test || uv pip install --system .
95
+
96
+ - name: Run tests with pytest
97
+ # Run pytest with verbose output
98
+ # Exit code 5 (no tests collected) is treated as success
99
+ run: |
100
+ pytest -v || if [ $? -eq 5 ]; then exit 0; else exit $?; fi
101
+
102
+ # Job 3.2: Test Suite - Pixi Workflow
103
+ # Pixi-based testing for projects using pixi.toml
104
+ test-pixi:
105
+ needs: detect-test-strategy
106
+ if: needs.detect-test-strategy.outputs.use_pixi == 'true'
107
+ runs-on: ubuntu-latest
108
+ strategy:
109
+ matrix:
110
+ # TODO: support python 3.14
111
+ environment: ['py311', 'py312', 'py313']
112
+ fail-fast: false
113
+
114
+ steps:
115
+ - name: Checkout code
116
+ uses: actions/checkout@v6
117
+
118
+ - name: Set up pixi
119
+ uses: prefix-dev/setup-pixi@v0.9.3
120
+ with:
121
+ pixi-version: v0.59.0
122
+ cache: true
123
+ # Optional: Uncomment if you need prefix.dev authentication
124
+ # auth-host: prefix.dev
125
+ # auth-token: ${{ secrets.PREFIX_DEV_TOKEN }}
126
+
127
+ - name: Run tests with pixi
128
+ # Runs test in specific Python environment (py311, py312, py313)
129
+ # Assumes you have environments defined in pixi.toml with a "test" task
130
+ # Example pixi.toml:
131
+ # [environments]
132
+ # py311 = ["py311", "test"]
133
+ # [feature.test.tasks]
134
+ # test = "pytest -v"
135
+ # Note: Exit code 5 (no tests collected) is treated as success
136
+ run: |
137
+ pixi run -e ${{ matrix.environment }} test || if [ $? -eq 5 ]; then exit 0; else exit $?; fi
138
+
139
+ # Job 3.3: Test Suite - Complete
140
+ # Unified test status for branch protection
141
+ test:
142
+ if: always()
143
+ needs: [detect-test-strategy, test-uv, test-pixi]
144
+ runs-on: ubuntu-latest
145
+ steps:
146
+ - name: Check test results
147
+ run: |
148
+ uv_result="${{ needs.test-uv.result }}"
149
+ pixi_result="${{ needs.test-pixi.result }}"
150
+ # One should succeed, the other should be skipped
151
+ if [ "$uv_result" == "success" ] || [ "$uv_result" == "skipped" ]; then
152
+ if [ "$pixi_result" == "success" ] || [ "$pixi_result" == "skipped" ]; then
153
+ echo "All tests passed!"
154
+ exit 0
155
+ fi
156
+ fi
157
+ echo "Tests failed: uv=$uv_result, pixi=$pixi_result"
158
+ exit 1
159
+
160
+ # Job 4: Build Verification
161
+ # Ensures the package can be built successfully
162
+ build:
163
+ runs-on: ubuntu-latest
164
+ steps:
165
+ - name: Checkout code
166
+ uses: actions/checkout@v6
167
+
168
+ - name: Set up Python
169
+ uses: actions/setup-python@v6
170
+ with:
171
+ python-version: '3.11'
172
+
173
+ - name: Install uv
174
+ uses: astral-sh/setup-uv@v7
175
+ with:
176
+ version: "latest"
177
+
178
+ - name: Build package
179
+ run: uv build
180
+
181
+ - name: List build artifacts
182
+ # Show what was built (wheel + sdist)
183
+ # Useful for debugging build issues
184
+ run: ls -lh dist/
185
+
186
+ - name: Install built package
187
+ # Test that the built wheel can be installed
188
+ # This catches packaging issues like missing files
189
+ run: uv pip install --system dist/*.whl
190
+
191
+ - name: Verify installation
192
+ # Confirm the CLI entry point works
193
+ # This ensures the package is usable after installation
194
+ run: ap --help
195
+
196
+ # Job 5: Type Check
197
+ # typecheck:
@@ -0,0 +1,30 @@
1
+ # Dependabot configuration for automatic dependency updates
2
+ # See: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
3
+
4
+ version: 2
5
+ updates:
6
+ # Update GitHub Actions versions
7
+ # This keeps your workflow actions secure and up-to-date
8
+ - package-ecosystem: "github-actions"
9
+ directory: "/"
10
+ schedule:
11
+ # Check for updates monthly (less noisy than weekly)
12
+ interval: "monthly"
13
+ labels:
14
+ - "dependencies"
15
+ - "github-actions"
16
+ # Group all GitHub Actions updates into a single PR
17
+ # This reduces PR noise and makes reviewing easier
18
+ groups:
19
+ github-actions:
20
+ patterns:
21
+ - "*"
22
+ # Automatically add reviewers (optional - uncomment and customize)
23
+ # reviewers:
24
+ # - "your-username"
25
+ # Limit how many Dependabot PRs can be open at once (optional)
26
+ # Default is 5, increase if you want more concurrent PRs
27
+ # open-pull-requests-limit: 10
28
+ #
29
+ # Note: Auto-merge is configured in GitHub repo settings, not here.
30
+ # Go to: Settings → General → Pull Requests → Allow auto-merge
@@ -35,7 +35,7 @@ jobs:
35
35
  runs-on: ubuntu-latest
36
36
  steps:
37
37
  - name: Checkout code
38
- uses: actions/checkout@v4
38
+ uses: actions/checkout@v6
39
39
 
40
40
  - name: Install pnpm
41
41
  uses: pnpm/action-setup@v4
@@ -43,19 +43,19 @@ jobs:
43
43
  version: 10
44
44
 
45
45
  - name: Set up Node.js
46
- uses: actions/setup-node@v4
46
+ uses: actions/setup-node@v6
47
47
  with:
48
48
  node-version: '24'
49
49
  cache: 'pnpm'
50
50
  cache-dependency-path: './afterpython/_website/pnpm-lock.yaml'
51
51
 
52
52
  - name: Set up Python
53
- uses: actions/setup-python@v5
53
+ uses: actions/setup-python@v6
54
54
  with:
55
55
  python-version: '3.11'
56
56
 
57
57
  - name: Install uv
58
- uses: astral-sh/setup-uv@v5
58
+ uses: astral-sh/setup-uv@v7
59
59
  with:
60
60
  version: "latest"
61
61
 
@@ -71,7 +71,7 @@ jobs:
71
71
  ap build
72
72
 
73
73
  - name: Upload artifact
74
- uses: actions/upload-pages-artifact@v3
74
+ uses: actions/upload-pages-artifact@v4
75
75
  with:
76
76
  path: './afterpython/_website/build'
77
77
 
@@ -14,15 +14,15 @@ jobs:
14
14
 
15
15
  steps:
16
16
  - name: Checkout code
17
- uses: actions/checkout@v4
17
+ uses: actions/checkout@v6
18
18
 
19
19
  - name: Set up Python
20
- uses: actions/setup-python@v5
20
+ uses: actions/setup-python@v6
21
21
  with:
22
22
  python-version: '3.11'
23
23
 
24
24
  - name: Install uv
25
- uses: astral-sh/setup-uv@v5
25
+ uses: astral-sh/setup-uv@v7
26
26
  with:
27
27
  version: "latest"
28
28
 
afterpython/tools/_git.py CHANGED
@@ -58,7 +58,7 @@ def get_github_url() -> str | None:
58
58
  return None
59
59
 
60
60
 
61
- def setup_github_auth():
61
+ def is_gh_authenticated():
62
62
  """Guide user through GitHub authentication."""
63
63
  if not has_gh():
64
64
  print("""
@@ -89,7 +89,6 @@ Please authenticate with GitHub:
89
89
  """)
90
90
  return False
91
91
 
92
- print("✓ GitHub authentication verified")
93
92
  return True
94
93
 
95
94
 
@@ -107,5 +106,32 @@ def get_github_username() -> str | None:
107
106
  return result.stdout.strip()
108
107
 
109
108
 
109
+ def get_current_user_id() -> int | None:
110
+ """
111
+ Get the GitHub user ID of the currently authenticated user.
112
+
113
+ Returns:
114
+ User ID or None if error
115
+ """
116
+ if not is_gh_authenticated():
117
+ return None
118
+
119
+ result = subprocess.run(
120
+ ["gh", "api", "user", "--jq", ".id"],
121
+ capture_output=True,
122
+ text=True,
123
+ )
124
+
125
+ if result.returncode != 0:
126
+ print(f"Error fetching user ID: {result.stderr}")
127
+ return None
128
+
129
+ try:
130
+ return int(result.stdout.strip())
131
+ except ValueError:
132
+ print("Error: Failed to parse user ID")
133
+ return None
134
+
135
+
110
136
  # TODO: use gh's token for pygithub to get repo issues
111
137
  # def get_repo_issues(owner: str, repo: str) -> list[dict]:
@@ -0,0 +1,107 @@
1
+ """
2
+ Branch protection rules for AfterPython projects.
3
+ Uses gh CLI to create GitHub rulesets.
4
+ """
5
+
6
+ import json
7
+ import subprocess
8
+ from copy import deepcopy
9
+
10
+ from afterpython.tools._git import is_gh_authenticated
11
+
12
+
13
+ DEFAULT_RULESET = {
14
+ "name": "afterpython-default", # The ruleset identifier in GitHub
15
+ "target": "branch", # This ruleset applies to branches (not tags)
16
+ "enforcement": "active", # Rules are enforced (vs "disabled" or "evaluate")
17
+ "conditions": { # Which branches this ruleset applies to
18
+ "ref_name": {
19
+ "include": ["refs/heads/main"], # Only protect the 'main' branch
20
+ "exclude": [], # No exclusions
21
+ }
22
+ },
23
+ "bypass_actors": [
24
+ {
25
+ "actor_id": 5, # Repository admin role
26
+ "actor_type": "RepositoryRole",
27
+ "bypass_mode": "always",
28
+ }
29
+ ],
30
+ "rules": [ # Array of protection rules
31
+ # Rule 1: No Force Pushes
32
+ {"type": "non_fast_forward"}, # Prevents git push --force
33
+ # Rule 2: No Branch Deletion
34
+ {"type": "deletion"}, # Prevents branch from being deleted
35
+ # Rule 3: CI Status Checks
36
+ {
37
+ "type": "required_status_checks",
38
+ "parameters": {
39
+ "required_status_checks": [
40
+ {"context": "lint"},
41
+ {"context": "test"},
42
+ {"context": "build"},
43
+ ],
44
+ # The PR branch must be up to date with the base branch (main) before merging
45
+ "strict_required_status_checks_policy": True,
46
+ },
47
+ },
48
+ ],
49
+ }
50
+
51
+
52
+ def list_rulesets() -> list[dict] | None:
53
+ """
54
+ List existing rulesets for the current repository.
55
+
56
+ Returns:
57
+ List of rulesets or None if error
58
+ """
59
+ if not is_gh_authenticated():
60
+ return None
61
+
62
+ result = subprocess.run(
63
+ ["gh", "api", "repos/{owner}/{repo}/rulesets"],
64
+ capture_output=True,
65
+ text=True,
66
+ )
67
+
68
+ if result.returncode != 0:
69
+ print(f"Error listing rulesets: {result.stderr}")
70
+ return None
71
+
72
+ try:
73
+ return json.loads(result.stdout)
74
+ except json.JSONDecodeError:
75
+ print("Error: Failed to parse rulesets JSON")
76
+ return None
77
+
78
+
79
+ def create_default_branch_rules(branch: str = "main") -> bool:
80
+ if not is_gh_authenticated():
81
+ return False
82
+
83
+ # Check if ruleset already exists
84
+ existing_rulesets = list_rulesets()
85
+ if existing_rulesets:
86
+ print(f"Ruleset '{DEFAULT_RULESET['name']}' already exists.")
87
+ return False
88
+
89
+ # Build payload
90
+ payload = deepcopy(DEFAULT_RULESET)
91
+ if branch != "main":
92
+ payload["conditions"]["ref_name"]["include"] = [f"refs/heads/{branch}"]
93
+
94
+ # Create ruleset
95
+ result = subprocess.run(
96
+ ["gh", "api", "-X", "POST", "repos/{owner}/{repo}/rulesets", "--input", "-"],
97
+ input=json.dumps(payload),
98
+ capture_output=True,
99
+ text=True,
100
+ )
101
+
102
+ if result.returncode != 0:
103
+ print(f"Error creating ruleset: {result.stderr}")
104
+ return False
105
+
106
+ print(f"✓ Created branch ruleset '{DEFAULT_RULESET['name']}' for {branch=}")
107
+ return True
@@ -1,78 +1,48 @@
1
1
  import shutil
2
+ from pathlib import Path
2
3
 
3
4
  import click
4
5
 
5
6
  import afterpython as ap
6
7
 
7
8
 
8
- def init_release_workflow():
9
- """Initialize GitHub Actions release workflow.
10
-
11
- Creates .github/workflows/release.yml in the user's project root.
12
- This workflow triggers on version tags and:
13
- - Builds the package with uv
14
- - Publishes to PyPI using trusted publishing
15
- - Creates GitHub releases (with pre-release detection)
16
- """
17
- user_path = ap.paths.user_path
18
- workflow_dir = user_path / ".github" / "workflows"
19
- workflow_path = workflow_dir / "release.yml"
20
-
21
- if workflow_path.exists():
22
- click.echo(f"GitHub Actions release workflow {workflow_path} already exists")
9
+ def _copy_github_template(template_name: str, target_path: Path):
10
+ """Helper to copy GitHub-related templates"""
11
+ if target_path.exists():
12
+ click.echo(f"{target_path} already exists")
23
13
  return
24
14
 
25
- # Create .github/workflows directory if it doesn't exist
26
- workflow_dir.mkdir(parents=True, exist_ok=True)
15
+ # Create parent directory if it doesn't exist
16
+ target_path.parent.mkdir(parents=True, exist_ok=True)
27
17
 
28
18
  # Copy template from package
29
- template_path = ap.paths.templates_path / "release-workflow-template.yml"
19
+ template_path = ap.paths.templates_path / template_name
30
20
  if not template_path.exists():
31
21
  raise FileNotFoundError(
32
22
  f"Template file not found: {template_path}\n"
33
23
  "This might indicate a corrupted installation. Please reinstall afterpython."
34
24
  )
35
25
 
36
- shutil.copy(template_path, workflow_path)
37
- click.echo(f"Created {workflow_path}")
38
- click.echo(
39
- "\n📋 Next steps to enable releases:\n"
40
- " 1. Enable PyPI trusted publishing:\n"
41
- " - Go to https://pypi.org/manage/account/publishing/\n"
42
- " - Add your repository and workflow file: release.yml\n"
43
- " 2. Push tags to trigger releases:\n"
44
- " - Use 'ap bump --release' for stable releases (auto-pushes tag)\n"
45
- " - Use 'ap bump --pre' then 'ap release' for pre-releases\n"
46
- )
26
+ shutil.copy(template_path, target_path)
27
+ print(f"Created {target_path}")
47
28
 
48
29
 
49
- def init_deploy_workflow():
50
- """Initialize GitHub Actions deployment workflow for GitHub Pages.
30
+ def create_workflow(workflow_name: str):
31
+ """Create a GitHub Actions workflow from template"""
32
+ if ".yml" in workflow_name:
33
+ workflow_name = workflow_name.replace(".yml", "")
51
34
 
52
- Creates .github/workflows/deploy.yml in the user's project root.
53
- This workflow triggers on pushes to main branch and:
54
- - Builds the project website with 'ap build'
55
- - Deploys to GitHub Pages
56
- - Allows manual deployment via workflow_dispatch
57
- """
58
35
  user_path = ap.paths.user_path
59
- workflow_dir = user_path / ".github" / "workflows"
60
- workflow_path = workflow_dir / "deploy.yml"
36
+ workflow_path = user_path / ".github" / "workflows" / f"{workflow_name}.yml"
37
+ template_name = f"{workflow_name}-workflow-template.yml"
61
38
 
62
- if workflow_path.exists():
63
- click.echo(f"GitHub Actions deploy workflow {workflow_path} already exists")
64
- return
39
+ _copy_github_template(template_name, workflow_path)
65
40
 
66
- # Create .github/workflows directory if it doesn't exist
67
- workflow_dir.mkdir(parents=True, exist_ok=True)
68
41
 
69
- # Copy template from package
70
- template_path = ap.paths.templates_path / "deploy-workflow-template.yml"
71
- if not template_path.exists():
72
- raise FileNotFoundError(
73
- f"Template file not found: {template_path}\n"
74
- "This might indicate a corrupted installation. Please reinstall afterpython."
75
- )
42
+ def create_dependabot():
43
+ """Create Dependabot configuration for GitHub Actions updates"""
44
+ user_path = ap.paths.user_path
45
+ dependabot_path = user_path / ".github" / "dependabot.yml"
46
+ template_name = "dependabot-template.yml"
76
47
 
77
- shutil.copy(template_path, workflow_path)
78
- click.echo(f"Created {workflow_path}")
48
+ _copy_github_template(template_name, dependabot_path)
afterpython/utils.py CHANGED
@@ -35,6 +35,11 @@ def has_uv() -> bool:
35
35
  return shutil.which("uv") is not None
36
36
 
37
37
 
38
+ def has_pixi() -> bool:
39
+ """Check if pixi is installed"""
40
+ return shutil.which("pixi") is not None
41
+
42
+
38
43
  def has_gh() -> bool:
39
44
  """Check if gh is installed"""
40
45
  return shutil.which("gh") is not None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev8
4
4
  Summary: CLI tool for AfterPython
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
@@ -13,7 +13,6 @@ Classifier: Topic :: Software Development :: Documentation
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
16
  Requires-Dist: click>=8.3.1
18
17
  Requires-Dist: trogon>=0.6.0
19
18
  Requires-Dist: httpx>=0.28.1
@@ -91,16 +90,20 @@ First-time maintainers must learn multiple tools and concepts, e.g. [MyST], [Sem
91
90
  <img alt="Project Website" src="afterpython/static/website-light.png">
92
91
  </picture>
93
92
 
93
+ 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).
94
+
94
95
  ---
95
96
  ## Installation
96
97
  ```bash
97
98
  # install afterpython as a dev dependency
98
99
  uv add --dev afterpython
100
+
99
101
  # initialize afterpython
100
102
  ap init
101
103
  ```
102
104
 
103
105
 
106
+ ---
104
107
  ## Tech Stack
105
108
  - [MyST]
106
109
  - [project-website-template]
@@ -114,3 +117,8 @@ ap init
114
117
  - [WebLLM]
115
118
  <!-- - ty -->
116
119
  <!-- - [pixi] -->
120
+
121
+
122
+ ---
123
+ ## *Our Mission*
124
+ > 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,4 +1,3 @@
1
- afterpython/.pre-commit-config.yaml,sha256=7mpSRHaFcG2M6SOY-5Sbs9WxN8UISnZ2g0NwCGsLNCA,915
2
1
  afterpython/__init__.py,sha256=aNochs72gU2NR1cBzIkk3NxSsdJ7ceHluI5gdYwaFpI,289
3
2
  afterpython/_io/toml.py,sha256=nrx0CFPONgmkz5WDCSMyr4caic6nzu0uCd33qOmhhF0,3408
4
3
  afterpython/_io/yaml.py,sha256=raksve_gaIFJim8lQrq-kQHkRjTloed7CT2FY0oUJSg,654
@@ -8,7 +7,7 @@ afterpython/builders/__init__.py,sha256=MOv3spIh-ABwyUtvQAHhPZ2horWb0D93WNmOPNAz
8
7
  afterpython/builders/metadata.py,sha256=fj7rlawZCh43aJrBBfcmOTlYew1aW-om3QTpVulKbOk,1273
9
8
  afterpython/builders/molab.py,sha256=Qji8piWJB1tbzhgtld0pQWIsUA7d7RsGCfnoLVKd5EA,3714
10
9
  afterpython/cli/__init__.py,sha256=1vLZylRMnGaV7cJ-kHO96dChp33_P4fBIJ_Vi-yyZg4,85
11
- afterpython/cli/commands/build.py,sha256=GkO06D8ktaH3OZWcZhuh463n4vFE1A_OE-iwRuagQdM,8051
10
+ afterpython/cli/commands/build.py,sha256=14C30vDU1SaR_5RVnA62lAAzX_eSRnlEfZI5h5AIrsk,8051
12
11
  afterpython/cli/commands/bump.py,sha256=Wg-q3UQIkIRGT3umZCUBPpwIQTAHwQZVs1yHSZhmzKE,3088
13
12
  afterpython/cli/commands/check.py,sha256=cLP4Vle3ENQHCXBv1okDlWK-L1XK-fv80m66xW3LQkg,1097
14
13
  afterpython/cli/commands/clean.py,sha256=dCEkkWOTn93758wGTrWJG9V6c-lv2Z59CYfoP3i3rJE,2531
@@ -16,7 +15,8 @@ afterpython/cli/commands/commit.py,sha256=yRLOBs6JetZSGkFy9_uLzgR8yAX9djeUF291ga
16
15
  afterpython/cli/commands/commitizen.py,sha256=naI8j4pfXIMsiOLmPMeZTGh831I3665_1_Sbn7yoKyE,811
17
16
  afterpython/cli/commands/dev.py,sha256=yI1g0v7HlVaVH6HwcHyukLYMyUbZjAd0BiCYk9QGdGw,6752
18
17
  afterpython/cli/commands/format.py,sha256=cXvuc-6XwULM1VJjS2B2bJBvnV1TVHFl1CeSs0SYQ24,738
19
- afterpython/cli/commands/init.py,sha256=Sc0B-Gv1bpYHzD8hYoVTXY2QsOV36kWcKH5vgHBmp1g,2987
18
+ afterpython/cli/commands/init.py,sha256=EDX6ojHJyQzaYTef2BjaWoy4_yL-bf6DAnCKa-ylfTU,3354
19
+ afterpython/cli/commands/init_branch_rules.py,sha256=fzAm8D5qbW3u4bEMpqPuRgO-0akuCGG7pu09JlKhYn4,243
20
20
  afterpython/cli/commands/install.py,sha256=wgH9DEuN7hsA6BslNJASqOBn4VHJrYHBvowXDHi4smE,573
21
21
  afterpython/cli/commands/pre_commit.py,sha256=DdmbkcZgJ7QArAY1y5vODYZmHUXWEIuXRyp69AS-pn0,1981
22
22
  afterpython/cli/commands/preview.py,sha256=EbuB-dFcgonmqumTb2aHlLpXRiXkGE4DI3ddJdHwcaM,619
@@ -24,24 +24,26 @@ afterpython/cli/commands/release.py,sha256=6SDEnPtuCL0_XUqWUlvRSKxAv011QqOD0kc5p
24
24
  afterpython/cli/commands/start.py,sha256=PnO1ECXnYtJxTOoDgG2ZoiVnkJVYNhPT63rabronjZ0,3992
25
25
  afterpython/cli/commands/sync.py,sha256=epgD8VvrkYB1GSKMu4UFQb3-uGjyiLzw8CjtjaGB6zs,4669
26
26
  afterpython/cli/commands/update.py,sha256=f6dzoJIiEod7j44dgnaFe-v7INVdUbopVoWPxv7-Mpo,5491
27
- afterpython/cli/main.py,sha256=GMcP7bmOA1glKGPsCuOXcd-9hv2k3BGkk_ofdo8oatw,2660
27
+ afterpython/cli/main.py,sha256=_M5Z3msEZEZ4sIWeM8hvEmnrt9Qd2PHbC_GjibdKVfk,2832
28
28
  afterpython/const.py,sha256=KsALiC1kERK9BQQdYRXSHdYosxVExxgDN7iGYMAJrZE,158
29
- afterpython/cz.toml,sha256=JlenGROat8jvxvqRH6KluDs6f7w0JAy8Skd3ak7sGLY,1506
30
29
  afterpython/main.py,sha256=w0z6mTib3u4p2HxMrk-tIJlZtur7uRaqsbE0ewlYlSs,490
31
30
  afterpython/pcu.py,sha256=RWgPcdAFyNBVhWbQPuL1seji9uyT8Pqz-zE6jCf_Qb8,6091
32
- afterpython/templates/deploy-workflow-template.yml,sha256=O3uGNg-m-nK5ZUhIPpRMqLje1uAFm4OWk7JoTbEq8H4,2365
33
- afterpython/templates/release-workflow-template.yml,sha256=DpuFLo3_43rqwBjDf3XrWbYJNRdlhy7-wJFL_TxdRNE,1606
31
+ afterpython/templates/ci-workflow-template.yml,sha256=2L-SRPB2GKlfR4bQAgpdq4r9bFdT9vCYo4oWjnIzKfg,5933
32
+ afterpython/templates/dependabot-template.yml,sha256=CBQEgh8EsXw2X7IqzGtFI7lVT9liF6PW4DvGLB0X2LY,1136
33
+ afterpython/templates/deploy-workflow-template.yml,sha256=2W4K3lKMYjSTPrlXIsMweBzqP9IoLtZXAAcUu5FA5NA,2365
34
+ afterpython/templates/release-workflow-template.yml,sha256=3YBkgrqmi--uznslpMxPz7Vj7L4_NPlhDxgwYBt-Ui8,1606
34
35
  afterpython/templates/ruff-template.toml,sha256=_yTtZ0YPsJiCxy66ZDfYaStTJ9aXXW-JXy-KW6l2bBE,1010
35
36
  afterpython/tools/_afterpython.py,sha256=W88GhtQADB09DV4A24GOYBeYrHsnVVzllLdWFiKgS1A,1904
36
- afterpython/tools/_git.py,sha256=pOcbP_hb6db3qRlbB4Hlp7MkShkTmUefnIBU2kqoq4I,3211
37
+ afterpython/tools/_git.py,sha256=aO-qDEQ4OevD_B74DYKK4nQKYeiaS1BXmYc8paJRfbQ,3772
38
+ afterpython/tools/branch_rules.py,sha256=Tn6PdiZjj3duQd2u2wa89Cgbkun1lT4tGbbK9Eb71Ck,3160
37
39
  afterpython/tools/commitizen.py,sha256=9aGXsO4tTyDs3s8qS_fiskdSabHqc9XjHwePThybJU0,1469
38
- afterpython/tools/github_actions.py,sha256=M3uGLJKOCml1fQBc8W2lkZWsR5p-0UVUolFWvqf5mDo,2866
40
+ afterpython/tools/github_actions.py,sha256=zVmg9aqUg-EO0CnbRHl9i8TmFtXpt7nd0VjzckqqE30,1527
39
41
  afterpython/tools/myst.py,sha256=w24zqWRUOLaZIC0twpGM4BF__wldmrgIvbns0FFdunA,6601
40
42
  afterpython/tools/pre_commit.py,sha256=stXUQfR0A6nPMCSuuKO-Icjc5F2z9VsZu2mKkIW1vWI,2193
41
43
  afterpython/tools/pyproject.py,sha256=YwJJAB3lIqYK9wt8UtL4dfCli9UN7uzi3e-vtycB0HE,2325
42
- afterpython/utils.py,sha256=WzP34cA0_Mh3F_focTtk-4sSqrIZUpv7PpkPJHpUUzc,6934
44
+ afterpython/utils.py,sha256=LcQCXUw3OlgSeSliRm_ivq4kt7EX-_Nh7YunrYD7TqA,7041
43
45
  afterpython/version.py,sha256=hihRV1S-gc1m46OaXD32ikkEkqzs-Gtq7AcAqOQV-wY,1603
44
- afterpython-0.1.0.dev7.dist-info/WHEEL,sha256=YUH1mBqsx8Dh2cQG2rlcuRYUhJddG9iClegy4IgnHik,79
45
- afterpython-0.1.0.dev7.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
46
- afterpython-0.1.0.dev7.dist-info/METADATA,sha256=eIAURxz9BQvYkgnlFQm9wXmiApHcOIx5JK555WhO1LY,5296
47
- afterpython-0.1.0.dev7.dist-info/RECORD,,
46
+ afterpython-0.1.0.dev8.dist-info/WHEEL,sha256=AaqHSNJgTyoT6I9ETCXrbV_7cVSjA_q07lkDGeNjGdQ,79
47
+ afterpython-0.1.0.dev8.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
48
+ afterpython-0.1.0.dev8.dist-info/METADATA,sha256=qSdlGSV3R2ip84sSGFOR7tNGiOz4y2KPSmnHef0sZ1w,5604
49
+ afterpython-0.1.0.dev8.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.11
2
+ Generator: uv 0.9.12
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,48 +0,0 @@
1
- default_install_hook_types:
2
- - pre-commit
3
- - commit-msg
4
- - pre-push
5
- exclude: ^afterpython/_website/
6
- repos:
7
- - repo: https://github.com/pre-commit/pre-commit-hooks
8
- rev: v6.0.0
9
- hooks:
10
- - id: trailing-whitespace
11
- stages:
12
- - pre-commit
13
- - id: end-of-file-fixer
14
- stages:
15
- - pre-commit
16
- - id: check-yaml
17
- stages:
18
- - pre-commit
19
- - id: check-toml
20
- stages:
21
- - pre-commit
22
- - id: check-added-large-files
23
- stages:
24
- - pre-commit
25
- - repo: https://github.com/astral-sh/ruff-pre-commit
26
- rev: v0.14.5
27
- hooks:
28
- - id: ruff-check
29
- stages:
30
- - pre-commit
31
- args:
32
- - --config
33
- - ./afterpython/ruff.toml
34
- - id: ruff-format
35
- stages:
36
- - pre-commit
37
- args:
38
- - --config
39
- - ./afterpython/ruff.toml
40
- - repo: https://github.com/commitizen-tools/commitizen
41
- rev: v4.10.0
42
- hooks:
43
- - id: commitizen
44
- stages:
45
- - commit-msg
46
- - id: commitizen-branch
47
- stages:
48
- - pre-push
afterpython/cz.toml DELETED
@@ -1,27 +0,0 @@
1
- [tool.commitizen]
2
- name = "cz_conventional_commits"
3
- # name = "cz_customize"
4
- tag_format = "v$version"
5
- version_scheme = "pep440"
6
- version_provider = "uv"
7
- update_changelog_on_bump = false
8
- major_version_zero = true
9
-
10
- # FIXME: commitizen can't even add a few custom types like "chore" and "wip", skip it for now
11
- # [[tool.commitizen.customize.questions]]
12
- # type = "list"
13
- # name = "change_type"
14
- # choices = [
15
- # {value = "fix", name = "fix: A bug fix. Correlates with PATCH in SemVer"},
16
- # {value = "feat", name = "feat: A new feature. Correlates with MINOR in SemVer"},
17
- # {value = "docs", name = "docs: Documentation only changes"},
18
- # {value = "style", name = "style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)"},
19
- # {value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature"},
20
- # {value = "perf", name = "perf: A code change that improves performance"},
21
- # {value = "test", name = "test: Adding missing or correcting existing tests"},
22
- # {value = "build", name = "build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)"},
23
- # {value = "ci", name = "ci: Changes to CI configuration files and scripts (example scopes: GitLabCI)"},
24
- # {value = "chore", name = "chore: Other changes that don't modify src or test files"},
25
- # {value = "wip", name = "wip: Work in progress"}
26
- # ]
27
- # message = "Select the type of change you are committing"