Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/actions/setup-hatch/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ description: >-
runs:
using: "composite"
steps:
# Match the dev extra and build-system backend pins in pyproject.toml.
- shell: bash
run: |
python -m pip install --upgrade pip
pip install "virtualenv<20.36"
pip install hatchling==1.27.0 hatch==1.14.0
pip install hatchling==1.33.0 hatch==1.18.2
12 changes: 6 additions & 6 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
uv: "true"
mode: firewall-free
Expand Down Expand Up @@ -241,7 +241,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
uv: "true"
mode: firewall-enterprise
Expand Down Expand Up @@ -332,7 +332,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
node: "true"
mode: firewall-free
Expand Down Expand Up @@ -393,7 +393,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
node: "true"
mode: firewall-enterprise
Expand Down Expand Up @@ -454,7 +454,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
python: "true"
mode: firewall-free
Expand Down Expand Up @@ -518,7 +518,7 @@ jobs:
echo "sha=${{ github.event.pull_request.head.sha }}"
} > sfw-artifacts/context.txt

- uses: $/.github/actions/setup-sfw
- uses: ./.github/actions/setup-sfw
with:
python: "true"
mode: firewall-enterprise
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
persist-credentials: false

- name: Set up Docker publishing
uses: $/.github/actions/setup-docker
uses: ./.github/actions/setup-docker
with:
# Retagging copies manifests between tags; nothing is built.
enable-qemu: "false"
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Lint

env:
PYTHON_VERSION: "3.12"

# Report on every PR, including changes outside the Python test paths.
# Required checks need a result even when a PR changes no Python files.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: lint-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
ruff:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: 🐍 setup python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: 🛠️ install deps
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --locked --extra dev
- name: 🧹 ruff check
run: uv run --locked --extra dev ruff check
- name: 🎨 ruff format
run: uv run --locked --extra dev ruff format --check
2 changes: 1 addition & 1 deletion .github/workflows/package-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
python-version: "3.12"

- name: Install build tooling
uses: $/.github/actions/setup-hatch
uses: ./.github/actions/setup-hatch

- name: Install distribution validator
run: python -m pip install "twine>=4.0.0"
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ jobs:
core.setOutput('head_sha', pullRequest.head.sha);

build:
# Manual builds execute PR code in the default branch's cache scope.
# Keep this job free of cache restore/save steps. Publishing runs separately
# with write permissions and consumes artifacts without checking out PR code.
needs: context
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -112,7 +115,7 @@ jobs:
python-version: "3.12"

- name: Install build tooling
uses: $/.github/actions/setup-hatch
uses: ./.github/actions/setup-hatch

- name: Install distribution validator
run: python -m pip install "twine>=4.0.0"
Expand Down Expand Up @@ -252,7 +255,7 @@ jobs:
path: dist

- name: Set up Docker publishing
uses: $/.github/actions/setup-docker
uses: ./.github/actions/setup-docker
with:
# QEMU is required to build the arm64 layer on an amd64 runner.
enable-qemu: "true"
Expand Down
20 changes: 0 additions & 20 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,6 @@ jobs:
print('import smoke OK')
"

ruff:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: 🐍 setup python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: 🛠️ install deps
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --extra dev
- name: 🧹 run ruff
run: uv run ruff check

unsupported-python-install:
runs-on: ubuntu-latest
timeout-minutes: 10
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ jobs:
python-version: '3.13'

- name: Install build tooling
uses: $/.github/actions/setup-hatch
uses: ./.github/actions/setup-hatch

- name: Get Version
id: version
env:
REF_NAME: ${{ github.ref_name }}
run: |
RAW_VERSION=$(hatch version)
echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV
echo "VERSION=$RAW_VERSION" >> "$GITHUB_ENV"
if [ "v$RAW_VERSION" != "$REF_NAME" ]; then
echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)"
exit 1
Expand All @@ -39,12 +39,12 @@ jobs:
env:
VERSION: ${{ env.VERSION }}
run: |
if curl -s -f https://pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then
if curl -s -f "https://pypi.org/pypi/socketsecurity/$VERSION/json" > /dev/null; then
echo "Version ${VERSION} already exists on PyPI"
echo "pypi_exists=true" >> $GITHUB_OUTPUT
echo "pypi_exists=true" >> "$GITHUB_OUTPUT"
else
echo "Version ${VERSION} not found on PyPI - proceeding with PyPI deployment"
echo "pypi_exists=false" >> $GITHUB_OUTPUT
echo "pypi_exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Check Docker image existence
Expand All @@ -54,9 +54,9 @@ jobs:
run: |
if curl -s -f "https://hub.docker.com/v2/repositories/socketdev/cli/tags/${VERSION}" > /dev/null; then
echo "Docker image socketdev/cli:${VERSION} already exists"
echo "docker_exists=true" >> $GITHUB_OUTPUT
echo "docker_exists=true" >> "$GITHUB_OUTPUT"
else
echo "docker_exists=false" >> $GITHUB_OUTPUT
echo "docker_exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Build package
Expand All @@ -70,7 +70,7 @@ jobs:
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3

- name: Set up Docker publishing
uses: $/.github/actions/setup-docker
uses: ./.github/actions/setup-docker
with:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down
101 changes: 1 addition & 100 deletions .github/workflows/version-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,106 +29,7 @@ jobs:
python -m pip install --upgrade pip
pip install packaging

# Get version from current PR
PR_VERSION=$(grep -o "__version__.*" socketsecurity/__init__.py | awk '{print $3}' | tr -d "'")
echo "PR_VERSION=$PR_VERSION" >> $GITHUB_ENV

# Get version from main branch
MAIN_VERSION=$(git show origin/main:socketsecurity/__init__.py | grep -o "__version__.*" | awk '{print $3}' | tr -d "'")
echo "MAIN_VERSION=$MAIN_VERSION" >> $GITHUB_ENV

export PR_VERSION
export MAIN_VERSION

# Compare against the latest published PyPI release.
python3 <<'PY'
import json
import os
import subprocess
import tomllib
import urllib.request
from packaging import version

pr_ver = version.parse(os.environ["PR_VERSION"])
main_ver = version.parse(os.environ["MAIN_VERSION"])

with open("pyproject.toml", "rb") as fh:
pyproject_ver = version.parse(tomllib.load(fh)["project"]["version"])

# The version is two hand-maintained literals with nothing deriving one
# from the other: pyproject.toml is what actually gets published, and
# socketsecurity/__init__.py is what the CLI reports as its User-Agent.
# Every comparison below reads only __init__.py, so bumping that alone
# would pass this job and then publish under the old number -- caught
# late, by twine rejecting an existing file, after the merge. Require
# the two to agree before comparing anything. (uv.lock carries a third
# copy, but uv derives it and `uv lock --locked` in python-tests
# already fails when it drifts.)
if pr_ver != pyproject_ver:
print(
f"❌ Version mismatch inside the PR: pyproject.toml is "
f"{pyproject_ver}, socketsecurity/__init__.py is {pr_ver}. "
f"Bump both."
)
raise SystemExit(1)

with urllib.request.urlopen("https://pypi.org/pypi/socketsecurity/json") as response:
pypi_data = json.load(response)

published_versions = []
for raw in pypi_data.get("releases", {}).keys():
parsed = version.parse(raw)
if not parsed.is_prerelease and not parsed.is_devrelease:
published_versions.append(parsed)

pypi_ver = max(published_versions) if published_versions else version.parse("0.0.0")

# The only hard requirement is that the version is ahead of what is
# actually released. Treating main's version as a second floor breaks
# the legitimate case where several PRs share one unreleased release:
# the first bumps main to the new version and the rest ride it without
# bumping again, which is what keeps them under a single changelog
# header. Main is therefore only a floor when this PR moves the
# version -- a change to it must go forwards, never backwards.
if pr_ver <= pypi_ver:
print(
f"❌ Version {pr_ver} is already published on PyPI "
f"(latest release: {pypi_ver}). Bump it."
)
raise SystemExit(1)

# Releases are immutable: once a v<version> release is published, its tag can't
# be moved or reused, even when that release failed and nothing reached PyPI.
# PyPI alone misses that, so a burned tag would only surface when the release
# run fails after the merge.
tag = f"v{os.environ['PR_VERSION']}"
if subprocess.run(
["git", "rev-parse", "--quiet", "--verify", f"refs/tags/{tag}"],
capture_output=True,
).returncode == 0:
print(
f"❌ Tag {tag} already exists, so {pr_ver} can never be released "
f"(latest PyPI release: {pypi_ver}). Bump to a version with no tag."
)
raise SystemExit(1)

if pr_ver < main_ver:
print(
f"❌ Version moves backwards: main is {main_ver}, PR is {pr_ver}."
)
raise SystemExit(1)

if pr_ver == main_ver:
print(
f"✅ Riding main's unreleased {pr_ver} "
f"(latest PyPI release: {pypi_ver})."
)
else:
print(
f"✅ Version properly incremented. "
f"Main: {main_ver}, PyPI: {pypi_ver}, PR: {pr_ver}"
)
PY
python3 .hooks/check_version.py

- name: Require uv.lock update when pyproject changes
run: |
Expand Down
Loading
Loading