Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 49 additions & 17 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,62 @@ on:
tags:
- 'v*'

permissions:
contents: read

jobs:
build-and-publish:
runs-on: ubuntu-latest
validate:
uses: ./.github/workflows/validate-package.yml
with:
expected-version: ${{ github.ref_name }}

publish:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install build dependencies
- name: Download validated distributions
uses: actions/download-artifact@v4
with:
name: validated-distributions
path: dist
- name: Confirm version is not already on PyPI
env:
RELEASE_VERSION: ${{ github.ref_name }}
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install build twine
python - <<'PY'
import os
from urllib.error import HTTPError
from urllib.request import urlopen

- name: Build package
run: python -m build

- name: Publish package to PyPI
version = os.environ["RELEASE_VERSION"].removeprefix("v")
try:
with urlopen(f"https://pypi.org/pypi/how-cli/{version}/json", timeout=20):
raise SystemExit(f"Refusing to publish how-cli {version}: this version already exists on PyPI")
except HTTPError as error:
if error.code != 404:
raise SystemExit(f"Could not check PyPI for how-cli {version}: HTTP {error.code}")
except Exception as error:
raise SystemExit(f"Could not check PyPI for how-cli {version}: {type(error).__name__}")
print(f"Version {version} is not currently on PyPI")
PY
- name: Install upload tool
run: python -m pip install --upgrade twine
- name: Publish distributions to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
run: |
if [ -z "$TWINE_PASSWORD" ]; then
echo "PyPI upload token is missing. Configure the PYPI_API_TOKEN repository secret or enable Trusted Publishing."
exit 1
fi
python -m twine upload --non-interactive dist/* || {
echo "PyPI upload failed. Check the repository token's validity and project permissions; credentials were not printed."
exit 1
}
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: pip
cache-dependency-path: requirements.txt
- name: Install project and validation tools
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
python -m pip install -e .
- name: Run tests
run: python -m pytest -q
- name: Check CLI startup
run: |
how --version
how --help
python -m how --help
how setup --help
how to --help
how fix --help
- name: Build distributions
run: python -m build
- name: Validate distributions
run: python -m twine check dist/*
84 changes: 84 additions & 0 deletions .github/workflows/validate-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Validate Package

on:
workflow_call:
inputs:
expected-version:
required: true
type: string
workflow_dispatch:
inputs:
expected-version:
description: Expected package version
required: true
type: string

permissions:
contents: read

jobs:
build-validate-install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build and metadata tools
run: python -m pip install --upgrade pip build twine packaging
- name: Build distributions
run: python -m build
- name: Validate distributions
run: python -m twine check dist/*
- name: Verify distributions and version
env:
EXPECTED_VERSION: ${{ inputs.expected-version || github.event.inputs.expected-version }}
run: |
python - <<'PY'
import os
import tarfile
import zipfile
from pathlib import Path
from email.parser import Parser
from packaging.utils import canonicalize_name, parse_wheel_filename, parse_sdist_filename

expected = os.environ["EXPECTED_VERSION"].removeprefix("v")
artifacts = list(Path("dist").iterdir())
wheels = [path for path in artifacts if path.suffix == ".whl"]
sdists = [path for path in artifacts if path.name.endswith(".tar.gz")]
if len(wheels) != 1 or len(sdists) != 1:
raise SystemExit(f"Expected one wheel and one sdist, found {artifacts}")

wheel_name, wheel_version, *_ = parse_wheel_filename(wheels[0].name)
sdist_name, sdist_version = parse_sdist_filename(sdists[0].name)
with zipfile.ZipFile(wheels[0]) as archive:
metadata_path = next(name for name in archive.namelist() if name.endswith(".dist-info/METADATA"))
wheel_metadata = Parser().parsestr(archive.read(metadata_path).decode())
with tarfile.open(sdists[0], "r:gz") as archive:
metadata_path = next(name for name in archive.getnames() if name.endswith("/PKG-INFO"))
sdist_metadata = Parser().parsestr(archive.extractfile(metadata_path).read().decode())
if canonicalize_name(wheel_metadata["Name"]) != "how-cli" or canonicalize_name(sdist_metadata["Name"]) != "how-cli":
raise SystemExit("Distribution metadata has an unexpected package name")
versions = tuple(map(str, (wheel_version, sdist_version, wheel_metadata["Version"], sdist_metadata["Version"])))
if any(version != expected for version in versions):
raise SystemExit(f"Expected version {expected}; found {versions}")
for archive in (wheels[0], sdists[0]):
if archive.stat().st_size == 0:
raise SystemExit(f"Empty distribution: {archive}")
print(f"Validated {wheel_name} {expected} in wheel and sdist")
PY
- name: Install wheel in a clean environment and smoke test
run: |
python -m venv /tmp/how-cli-wheel-env
/tmp/how-cli-wheel-env/bin/python -m pip install --upgrade pip
/tmp/how-cli-wheel-env/bin/python -m pip install dist/*.whl
cd /tmp
/tmp/how-cli-wheel-env/bin/how --version
/tmp/how-cli-wheel-env/bin/how --help
/tmp/how-cli-wheel-env/bin/python -c 'import how; print(how.__file__)'
- name: Save validated distributions
uses: actions/upload-artifact@v4
with:
name: validated-distributions
path: dist/*
if-no-files-found: error
2 changes: 1 addition & 1 deletion how/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
Package containing the core modules of the project.
"""

__version__ = "0.3.0"
__version__ = "0.3.1"
__author__ = "Rudransh Joshi (FireHead90544)"
6 changes: 6 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-r requirements.txt

# Local and CI validation tools
pytest
build
twine
Loading