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
245 changes: 152 additions & 93 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,93 +1,152 @@
name: CI/CD

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
release:
types: [ published ]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run tests
run: |
pytest --cov=pdf_splitter --cov-report=xml --cov-report=term

- name: Upload coverage
uses: codecov/codecov-action@v3
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
file: ./coverage.xml
fail_ci_if_error: false

lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

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

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run flake8
run: flake8 src/

- name: Run black check
run: black --check src/

- name: Run mypy
run: mypy src/

publish:
needs: [test, lint]
runs-on: ubuntu-latest
if: github.event_name == 'release'

steps:
- uses: actions/checkout@v3

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

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Build package
run: python -m build

- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
name: CI/CD

on:
push:
branches: [main, dev, qa, release]
pull_request:
branches: [main, dev, qa, release]
workflow_dispatch:

permissions:
contents: read

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run tests
run: |
pytest --cov=pdf_splitter --cov-report=xml --cov-report=term

- name: Upload coverage
uses: codecov/codecov-action@v4
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
with:
file: ./coverage.xml
fail_ci_if_error: false

lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run flake8
run: flake8 src/

- name: Run black check
run: black --check src/

- name: Run mypy
run: mypy src/

publish-preview:
needs: [test, lint]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/qa'

steps:
- uses: actions/checkout@v4

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

- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Set preview version
env:
RUN_NUMBER: ${{ github.run_number }}
run: |
python - <<'PY'
from pathlib import Path
import os
import re

pyproject = Path('pyproject.toml')
content = pyproject.read_text(encoding='utf-8')
match = re.search(r'^version\s*=\s*"([^"]+)"', content, flags=re.MULTILINE)
if not match:
raise SystemExit('Could not find [project].version in pyproject.toml')

stable_version = match.group(1)
run_number = os.environ['RUN_NUMBER']
preview_version = f"{stable_version}.dev{run_number}"
updated = re.sub(
r'^version\s*=\s*"([^"]+)"',
f'version = "{preview_version}"',
content,
count=1,
flags=re.MULTILINE,
)
pyproject.write_text(updated, encoding='utf-8')
print(f"Preview version set to: {preview_version}")
PY

- name: Build preview package
run: python -m build

- name: Publish preview to TestPyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: twine upload --repository-url https://test.pypi.org/legacy/ --skip-existing dist/*

publish-release:
needs: [test, lint]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/release'

steps:
- uses: actions/checkout@v4

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

- name: Install build tooling
run: |
python -m pip install --upgrade pip
pip install build twine

- name: Build package
run: python -m build

- name: Publish release to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload --skip-existing dist/*
45 changes: 38 additions & 7 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,27 @@ This creates:
- `dist/pdf_chapter_splitter-X.Y.Z-py3-none-any.whl` (wheel)
- `dist/pdf-chapter-splitter-X.Y.Z.tar.gz` (source)

## Publishing to PyPI

### Test PyPI (recommended first)
## Publishing to PyPI

## Branching and Promotion Flow

The repository uses a staged promotion model:

- `dev`: integration branch for feature and bug-fix PRs
- `qa`: pre-release validation branch (preview package publishing)
- `release`: production-ready branch (stable package publishing)
- `main`: should be kept aligned with `release` and not used for direct feature work

Recommended merge path:

1. `feature/*` -> `dev`
2. `dev` -> `qa`
3. `qa` -> `release`
4. `release` -> `main`

Use branch protection rules on `dev`, `qa`, `release`, and `main` to require PRs and passing CI checks.

### Test PyPI (recommended first)

1. Create account on [test.pypi.org](https://test.pypi.org)
2. Generate API token
Expand All @@ -116,7 +134,7 @@ twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ pdf-chapter-splitter
```

### Production PyPI
### Production PyPI

1. Create account on [pypi.org](https://pypi.org)
2. Generate API token
Expand All @@ -128,13 +146,26 @@ username = __token__
password = pypi-your-api-token-here
```

4. Upload:
4. Upload:

```bash
make publish
# Or
twine upload dist/*
```
twine upload dist/*
```

## Automated Publishing via GitHub Actions

The CI workflow is configured so that:

- PRs and pushes to `main`, `dev`, `qa`, and `release` run tests and lint checks.
- Pushes to `qa` publish a preview package to TestPyPI using a generated `.dev<run_number>` suffix.
- Pushes to `release` publish stable packages to PyPI.

Required repository secrets:

- `TEST_PYPI_API_TOKEN` for preview publishing from `qa`
- `PYPI_API_TOKEN` for stable publishing from `release`

## Project Structure

Expand Down
Loading