feat(validation): accept a checkout as a meta-schema source #48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Main | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| workflow_dispatch: | |
| jobs: | |
| quality: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pre-commit | |
| key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| - name: Run checks | |
| run: make check | |
| tests-and-type-check: | |
| runs-on: ${{ matrix.platform }} | |
| strategy: | |
| matrix: | |
| platform: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| fail-fast: false | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Run tests | |
| run: uv run python -m pytest tests --cov --cov-config=pyproject.toml --cov-report=xml | |
| - name: Check typing | |
| run: uv run ty check | |
| - name: Upload coverage reports to Codecov on Ubuntu / Python 3.11 | |
| uses: codecov/codecov-action@v4 | |
| if: ${{ matrix.platform == 'ubuntu-latest' && matrix.python-version == '3.11' }} | |
| env: | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| validate-schemas: | |
| # Runs the OO-LD validator over the committed fixtures, offline, so a regression in the | |
| # validation pipeline fails CI even when the unit tests still pass. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| - name: Validate the committed OO-LD fixtures | |
| run: make validate | |
| - name: Check parity against the reference harness | |
| run: | | |
| # oold-schema owns the reference implementation. Comparing against its current main | |
| # catches drift that the committed fixture snapshot cannot see. | |
| git clone --depth 1 https://github.com/OO-LD/oold-schema.git /tmp/oold-schema | |
| npm --prefix /tmp/oold-schema install --no-audit --no-fund | |
| OOLD_SCHEMA_DIR=/tmp/oold-schema uv run python -m pytest \ | |
| tests/test_validation/test_parity_live.py -v --no-cov | |
| continue-on-error: true | |
| check-docs: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| # A project that declares any markdown_extensions replaces Zensical's defaults | |
| # rather than extending them, so a missing one silently switches an extension | |
| # off without failing the build below. Catch that before it does. | |
| - name: Check the restated Markdown extensions match Zensical's defaults | |
| run: uv run python scripts/check_markdown_extensions.py | |
| - name: Check if documentation can be built | |
| run: uv run zensical build -s | |
| version-preview: | |
| # Show contributors what a merge would release, based on the conventional | |
| # commits on the branch. Pull requests only; informational. | |
| if: github.event_name == 'pull_request' && github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # PSR needs full history + tags to compute the bump | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| - name: Compute release preview | |
| id: preview | |
| run: | | |
| # PSR only computes a release on the main branch; name the checked-out | |
| # PR head 'main' so the preview reflects what a merge would release. | |
| git checkout -B main >/dev/null 2>&1 || true | |
| current=$(uv version --short 2>/dev/null || echo "unknown") | |
| next=$(uv run semantic-release version --print 2>/dev/null || echo "") | |
| if [ -n "$next" ] && [ "$next" != "$current" ]; then | |
| echo "released=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "released=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "current=$current" >> "$GITHUB_OUTPUT" | |
| echo "next=$next" >> "$GITHUB_OUTPUT" | |
| # Stamp the upcoming changelog section locally (no commit/tag/push/build) | |
| # so the preview shows the notes this merge would add. | |
| if [ -n "$next" ] && [ "$next" != "$current" ]; then | |
| uv run semantic-release version --no-commit --no-tag --no-push --no-vcs-release --skip-build >/dev/null 2>&1 || true | |
| fi | |
| { | |
| echo "changelog<<PSR_EOF" | |
| git --no-pager diff HEAD -- CHANGELOG.md | awk '/^\+/ && !/^\+\+\+/ {sub(/^\+/,""); print}' | head -80 | |
| echo "PSR_EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Post release preview comment | |
| uses: marocchino/sticky-pull-request-comment@v2 | |
| with: | |
| header: version-preview | |
| message: | | |
| ## Release preview | |
| ${{ steps.preview.outputs.released == 'true' && format('Merging this PR would release **v{0}** (current: `v{1}`).', steps.preview.outputs.next, steps.preview.outputs.current) || format('No version bump from the current commits (stays at `v{0}`). Use conventional commit types (`feat`, `fix`, ...) to trigger a release.', steps.preview.outputs.current) }} | |
| <details><summary>Changelog preview (truncated)</summary> | |
| ```markdown | |
| ${{ steps.preview.outputs.changelog }} | |
| ``` | |
| </details> | |
| <sub>Preview via python-semantic-release and conventional commits.</sub> | |
| benchmark: | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Check out | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # need history for baseline comparison | |
| - name: Set up the environment | |
| uses: ./.github/actions/setup-python-env | |
| # Run current benchmarks | |
| - name: Run benchmark tests | |
| run: >- | |
| uv run python -m pytest -c pytest.benchmark.ini | |
| --benchmark-only --benchmark-autosave | |
| --benchmark-json=benchmark_results.json | |
| continue-on-error: true | |
| # Try to get baseline from main branch via worktree (avoids switching branches | |
| # in the main workspace, which would delete action files not yet on main) | |
| - name: Get baseline benchmarks | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| git worktree add /tmp/baseline main 2>/dev/null || { | |
| echo "⚠️ Could not create worktree for main, skipping baseline" | |
| exit 0 | |
| } | |
| cd /tmp/baseline | |
| uv run python -m pytest -c pytest.benchmark.ini \ | |
| --benchmark-only --benchmark-autosave \ | |
| --benchmark-json="$GITHUB_WORKSPACE/baseline_benchmark.json" || true | |
| if [ -f "$GITHUB_WORKSPACE/baseline_benchmark.json" ]; then | |
| echo "✓ Saved baseline benchmark results" | |
| else | |
| echo "⚠️ No baseline benchmarks generated" | |
| fi | |
| git -C "$GITHUB_WORKSPACE" worktree remove /tmp/baseline --force || true | |
| continue-on-error: true | |
| # Compare benchmarks if baseline exists | |
| - name: Compare with baseline | |
| if: github.event_name == 'pull_request' && hashFiles('baseline_benchmark.json') != '' | |
| run: | | |
| if [ ! -f baseline_benchmark.json ]; then | |
| echo "❌ baseline_benchmark.json not found!" | |
| exit 1 | |
| fi | |
| if [ ! -f benchmark_results.json ]; then | |
| echo "❌ benchmark_results.json not found!" | |
| exit 1 | |
| fi | |
| uv run python scripts/compare_benchmarks.py \ | |
| baseline_benchmark.json \ | |
| benchmark_results.json \ | |
| --threshold 1.3 \ | |
| --quiet > benchmark_comparison.txt | |
| echo "Comparison output:" | |
| cat benchmark_comparison.txt | |
| continue-on-error: true | |
| # Comment on PR with results | |
| - name: Post benchmark results to PR | |
| if: github.event_name == 'pull_request' && hashFiles('baseline_benchmark.json') != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let comparison = 'No comparison data available'; | |
| try { | |
| if (fs.existsSync('benchmark_comparison.txt')) { | |
| comparison = fs.readFileSync('benchmark_comparison.txt', 'utf8').trim(); | |
| if (!comparison) { | |
| comparison = 'Comparison file is empty - check workflow logs for details'; | |
| } | |
| } | |
| } catch (e) { | |
| comparison = `Error reading comparison: ${e.message}`; | |
| console.error('Error:', e); | |
| } | |
| const body = `## 📊 Benchmark Results | |
| <details> | |
| <summary>Click to see benchmark comparison</summary> | |
| \`\`\` | |
| ${comparison} | |
| \`\`\` | |
| </details> | |
| **Threshold:** 1.3x (30% slower triggers a regression warning) | |
| **Note:** Benchmarks are informational only and won't fail the build. | |
| <sub>💡 Tip: Download the \`benchmark-results\` artifact for detailed JSON data</sub> | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| continue-on-error: true | |
| # Upload benchmark artifacts | |
| - name: Upload benchmark results | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: benchmark-results | |
| path: | | |
| benchmark_results.json | |
| baseline_benchmark.json | |
| .benchmarks/ | |
| retention-days: 30 |