Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 23 additions & 0 deletions .github/workflows/_codespell.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: codespell

on:
workflow_call:

permissions:
contents: read

jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
# actions/checkout@v4
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# codespell-project/actions-codespell@v2
- name: Codespell
uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630
with:
skip: "*.lock,*.json,*.yaml,*.yml"
ignore_words_list: "oci,orcl"
38 changes: 38 additions & 0 deletions .github/workflows/_lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: lint

on:
workflow_call:

permissions:
contents: read

env:
HATCH_VERSION: "1.14.0"
RUFF_OUTPUT_FORMAT: github

jobs:
build:
name: "make lint #${{ matrix.python-version }}"
runs-on: ubuntu-latest
strategy:
matrix:
# Only lint on the min and max supported Python versions.
python-version:
- "3.11"
- "3.14"
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# actions/setup-python@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install Hatch
run: pip install hatch==${{ env.HATCH_VERSION }}

- name: Run linting
run: hatch run lint
180 changes: 180 additions & 0 deletions .github/workflows/_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: Release

on:
release:
types: [published]
workflow_dispatch:

# Default to read-only. Individual publishing jobs declare the scopes they
# actually need (id-token for PyPI trusted publishing in future; none of
# these jobs currently need contents: write).
permissions:
contents: read

env:
PYTHON_VERSION: "3.11"
HATCH_VERSION: "1.14.0"

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# actions/setup-python@v5
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install Hatch
run: pip install hatch==${{ env.HATCH_VERSION }}

- name: Run unit tests
run: hatch run test

build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
pkg-name: ${{ steps.check-version.outputs.pkg-name }}
version: ${{ steps.check-version.outputs.version }}
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# actions/setup-python@v5
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install Hatch
run: pip install hatch==${{ env.HATCH_VERSION }}

- name: Build project for distribution
run: hatch build

# actions/upload-artifact@v4
- name: Upload build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: dist
path: dist/

- name: Check Version
id: check-version
run: |
echo "pkg-name=locus" >> "$GITHUB_OUTPUT"
echo "version=$(hatch version)" >> "$GITHUB_OUTPUT"

test-pypi-publish:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
environment:
name: testpypi
url: https://test.pypi.org/project/${{ needs.build.outputs.pkg-name }}/
steps:
# actions/download-artifact@v4
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: dist
path: dist/

# actions/setup-python@v5
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.x"

- name: Publish to TestPyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }}
run: |
pip install twine
twine upload -r testpypi dist/* --verbose

pre-release-checks:
needs:
- build
- test-pypi-publish
runs-on: ubuntu-latest
permissions:
contents: read
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# actions/setup-python@v5
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Import published package
# Fetch the freshly-published package from TestPyPI *without* its
# dependencies (--no-deps), then resolve the dependency tree from
# PyPI only. Using --extra-index-url on test.pypi.org lets pip pick
# a higher-versioned squatted transitive dependency from the
# open-registration TestPyPI sandbox (classic dependency confusion,
# CWE-829 / CWE-494).
env:
PKG_NAME: ${{ needs.build.outputs.pkg-name }}
VERSION: ${{ needs.build.outputs.version }}
run: |
set -eu
# Step 1: install the package itself from TestPyPI, no deps.
install_from_testpypi() {
pip install \
--index-url https://test.pypi.org/simple/ \
--no-deps \
"$PKG_NAME==$VERSION"
}
install_from_testpypi || ( sleep 5 && install_from_testpypi )
# Step 2: resolve dependencies from PyPI only.
pip install \
--index-url https://pypi.org/simple/ \
"$PKG_NAME==$VERSION"
python -c "import locus; print(dir(locus))"

publish:
needs:
- build
- test-pypi-publish
- pre-release-checks
runs-on: ubuntu-latest
permissions:
contents: read
environment:
name: pypi
url: https://pypi.org/p/${{ needs.build.outputs.pkg-name }}
steps:
# actions/download-artifact@v4
- name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: dist
path: dist/

# actions/setup-python@v5
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.x"

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
pip install twine
twine upload dist/*
45 changes: 45 additions & 0 deletions .github/workflows/_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: test

on:
workflow_call:

permissions:
contents: read

env:
HATCH_VERSION: "1.14.0"

jobs:
build:
name: "make test #${{ matrix.python-version }}"
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

# actions/setup-python@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install Hatch
run: pip install hatch==${{ env.HATCH_VERSION }}

- name: Run core tests
run: hatch run test

- name: Ensure the tests did not create any additional files
run: |
set -eu
STATUS="$(git status)"
echo "$STATUS"
echo "$STATUS" | grep 'nothing to commit, working tree clean'
77 changes: 77 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Minimal default permissions for the CI pipeline. Individual jobs can raise
# to the narrowest scope they actually need; reusable sub-workflows declare
# their own permissions blocks.
permissions:
contents: read

# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Lint
uses: ./.github/workflows/_lint.yml
permissions:
contents: read

test:
name: Test
uses: ./.github/workflows/_test.yml
permissions:
contents: read

codespell:
name: Codespell
uses: ./.github/workflows/_codespell.yml
permissions:
contents: read

# Verify commits are signed off (OCA compliance)
dco:
name: DCO Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: read
steps:
# actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0

- name: Check DCO
# tisonkun/actions-dco@v1.1 — v1.2 was deleted upstream; pin to SHA
# to avoid silently using a tag that disappears or is rewritten.
uses: tisonkun/actions-dco@f1024cd563550b5632e754df11b7d30b73be54a5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

ci-success:
name: CI Success
needs: [lint, test, codespell]
if: always()
runs-on: ubuntu-latest
permissions: {}
env:
JOBS_JSON: ${{ toJSON(needs) }}
RESULTS_JSON: ${{ toJSON(needs.*.result) }}
EXIT_CODE: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && '0' || '1'}}
steps:
- name: CI Success
run: |
echo "$JOBS_JSON"
echo "$RESULTS_JSON"
echo "Exiting with $EXIT_CODE"
exit "$EXIT_CODE"
Loading