openvisionkit 0.4.0__tar.gz
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.
- openvisionkit-0.4.0/.flake8 +5 -0
- openvisionkit-0.4.0/.gitattributes +3 -0
- openvisionkit-0.4.0/.github/workflows/ci-integration.yml +86 -0
- openvisionkit-0.4.0/.github/workflows/ci-security.yml +125 -0
- openvisionkit-0.4.0/.github/workflows/ci-unit.yml +57 -0
- openvisionkit-0.4.0/.github/workflows/publish.yml +135 -0
- openvisionkit-0.4.0/.github/workflows/renovate.yml +37 -0
- openvisionkit-0.4.0/.github/workflows/reusable-github-release.yml +193 -0
- openvisionkit-0.4.0/.github/workflows/semantic-versioning.yml +218 -0
- openvisionkit-0.4.0/.gitignore +13 -0
- openvisionkit-0.4.0/.pre-commit-config.yaml +101 -0
- openvisionkit-0.4.0/.python-version +1 -0
- openvisionkit-0.4.0/CLAUDE.md +528 -0
- openvisionkit-0.4.0/Makefile +109 -0
- openvisionkit-0.4.0/PKG-INFO +1018 -0
- openvisionkit-0.4.0/README.md +993 -0
- openvisionkit-0.4.0/docs/superpowers/plans/2026-06-09-visionkit-utility-methods.md +2633 -0
- openvisionkit-0.4.0/docs/superpowers/specs/2026-06-09-visionkit-utility-methods-design.md +229 -0
- openvisionkit-0.4.0/main.py +6 -0
- openvisionkit-0.4.0/openvisionkit/__init__.py +1 -0
- openvisionkit-0.4.0/openvisionkit/_version.py +24 -0
- openvisionkit-0.4.0/openvisionkit/capture/draw_object.py +296 -0
- openvisionkit-0.4.0/openvisionkit/capture/image_template.py +61 -0
- openvisionkit-0.4.0/openvisionkit/capture/screen_capture.py +13 -0
- openvisionkit-0.4.0/openvisionkit/capture/video_recorder.py +128 -0
- openvisionkit-0.4.0/openvisionkit/capture/video_template.py +336 -0
- openvisionkit-0.4.0/openvisionkit/lib/classifier.py +186 -0
- openvisionkit-0.4.0/openvisionkit/lib/face_detector.py +587 -0
- openvisionkit-0.4.0/openvisionkit/lib/face_mesh_detector.py +913 -0
- openvisionkit-0.4.0/openvisionkit/lib/form_detector.py +465 -0
- openvisionkit-0.4.0/openvisionkit/lib/form_roi_annotator.py +679 -0
- openvisionkit-0.4.0/openvisionkit/lib/form_roi_detector.py +1078 -0
- openvisionkit-0.4.0/openvisionkit/lib/fps_counter.py +38 -0
- openvisionkit-0.4.0/openvisionkit/lib/hair_segmentation.py +298 -0
- openvisionkit-0.4.0/openvisionkit/lib/hand_detector.py +1230 -0
- openvisionkit-0.4.0/openvisionkit/lib/image_detector.py +1095 -0
- openvisionkit-0.4.0/openvisionkit/lib/object_detector.py +401 -0
- openvisionkit-0.4.0/openvisionkit/lib/pose_detector.py +919 -0
- openvisionkit-0.4.0/openvisionkit/lib/selfie_segmentation.py +528 -0
- openvisionkit-0.4.0/openvisionkit/lib/text_detector.py +1229 -0
- openvisionkit-0.4.0/openvisionkit/utility/live_plot.py +141 -0
- openvisionkit-0.4.0/openvisionkit/utility/vision_utilis.py +871 -0
- openvisionkit-0.4.0/pyproject.toml +103 -0
- openvisionkit-0.4.0/renovate.json +79 -0
- openvisionkit-0.4.0/tests/__init__.py +0 -0
- openvisionkit-0.4.0/tests/capture/__init__.py +0 -0
- openvisionkit-0.4.0/tests/capture/test_video_capture_template.py +560 -0
- openvisionkit-0.4.0/tests/conftest.py +96 -0
- openvisionkit-0.4.0/tests/lib/__init__.py +0 -0
- openvisionkit-0.4.0/tests/lib/test_face_detector.py +116 -0
- openvisionkit-0.4.0/tests/lib/test_face_mesh_detector.py +120 -0
- openvisionkit-0.4.0/tests/lib/test_form_roi_annotator.py +70 -0
- openvisionkit-0.4.0/tests/lib/test_form_roi_detector.py +98 -0
- openvisionkit-0.4.0/tests/lib/test_hair_segmentation.py +77 -0
- openvisionkit-0.4.0/tests/lib/test_hand_detector.py +138 -0
- openvisionkit-0.4.0/tests/lib/test_image_detector.py +82 -0
- openvisionkit-0.4.0/tests/lib/test_object_detector.py +85 -0
- openvisionkit-0.4.0/tests/lib/test_pose_detector.py +134 -0
- openvisionkit-0.4.0/tests/lib/test_selfie_segmentation.py +91 -0
- openvisionkit-0.4.0/tests/lib/test_text_detector.py +94 -0
- openvisionkit-0.4.0/uv.lock +2790 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: Integration Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
skip_model_download:
|
|
11
|
+
description: "Skip model download (run inference-free integration tests only)"
|
|
12
|
+
required: false
|
|
13
|
+
default: "false"
|
|
14
|
+
type: boolean
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: integration-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
integration:
|
|
22
|
+
name: pytest integration
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Install uv
|
|
30
|
+
uses: astral-sh/setup-uv@v5
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
cache-dependency-glob: "uv.lock"
|
|
34
|
+
|
|
35
|
+
- name: Set up Python 3.11
|
|
36
|
+
uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.11"
|
|
39
|
+
|
|
40
|
+
- name: Install system dependencies (Tesseract)
|
|
41
|
+
run: |
|
|
42
|
+
sudo apt-get update -qq
|
|
43
|
+
sudo apt-get install -y tesseract-ocr tesseract-ocr-eng
|
|
44
|
+
|
|
45
|
+
- name: Install project dependencies
|
|
46
|
+
run: uv sync --all-groups
|
|
47
|
+
|
|
48
|
+
# ── Model files ──────────────────────────────────────────────────────
|
|
49
|
+
# Models are NOT committed to git (binary, large, user-provided).
|
|
50
|
+
# Store each .tflite/.task file as a GitHub Actions secret (base64)
|
|
51
|
+
# or upload them to a private artifact store and cache them here.
|
|
52
|
+
#
|
|
53
|
+
# Example: add secret FACE_DETECTION_MODEL_B64 = base64-encoded .tflite
|
|
54
|
+
# and uncomment the block below.
|
|
55
|
+
#
|
|
56
|
+
# - name: Restore model files
|
|
57
|
+
# env:
|
|
58
|
+
# FACE_DETECTION_MODEL_B64: ${{ secrets.FACE_DETECTION_MODEL_B64 }}
|
|
59
|
+
# run: |
|
|
60
|
+
# mkdir -p models
|
|
61
|
+
# echo "$FACE_DETECTION_MODEL_B64" | base64 -d > models/face_detection.tflite
|
|
62
|
+
|
|
63
|
+
- name: Cache model files
|
|
64
|
+
id: cache-models
|
|
65
|
+
uses: actions/cache@v4
|
|
66
|
+
with:
|
|
67
|
+
path: models/
|
|
68
|
+
key: openvisionkit-models-${{ hashFiles('pyproject.toml') }}
|
|
69
|
+
|
|
70
|
+
- name: Run integration tests
|
|
71
|
+
env:
|
|
72
|
+
VISIONKIT_MODELS_DIR: ${{ github.workspace }}/models
|
|
73
|
+
run: |
|
|
74
|
+
uv run pytest tests/ \
|
|
75
|
+
-m "integration" \
|
|
76
|
+
--tb=short \
|
|
77
|
+
-v
|
|
78
|
+
|
|
79
|
+
- name: Upload test results
|
|
80
|
+
if: always()
|
|
81
|
+
uses: actions/upload-artifact@v4
|
|
82
|
+
with:
|
|
83
|
+
name: integration-test-results
|
|
84
|
+
path: |
|
|
85
|
+
.pytest_cache/
|
|
86
|
+
retention-days: 7
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
name: Security Scan
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
schedule:
|
|
9
|
+
# Run daily at 02:00 UTC to catch newly published CVEs
|
|
10
|
+
- cron: "0 2 * * *"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
security-events: write # required to upload SARIF to GitHub Security tab
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
# ── 1. Python dependency audit (pip-audit + OSV database) ─────────────────
|
|
19
|
+
pip-audit:
|
|
20
|
+
name: pip-audit (Python deps)
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
cache-dependency-glob: "uv.lock"
|
|
32
|
+
|
|
33
|
+
- name: Set up Python 3.11
|
|
34
|
+
uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.11"
|
|
37
|
+
|
|
38
|
+
- name: Install project dependencies
|
|
39
|
+
run: uv sync --all-groups
|
|
40
|
+
|
|
41
|
+
- name: Run pip-audit (SBOM)
|
|
42
|
+
run: |
|
|
43
|
+
uv run pip-audit \
|
|
44
|
+
--local \
|
|
45
|
+
--format cyclonedx-json \
|
|
46
|
+
--output pip-audit-sbom.json \
|
|
47
|
+
--progress-spinner off || true
|
|
48
|
+
|
|
49
|
+
- name: Run pip-audit (fail on critical)
|
|
50
|
+
run: |
|
|
51
|
+
uv run pip-audit \
|
|
52
|
+
--local \
|
|
53
|
+
--progress-spinner off
|
|
54
|
+
|
|
55
|
+
- name: Upload SBOM artifact
|
|
56
|
+
if: always()
|
|
57
|
+
uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: pip-audit-sbom
|
|
60
|
+
path: pip-audit-sbom.json
|
|
61
|
+
retention-days: 30
|
|
62
|
+
|
|
63
|
+
# ── 2. Trivy filesystem + dependency scan with SARIF upload ───────────────
|
|
64
|
+
trivy:
|
|
65
|
+
name: Trivy (filesystem + SCA)
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
|
|
68
|
+
steps:
|
|
69
|
+
- name: Checkout
|
|
70
|
+
uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- name: Run Trivy vulnerability scan
|
|
73
|
+
uses: aquasecurity/trivy-action@v0.36.0
|
|
74
|
+
with:
|
|
75
|
+
scan-type: fs
|
|
76
|
+
scan-ref: .
|
|
77
|
+
format: sarif
|
|
78
|
+
output: trivy-results.sarif
|
|
79
|
+
severity: CRITICAL,HIGH,MEDIUM
|
|
80
|
+
exit-code: "0" # don't block CI — upload findings first
|
|
81
|
+
ignore-unfixed: true
|
|
82
|
+
|
|
83
|
+
- name: Upload Trivy SARIF to GitHub Security tab
|
|
84
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
85
|
+
if: always()
|
|
86
|
+
with:
|
|
87
|
+
sarif_file: trivy-results.sarif
|
|
88
|
+
category: trivy
|
|
89
|
+
|
|
90
|
+
- name: Fail on CRITICAL vulnerabilities
|
|
91
|
+
uses: aquasecurity/trivy-action@v0.36.0
|
|
92
|
+
with:
|
|
93
|
+
scan-type: fs
|
|
94
|
+
scan-ref: .
|
|
95
|
+
format: table
|
|
96
|
+
severity: CRITICAL
|
|
97
|
+
exit-code: "1"
|
|
98
|
+
ignore-unfixed: true
|
|
99
|
+
|
|
100
|
+
# ── 3. CodeQL static analysis ─────────────────────────────────────────────
|
|
101
|
+
codeql:
|
|
102
|
+
name: CodeQL (static analysis)
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
permissions:
|
|
105
|
+
actions: read
|
|
106
|
+
contents: read
|
|
107
|
+
security-events: write
|
|
108
|
+
|
|
109
|
+
steps:
|
|
110
|
+
- name: Checkout
|
|
111
|
+
uses: actions/checkout@v4
|
|
112
|
+
|
|
113
|
+
- name: Initialize CodeQL
|
|
114
|
+
uses: github/codeql-action/init@v3
|
|
115
|
+
with:
|
|
116
|
+
languages: python
|
|
117
|
+
queries: security-and-quality
|
|
118
|
+
|
|
119
|
+
- name: Autobuild
|
|
120
|
+
uses: github/codeql-action/autobuild@v3
|
|
121
|
+
|
|
122
|
+
- name: Perform CodeQL Analysis
|
|
123
|
+
uses: github/codeql-action/analyze@v3
|
|
124
|
+
with:
|
|
125
|
+
category: codeql-python
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Unit Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: unit-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
unit:
|
|
15
|
+
name: pytest unit — Python ${{ matrix.python-version }}
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ["3.11", "3.12"]
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
cache-dependency-glob: "uv.lock"
|
|
32
|
+
|
|
33
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
34
|
+
uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python-version }}
|
|
37
|
+
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: uv sync --all-groups
|
|
40
|
+
|
|
41
|
+
- name: Run unit tests
|
|
42
|
+
run: |
|
|
43
|
+
uv run pytest tests/ \
|
|
44
|
+
-m "not integration" \
|
|
45
|
+
--cov=openvisionkit \
|
|
46
|
+
--cov-report=xml \
|
|
47
|
+
--cov-report=term-missing \
|
|
48
|
+
-v
|
|
49
|
+
|
|
50
|
+
- name: Upload coverage to Codecov
|
|
51
|
+
if: matrix.python-version == '3.11'
|
|
52
|
+
uses: codecov/codecov-action@v4
|
|
53
|
+
with:
|
|
54
|
+
files: coverage.xml
|
|
55
|
+
fail_ci_if_error: false
|
|
56
|
+
env:
|
|
57
|
+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: "Publish target"
|
|
10
|
+
required: true
|
|
11
|
+
default: testpypi
|
|
12
|
+
type: choice
|
|
13
|
+
options:
|
|
14
|
+
- testpypi
|
|
15
|
+
- pypi
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
id-token: write # required for PyPI Trusted Publishing (OIDC)
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
# ── 1. Unit test gate ─────────────────────────────────────────────────────
|
|
23
|
+
test:
|
|
24
|
+
name: Unit tests (pre-publish gate)
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
with:
|
|
34
|
+
enable-cache: true
|
|
35
|
+
cache-dependency-glob: "uv.lock"
|
|
36
|
+
|
|
37
|
+
- name: Set up Python 3.11
|
|
38
|
+
uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.11"
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
run: uv sync --all-groups
|
|
44
|
+
|
|
45
|
+
- name: Run unit tests
|
|
46
|
+
run: uv run pytest tests/ -m "not integration" -q
|
|
47
|
+
|
|
48
|
+
# ── 2. Build sdist + wheel ────────────────────────────────────────────────
|
|
49
|
+
build:
|
|
50
|
+
name: Build distribution
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
needs: test
|
|
53
|
+
|
|
54
|
+
steps:
|
|
55
|
+
- name: Checkout
|
|
56
|
+
uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- name: Install uv
|
|
59
|
+
uses: astral-sh/setup-uv@v5
|
|
60
|
+
with:
|
|
61
|
+
enable-cache: true
|
|
62
|
+
|
|
63
|
+
- name: Set up Python 3.11
|
|
64
|
+
uses: actions/setup-python@v5
|
|
65
|
+
with:
|
|
66
|
+
python-version: "3.11"
|
|
67
|
+
|
|
68
|
+
- name: Install dependencies
|
|
69
|
+
run: uv sync --all-groups
|
|
70
|
+
|
|
71
|
+
- name: Build sdist and wheel
|
|
72
|
+
run: uv build
|
|
73
|
+
|
|
74
|
+
- name: Verify build artifacts
|
|
75
|
+
run: |
|
|
76
|
+
ls -lh dist/
|
|
77
|
+
uv run python -m twine check dist/*
|
|
78
|
+
|
|
79
|
+
- name: Upload dist artifacts
|
|
80
|
+
uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: dist
|
|
83
|
+
path: dist/
|
|
84
|
+
retention-days: 7
|
|
85
|
+
|
|
86
|
+
# ── 3. Publish to TestPyPI (pre-releases + manual) ────────────────────────
|
|
87
|
+
publish-testpypi:
|
|
88
|
+
name: Publish → TestPyPI
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
needs: build
|
|
91
|
+
environment:
|
|
92
|
+
name: testpypi
|
|
93
|
+
url: https://test.pypi.org/p/openvisionkit
|
|
94
|
+
|
|
95
|
+
if: |
|
|
96
|
+
(github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi') ||
|
|
97
|
+
(github.event_name == 'release' && github.event.release.prerelease == true)
|
|
98
|
+
|
|
99
|
+
steps:
|
|
100
|
+
- name: Download dist artifacts
|
|
101
|
+
uses: actions/download-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: dist
|
|
104
|
+
path: dist/
|
|
105
|
+
|
|
106
|
+
- name: Publish to TestPyPI
|
|
107
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
108
|
+
with:
|
|
109
|
+
repository-url: https://test.pypi.org/legacy/
|
|
110
|
+
print-hash: true
|
|
111
|
+
|
|
112
|
+
# ── 4. Publish to PyPI (stable releases + manual) ────────────────────────
|
|
113
|
+
publish-pypi:
|
|
114
|
+
name: Publish → PyPI
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
needs: build
|
|
117
|
+
environment:
|
|
118
|
+
name: pypi
|
|
119
|
+
url: https://pypi.org/p/openvisionkit
|
|
120
|
+
|
|
121
|
+
if: |
|
|
122
|
+
(github.event_name == 'workflow_dispatch' && inputs.target == 'pypi') ||
|
|
123
|
+
(github.event_name == 'release' && github.event.release.prerelease == false)
|
|
124
|
+
|
|
125
|
+
steps:
|
|
126
|
+
- name: Download dist artifacts
|
|
127
|
+
uses: actions/download-artifact@v4
|
|
128
|
+
with:
|
|
129
|
+
name: dist
|
|
130
|
+
path: dist/
|
|
131
|
+
|
|
132
|
+
- name: Publish to PyPI
|
|
133
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
134
|
+
with:
|
|
135
|
+
print-hash: true
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Renovate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# Run every Monday at 01:00 UTC (before 6am SGT per renovate.json schedule)
|
|
6
|
+
- cron: "0 1 * * 1"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
dry_run:
|
|
10
|
+
description: "Dry-run (log only, no PRs created)"
|
|
11
|
+
required: false
|
|
12
|
+
default: "false"
|
|
13
|
+
type: boolean
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: write
|
|
17
|
+
pull-requests: write
|
|
18
|
+
issues: write
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
renovate:
|
|
22
|
+
name: Renovate dependency update bot
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Run Renovate
|
|
30
|
+
uses: renovatebot/github-action@v40
|
|
31
|
+
env:
|
|
32
|
+
# Set LOG_LEVEL=debug for verbose output
|
|
33
|
+
LOG_LEVEL: info
|
|
34
|
+
RENOVATE_DRY_RUN: ${{ inputs.dry_run == 'true' && 'full' || 'null' }}
|
|
35
|
+
with:
|
|
36
|
+
token: ${{ secrets.RENOVATE_TOKEN }}
|
|
37
|
+
# renovate.json at repo root is picked up automatically
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
name: Reusable GitHub Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_call:
|
|
5
|
+
inputs:
|
|
6
|
+
tag:
|
|
7
|
+
description: Git tag to release (e.g. v0.2.1). Must already exist in the repo.
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
python-version:
|
|
11
|
+
description: Python version used to build distribution artifacts
|
|
12
|
+
required: false
|
|
13
|
+
default: "3.11"
|
|
14
|
+
type: string
|
|
15
|
+
prerelease:
|
|
16
|
+
description: Mark this release as a pre-release
|
|
17
|
+
required: false
|
|
18
|
+
default: false
|
|
19
|
+
type: boolean
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
github-release:
|
|
23
|
+
name: Create GitHub Release — ${{ inputs.tag }}
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write # create release, upload assets
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
# ── Checkout at the exact tag ─────────────────────────────────────────
|
|
30
|
+
- name: Check out repository at tag
|
|
31
|
+
uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
ref: ${{ inputs.tag }}
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
fetch-tags: true
|
|
36
|
+
|
|
37
|
+
# ── Toolchain ─────────────────────────────────────────────────────────
|
|
38
|
+
- name: Set up uv
|
|
39
|
+
uses: astral-sh/setup-uv@v5
|
|
40
|
+
with:
|
|
41
|
+
python-version: ${{ inputs.python-version }}
|
|
42
|
+
enable-cache: true
|
|
43
|
+
|
|
44
|
+
# ── Derive version string from tag ────────────────────────────────────
|
|
45
|
+
- name: Derive version from tag
|
|
46
|
+
id: version
|
|
47
|
+
run: |
|
|
48
|
+
TAG="${{ inputs.tag }}"
|
|
49
|
+
VERSION="${TAG#v}" # strip leading 'v'
|
|
50
|
+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
51
|
+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
52
|
+
|
|
53
|
+
PRERELEASE="${{ inputs.prerelease }}"
|
|
54
|
+
if [ "$PRERELEASE" = "true" ] || echo "$TAG" | grep -qE '\-(alpha|beta|rc|dev)'; then
|
|
55
|
+
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
|
56
|
+
else
|
|
57
|
+
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# ── Build distribution artifacts ──────────────────────────────────────
|
|
61
|
+
- name: Build sdist and wheel
|
|
62
|
+
run: uv build
|
|
63
|
+
|
|
64
|
+
- name: List built artifacts
|
|
65
|
+
run: ls -lh dist/
|
|
66
|
+
|
|
67
|
+
# ── Validate distribution with twine ──────────────────────────────────
|
|
68
|
+
- name: Validate distributions
|
|
69
|
+
run: |
|
|
70
|
+
uv tool install twine
|
|
71
|
+
uv tool run twine check dist/*
|
|
72
|
+
|
|
73
|
+
# ── Generate SHA-256 checksums ─────────────────────────────────────────
|
|
74
|
+
- name: Generate SHA-256 checksums
|
|
75
|
+
run: |
|
|
76
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
77
|
+
cd dist
|
|
78
|
+
sha256sum * | tee "../checksums-${VERSION}.sha256"
|
|
79
|
+
cd ..
|
|
80
|
+
echo "Checksums:"
|
|
81
|
+
cat "checksums-${VERSION}.sha256"
|
|
82
|
+
|
|
83
|
+
# ── Extract release notes from CHANGELOG.md ───────────────────────────
|
|
84
|
+
- name: Extract release notes from CHANGELOG.md
|
|
85
|
+
id: notes
|
|
86
|
+
env:
|
|
87
|
+
RELEASE_VERSION: ${{ steps.version.outputs.version }}
|
|
88
|
+
RELEASE_TAG: ${{ steps.version.outputs.tag }}
|
|
89
|
+
run: |
|
|
90
|
+
NOTES=$(python3 - <<'PYEOF'
|
|
91
|
+
import re, os, sys
|
|
92
|
+
|
|
93
|
+
version = os.environ.get("RELEASE_VERSION", "")
|
|
94
|
+
tag = os.environ.get("RELEASE_TAG", "")
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
content = open("CHANGELOG.md").read()
|
|
98
|
+
except FileNotFoundError:
|
|
99
|
+
print("No CHANGELOG.md found.")
|
|
100
|
+
sys.exit(0)
|
|
101
|
+
|
|
102
|
+
sections = re.split(r"\n(?=## )", content)
|
|
103
|
+
target = next(
|
|
104
|
+
(s for s in sections
|
|
105
|
+
if version in s.split("\n")[0] or tag in s.split("\n")[0]),
|
|
106
|
+
None,
|
|
107
|
+
)
|
|
108
|
+
if target:
|
|
109
|
+
body = "\n".join(target.split("\n")[1:]).strip()
|
|
110
|
+
print(body if body else "No details in CHANGELOG for this version.")
|
|
111
|
+
else:
|
|
112
|
+
print(f"See CHANGELOG.md for details on {tag}.")
|
|
113
|
+
PYEOF
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
echo "notes<<RELEASE_NOTES_DELIMITER"
|
|
118
|
+
echo "$NOTES"
|
|
119
|
+
echo "RELEASE_NOTES_DELIMITER"
|
|
120
|
+
} >> "$GITHUB_OUTPUT"
|
|
121
|
+
|
|
122
|
+
# ── Create or update the GitHub release ───────────────────────────────
|
|
123
|
+
- name: Create GitHub release
|
|
124
|
+
id: create-release
|
|
125
|
+
env:
|
|
126
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
127
|
+
run: |
|
|
128
|
+
TAG="${{ steps.version.outputs.tag }}"
|
|
129
|
+
PRERELEASE="${{ steps.version.outputs.prerelease }}"
|
|
130
|
+
TITLE="Release ${TAG}"
|
|
131
|
+
NOTES="${{ steps.notes.outputs.notes }}"
|
|
132
|
+
|
|
133
|
+
PR_FLAG=""
|
|
134
|
+
[ "$PRERELEASE" = "true" ] && PR_FLAG="--prerelease"
|
|
135
|
+
|
|
136
|
+
if gh release view "$TAG" &>/dev/null; then
|
|
137
|
+
echo "Release $TAG already exists — updating notes."
|
|
138
|
+
gh release edit "$TAG" \
|
|
139
|
+
--title "$TITLE" \
|
|
140
|
+
--notes "$NOTES" \
|
|
141
|
+
$PR_FLAG \
|
|
142
|
+
--latest
|
|
143
|
+
else
|
|
144
|
+
gh release create "$TAG" \
|
|
145
|
+
--title "$TITLE" \
|
|
146
|
+
--notes "$NOTES" \
|
|
147
|
+
--verify-tag \
|
|
148
|
+
$PR_FLAG \
|
|
149
|
+
--latest
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# ── Upload distribution artifacts and checksums as release assets ─────
|
|
153
|
+
- name: Upload release assets
|
|
154
|
+
env:
|
|
155
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
156
|
+
run: |
|
|
157
|
+
TAG="${{ steps.version.outputs.tag }}"
|
|
158
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
159
|
+
|
|
160
|
+
gh release upload "$TAG" \
|
|
161
|
+
dist/* \
|
|
162
|
+
"checksums-${VERSION}.sha256" \
|
|
163
|
+
--clobber
|
|
164
|
+
|
|
165
|
+
# ── Job summary ───────────────────────────────────────────────────────
|
|
166
|
+
- name: Write job summary
|
|
167
|
+
if: always()
|
|
168
|
+
env:
|
|
169
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
170
|
+
run: |
|
|
171
|
+
TAG="${{ steps.version.outputs.tag }}"
|
|
172
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
173
|
+
RELEASE_URL=$(gh release view "$TAG" --json url -q .url 2>/dev/null || echo "N/A")
|
|
174
|
+
|
|
175
|
+
{
|
|
176
|
+
echo "## GitHub Release: ${TAG:-unknown}"
|
|
177
|
+
echo ""
|
|
178
|
+
echo "| Field | Value |"
|
|
179
|
+
echo "|-------|-------|"
|
|
180
|
+
echo "| Tag | \`${TAG}\` |"
|
|
181
|
+
echo "| Version | \`${VERSION}\` |"
|
|
182
|
+
echo "| Pre-release | \`${{ steps.version.outputs.prerelease }}\` |"
|
|
183
|
+
echo "| Release URL | ${RELEASE_URL} |"
|
|
184
|
+
echo ""
|
|
185
|
+
echo "### Artifacts"
|
|
186
|
+
echo "\`\`\`"
|
|
187
|
+
[ -d dist ] && ls -lh dist/ || echo "dist/ not found — build may have failed"
|
|
188
|
+
[ -f "checksums-${VERSION}.sha256" ] && cat "checksums-${VERSION}.sha256" || echo "checksums file not found"
|
|
189
|
+
echo "\`\`\`"
|
|
190
|
+
echo ""
|
|
191
|
+
echo "### Release Notes"
|
|
192
|
+
echo "${{ steps.notes.outputs.notes }}"
|
|
193
|
+
} >> "$GITHUB_STEP_SUMMARY"
|