diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..6220de7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,89 @@ +name: publish + +on: + push: + tags: ["v*", "!archive/**"] + +permissions: + contents: read + +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false + +jobs: + version: + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.version.outputs.version }} + prerelease: ${{ steps.version.outputs.prerelease }} + major: ${{ steps.version.outputs.major }} + minor: ${{ steps.version.outputs.minor }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + - name: Tag matches the declared version + id: version + run: | + uv run --no-project --with packaging python - <<'EOF' >> "$GITHUB_OUTPUT" + import os, subprocess, sys + from packaging.version import Version + declared = Version(subprocess.check_output(["uvx", "--from", "poetry", "--with", "poetry-dynamic-versioning[plugin]", "poetry", "version", "--short"], text=True).strip()) + # org tag convention: vX.Y.Z or vX.Y.Z-rc.N, which must spell exactly the declared version + base = declared.base_version + expected = f"v{base}-rc.{declared.pre[1]}" if declared.pre and declared.pre[0] == "rc" else f"v{declared}" + if os.environ["GITHUB_REF_NAME"] != expected or declared.dev is not None or declared.local: + sys.exit(f"tag {os.environ['GITHUB_REF_NAME']} does not match poetry-dynamic-versioning {declared} (expected {expected})") + print(f"version={declared}") + print(f"prerelease={str(declared.is_prerelease).lower()}") + print(f"major={declared.major}") + print(f"minor={declared.major}.{declared.minor}") + EOF + + tests: + needs: version + uses: ./.github/workflows/tests.yml + secrets: inherit + + pypi: + environment: pypi + needs: [version, tests] + runs-on: ubuntu-24.04 + permissions: + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + - run: uv build + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + + release: + needs: [pypi] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 + with: + path: . + format: spdx-json + output-file: ${{ github.event.repository.name }}-${{ github.ref_name }}.spdx.json + upload-artifact: false + upload-release-assets: false + - name: GitHub Release with SBOM + env: + GH_TOKEN: ${{ github.token }} + run: | + PRERELEASE=() + if [[ "$GITHUB_REF_NAME" == *-rc.* ]]; then PRERELEASE=(--prerelease); fi + gh release create "$GITHUB_REF_NAME" "${{ github.event.repository.name }}-$GITHUB_REF_NAME.spdx.json" \ + --repo "$GITHUB_REPOSITORY" --verify-tag --generate-notes "${PRERELEASE[@]}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f20c6d6..87d2382 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,8 +3,11 @@ on: push: branches: - master - - dev pull_request: + workflow_call: + +permissions: + contents: read concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} @@ -12,104 +15,49 @@ concurrency: jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - run: | - pipx install ruff - ruff check - ruff format + pipx install poetry + poetry install --only dev --no-root + poetry run ruff check + poetry run ruff format --check test: needs: lint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: # if one python version fails, let the others finish fail-fast: false matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt update - sudo apt install -y tesseract-ocr chromium-browser + sudo apt install -y tesseract-ocr pip install poetry poetry install + # chromium-browser on ubuntu-24.04 is a snap shim that fails to install. The image ships chrome, but AppArmor + # blocks the unprivileged user namespaces its sandbox needs. + - name: Allow the Chrome sandbox + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Run tests - env: - CHROME_DEVEL_SANDBOX: /usr/lib/chromium/chrome-sandbox run: | poetry run pytest -s -vv --disable-warnings --cov-report xml:cov.xml --cov=webcap . - name: Upload Code Coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./cov.xml verbose: true - publish: - runs-on: ubuntu-latest - needs: test - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install poetry build - poetry self add "poetry-dynamic-versioning[plugin]" - - name: Get current version from Poetry - id: get_version - run: | - VERSION=$(poetry version --short) - echo "VERSION=$VERSION" | tee -a $GITHUB_ENV - - name: Fetch latest tag - run: | - git fetch --tags - LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "v0.0.0") - echo "LATEST_TAG=$LATEST_TAG" | tee -a $GITHUB_ENV - - name: Detect version change - run: | - # Retrieve and strip "v" prefix if present - PREVIOUS_VERSION="${{ env.LATEST_TAG }}" - CURRENT_VERSION="${{ env.VERSION }}" - - echo "PREVIOUS_VERSION=$PREVIOUS_VERSION" - echo "CURRENT_VERSION=$CURRENT_VERSION" - - # Extract major.minor for comparison - CURRENT_MAJOR_MINOR=$(echo "${CURRENT_VERSION#v}" | cut -d '.' -f 1-2) - PREVIOUS_MAJOR_MINOR=$(echo "${PREVIOUS_VERSION#v}" | cut -d '.' -f 1-2) - - # Compare versions - if [ "$CURRENT_MAJOR_MINOR" == "$PREVIOUS_MAJOR_MINOR" ]; then - echo "VERSION_CHANGE=false" | tee -a $GITHUB_ENV - else - echo "VERSION_CHANGE=true" | tee -a $GITHUB_ENV - fi - shell: bash - - name: Build PyPi package - if: github.ref == 'refs/heads/master' - run: poetry build - - name: Publish PyPi package - if: github.ref == 'refs/heads/master' - uses: pypa/gh-action-pypi-publish@release/v1.12 - with: - password: ${{ secrets.PYPI_API_TOKEN }} - - name: Tag the release - if: github.ref == 'refs/heads/master' && env.VERSION_CHANGE == 'true' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ env.VERSION }}" -m "Release ${{ env.VERSION }}" - git push origin "refs/tags/${{ env.VERSION }}" diff --git a/poetry.lock b/poetry.lock index 3da6135..afa41ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1445,4 +1445,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.14" -content-hash = "13c16c3b1c6b7ba5138c7945cca0fe84bdd1e08c51d6f6a582a7b7c89849791f" +content-hash = "e80b7f3dae508bdf2312a8002e46c6f1204d78d7a8e3a743cb8379e5ed632535" diff --git a/pyproject.toml b/pyproject.toml index 25ca767..1e89c17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ line-length = 119 lint.ignore = ["E402", "E721", "E741", "F401", "F403", "F405", "E713"] [tool.poetry.group.dev.dependencies] -ruff = ">=0.9.1,<0.12.0" +ruff = "==0.11.10" pytest-httpserver = "^1.1.0" pytest-asyncio = "^0.25.2" poetry-dynamic-versioning = "^1.4.1" @@ -49,4 +49,3 @@ build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] enable = true metadata = true -format = "v0.1.{distance}"