Skip to content

Commit 43b7a39

Browse files
authored
refactor: modularize CLI architecture and add hooks support (#1)
This commit represents a major architectural refactoring of uvtask, transforming the monolithic CLI implementation into a modular, testable architecture while adding new features and infrastructure improvements.
1 parent 2b9ed86 commit 43b7a39

28 files changed

+2677
-490
lines changed

.github/workflows/cd.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: CD
2+
3+
run-name: CD - ${{ github.event_name == 'workflow_run' && format('{0} (from CI)', github.event.workflow_run.head_commit.message) || format('{0}{1}', github.event.inputs.environment != '' && github.event.inputs.environment || 'dev', github.event.inputs.version != '' && format(' (v{0})', github.event.inputs.version) || '') }}
4+
5+
on:
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: "Release version"
10+
default: "None"
11+
required: false
12+
workflow_run:
13+
workflows: ["CI"]
14+
branches: [main]
15+
types:
16+
- completed
17+
18+
env:
19+
CD: ${{ vars.CONTINUOUS_DEPLOYMENT }}
20+
DEPLOY_MODE: "true"
21+
VERSION: ${{ github.event.inputs.version != '' && github.event.inputs.version || 'None' }}
22+
23+
jobs:
24+
# Continuous Deployment (CD) pipeline
25+
cd:
26+
if: ${{ vars.CONTINUOUS_DEPLOYMENT == 'true' && github.ref_name == 'main' && (github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')) }}
27+
permissions:
28+
contents: write
29+
id-token: write
30+
timeout-minutes: 15
31+
runs-on: ${{ matrix.os }}
32+
strategy:
33+
matrix:
34+
os: [ubuntu-latest]
35+
platforms: [linux/amd64]
36+
steps:
37+
- name: Checkout Git repository
38+
uses: actions/checkout@v6
39+
with:
40+
fetch-depth: 0
41+
42+
- name: Install uv and set the Python version
43+
uses: astral-sh/setup-uv@v7
44+
45+
- name: Install dependencies
46+
shell: bash
47+
run: uvx uvtask dev-install
48+
49+
- name: Set version
50+
shell: bash
51+
run: |
52+
if [ -z "${VERSION}" ] || [ "${VERSION}" = "None" ]; then
53+
uv version --bump minor
54+
VERSION=$(uv version --short)
55+
echo "VERSION=${VERSION}" >> "${GITHUB_ENV:-/dev/null}"
56+
else
57+
uv version ${VERSION}
58+
fi
59+
git config user.name "${{ github.actor }}"
60+
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
61+
git tag -a "${VERSION}" -m "Release ${VERSION}"
62+
git push origin "${VERSION}"
63+
if: ${{ env.PIPELINE_TAG == 'true' && env.RELEASE_MODE == 'true' }}
64+
65+
- name: Build package
66+
shell: bash
67+
run: uv build
68+
if: ${{ env.PIPELINE_TAG == 'true' && env.RELEASE_MODE == 'true' }}
69+
70+
- name: Upload package to artifact registry
71+
uses: actions/upload-artifact@v6
72+
with:
73+
name: uvtask
74+
path: dist/
75+
if: ${{ env.PIPELINE_TAG == 'true' && env.RELEASE_MODE == 'true' }}
76+
77+
- name: Publish package
78+
shell: bash
79+
run: uv publish
80+
if: ${{ env.PIPELINE_TAG == 'true' && env.RELEASE_MODE == 'true' }}
81+
82+
- name: Clean
83+
shell: bash
84+
run: uvx uvtask clean

.github/workflows/ci.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: CI
2+
3+
run-name: CI - ${{ github.event_name == 'pull_request' && github.event.pull_request.title || github.event_name == 'push' && github.event.head_commit.message || github.ref_name }} ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' && format('(v{0})', github.event.inputs.version) || '' }}
4+
5+
on:
6+
push:
7+
branches: [main]
8+
pull_request:
9+
workflow_dispatch:
10+
inputs:
11+
version:
12+
description: "Release version"
13+
default: "None"
14+
required: false
15+
16+
env:
17+
CI: "true"
18+
19+
jobs:
20+
# Continuous Integration (CI) pipeline
21+
ci:
22+
if: ${{ vars.CONTINUOUS_INTEGRATION == 'true' }}
23+
permissions:
24+
contents: read
25+
env:
26+
PIPELINE_TESTS: ${{ github.event_name != 'workflow_dispatch' && github.event.inputs.version == '' && startsWith(github.ref, 'refs/tags/') == false && github.ref != 'refs/heads/main' && 'true' || 'false' }}
27+
RELEASE_MODE: "false"
28+
VERSION: ${{ github.run_id }}
29+
timeout-minutes: 15
30+
runs-on: ${{ matrix.os }}
31+
strategy:
32+
matrix:
33+
os: [ubuntu-latest]
34+
platforms: [linux/amd64, linux/arm64]
35+
steps:
36+
- name: Checkout Git repository
37+
uses: actions/checkout@v6
38+
with:
39+
fetch-depth: 0
40+
41+
- name: Install uv and set the Python version
42+
uses: astral-sh/setup-uv@v7
43+
44+
- name: Install dependencies
45+
shell: bash
46+
run: uvx uvtask dev-install
47+
48+
- name: security-analysis-licenses
49+
shell: bash
50+
run: uvx uvtask security-analysis:licenses
51+
if: ${{ env.PIPELINE_TESTS == 'true' }}
52+
53+
- name: security-analysis-vulnerabilities
54+
shell: bash
55+
run: uvx uvtask security-analysis:vulnerabilities
56+
if: ${{ env.PIPELINE_TESTS == 'true' }}
57+
58+
- name: static-analysis-linter
59+
shell: bash
60+
run: uvx uvtask static-analysis:linter
61+
if: ${{ env.PIPELINE_TESTS == 'true' }}
62+
63+
- name: static-analysis-types
64+
shell: bash
65+
run: uvx uvtask static-analysis:types
66+
if: ${{ env.PIPELINE_TESTS == 'true' }}
67+
68+
- name: unit-tests
69+
shell: bash
70+
run: uvx uvtask unit-tests
71+
if: ${{ env.PIPELINE_TESTS == 'true' }}
72+
73+
- name: integration-tests
74+
shell: bash
75+
run: uvx uvtask integration-tests
76+
if: ${{ env.PIPELINE_TESTS == 'true' }}
77+
78+
- name: Clean
79+
shell: bash
80+
run: uvx uvtask clean

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*.egg-info/
99
*.pyc
1010
__pycache__/
11+
.coverage
1112
build/
1213
dist/
1314
requirements-dev.txt

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.14

README.md

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,67 @@
1-
# 🚀 uvtask
1+
# uvtask
22

3-
[![PyPI version](https://badge.fury.io/py/uvtask.svg)](https://badge.fury.io/py/uvtask)
3+
[![image](https://img.shields.io/pypi/v/uvtask.svg)](https://pypi.python.org/pypi/uvtask)
4+
[![image](https://img.shields.io/pypi/l/uvtask.svg)](https://pypi.python.org/pypi/uvtask)
5+
[![image](https://img.shields.io/pypi/pyversions/uvtask.svg)](https://pypi.python.org/pypi/uvtask)
6+
[![Actions status](https://github.com/aiopy/python-uvtask/actions/workflows/ci.yml/badge.svg)](https://github.com/aiopy/python-uvtask/actions)
47
[![PyPIDownloads](https://static.pepy.tech/badge/uvtask)](https://pepy.tech/project/uvtask)
58

6-
**uvtask** is a modern, fast, and flexible Python task runner and test automation tool designed to simplify development workflows. It supports running, organizing, and managing tasks or tests in Python projects with an emphasis on ease of use and speed. ⚡
9+
An extremely fast Python task runner.
10+
11+
## Highlights
12+
13+
-**Extremely fast** - Built for speed with zero installation overhead
14+
- 📝 **Simple configuration** - Define scripts in `pyproject.toml`
15+
- 🔗 **Pre/post hooks** - Automatically run hooks before and after commands
16+
- 🎨 **Beautiful output** - Colorful, `uv`-inspired CLI
717

818
## 🎯 Quick Start
919

10-
Run tasks defined in your `pyproject.toml`:
20+
Run `uvtask` directly with `uvx` (no installation required):
1121

1222
```shell
13-
uvx uvtask <task_name>
23+
uvx uvtask <OPTIONS> [COMMAND]
24+
```
25+
26+
Or install it and use it directly:
27+
28+
```shell
29+
uv add --dev uvtask
30+
uvtask <OPTIONS> [COMMAND]
1431
```
1532

1633
## 📝 Configuration
1734

18-
Define your tasks in `pyproject.toml` under the `[tool.run-script]` section:
35+
Define your scripts in `pyproject.toml` under the `[tool.run-script]` section:
1936

2037
```toml
2138
[tool.run-script]
22-
hello-world = "echo 'hello world'"
39+
install = "uv sync --dev --all-extras"
40+
format = "ruff format ."
41+
lint = { command = "ruff check .", description = "Check code quality" }
42+
check = ["ty check .", "mypy ."]
43+
pre-test = "echo 'Running tests...'"
44+
test = "pytest"
45+
post-test = "echo 'Tests completed!'"
46+
deploy = [
47+
"echo 'Building...'",
48+
"uv build",
49+
"echo 'Deploying...'",
50+
"uv deploy"
51+
]
2352
```
2453

2554
## 🛠️ Development
2655

2756
To run the development version:
2857

2958
```shell
30-
uvx --no-cache --from $PWD run --help
59+
uvx -q --no-cache --from $PWD uvtask
3160
```
3261

33-
## 📋 Requirements
34-
35-
- 🐍 Python >= 3.13
36-
3762
## 🤝 Contributing
3863

39-
Contributions are welcome! 🎉
40-
41-
- For major changes, please open an issue first to discuss what you would like to change
42-
- Make sure to update tests as appropriate
43-
- Follow the existing code style and conventions
64+
Contributions are welcome! Please feel free to submit a Pull Request.
4465

4566
## 📄 License
4667

pyproject.toml

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[build-system]
2-
build-backend = "setuptools.build_meta"
3-
requires = ["setuptools"]
2+
requires = ["uv_build"]
3+
build-backend = "uv_build"
4+
5+
[tool.uv.build-backend]
6+
module-name = "uvtask"
7+
module-root = ""
48

59
[project]
610
authors = [
@@ -27,8 +31,8 @@ classifiers = [
2731
"Programming Language :: Python :: 3.14",
2832
]
2933
dependencies = []
30-
description = "uvtask is a modern, fast, and flexible Python task runner and test automation tool designed to simplify development workflows. It supports running, organizing, and managing tasks or tests in Python projects with an emphasis on ease of use and speed."
31-
dynamic = ["version"]
34+
description = "An extremely fast Python task runner."
35+
version = "0.0.0"
3236
keywords = [
3337
"uv",
3438
"uvx",
@@ -51,25 +55,13 @@ dev = [
5155
"pytest-cov>=7.0.0", # test, coverage
5256
"pytest-xdist>=3.8.0", # test
5357
"ruff>=0.14.10", # code-formatter, static-analysis
54-
"ty>=0.0.4", # static-analysis
58+
"ty>=0.0.6", # static-analysis
5559
]
5660

5761
[project.urls]
5862
"documentation" = "https://aiopy.github.io/python-uvtask/"
5963
"repository" = "https://github.com/aiopy/python-uvtask"
6064

61-
[tool.setuptools.dynamic]
62-
version = { attr = "uvtask.__version__" }
63-
64-
[tool.setuptools.packages.find]
65-
include = ["uvtask*"]
66-
67-
[tool.setuptools.package-data]
68-
"uvtask" = ["py.typed"]
69-
70-
[[tool.uv.index]]
71-
url = "https://pypi.org/simple"
72-
7365
[tool.bandit]
7466
exclude_dirs = ["tests"]
7567
skips = ["B404", "B602"]
@@ -103,6 +95,8 @@ lint.select = [
10395
]
10496
lint.ignore = [
10597
"PLC0415",
98+
"PLR0912",
99+
"PLR0913",
106100
"PLR2004",
107101
]
108102

@@ -118,7 +112,7 @@ force-single-line = false
118112
known-first-party = ["uvtask"]
119113

120114
[tool.ruff.lint.mccabe]
121-
max-complexity = 15
115+
max-complexity = 20
122116

123117
[tool.ty.environment]
124118
python-version = "3.13"
@@ -127,35 +121,34 @@ python-version = "3.13"
127121
exclude = [
128122
"tests/fixtures/**",
129123
"var",
124+
".venv",
130125
]
131126

132127
[tool.run-script]
133128
install = "uv sync --frozen --no-dev"
134129
upgrade-install = "uv sync --frozen --no-dev --upgrade --refresh"
135130
dev-install = "uv sync --dev --all-extras"
136131
upgrade-dev-install = "uv sync --dev --all-extras --upgrade --refresh"
137-
deploy = "uv build && uv publish"
138-
docs = "python3 -m mkdocs build -f docs_src/config/en/mkdocs.yml && python3 -m mkdocs build -f docs_src/config/es/mkdocs.yml"
139-
dev-docs = "python3 -m mkdocs serve -f docs_src/config/en/mkdocs.yml"
140-
code-formatter = "uv run ruff format uvtask tests $@"
132+
code-formatter = "uv run ruff format uvtask tests"
141133
"security-analysis:licenses" = "uv run pip-licenses"
142134
"security-analysis:vulnerabilities" = "uv run bandit -r -c pyproject.toml uvtask tests"
143135
"static-analysis:linter" = "uv run ruff check uvtask tests"
144136
"static-analysis:types" = "uv run ty check uvtask tests"
145-
test = "uv run pytest"
146137
unit-tests = "uv run pytest tests/unit"
147138
integration-tests = "uv run pytest tests/integration"
148139
functional-tests = "uv run pytest -n1 tests/functional"
149140
coverage = "uv run pytest -n1 --cov --cov-report=html"
150-
clean = """python3 -c \"
141+
clean = """python3 -c "
151142
from glob import iglob
152143
from shutil import rmtree
153144
154145
for pathname in ['./build', './*.egg-info', './dist', './var', '**/__pycache__']:
155146
for path in iglob(pathname, recursive=True):
156147
rmtree(path, ignore_errors=True)
157-
\""""
148+
"
149+
"""
158150

159151
[project.scripts]
160152
uvtask = "uvtask.cli:main"
153+
run = "uvtask.cli:main"
161154
run-script = "uvtask.cli:main"

tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"""Test package for uvtask."""
1+

0 commit comments

Comments
 (0)