Metadata-Version: 2.4
Name: gheasy
Version: 0.0.2
Summary: github made easy
Project-URL: Repository, https://github.com/vedicreader/gheasy
Project-URL: Documentation, https://vedicreader.github.io/gheasy/
Author-email: Karthik <karthik.rajgopal@hotmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: nbdev
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Requires-Dist: cyclopts>=0.23
Requires-Dist: ghapi>=1.0.13
Requires-Dist: pyyaml>=6.0
Requires-Dist: ruamel-yaml>=0.18
Requires-Dist: tomlkit>=0.14.0
Description-Content-Type: text/markdown

# gheasy


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Install

``` bash
pip install gheasy
```

## Quickstart

One call generates a complete Python CI workflow:

``` python
from gheasy.workflow import uv_ci
print(uv_ci("ci", lint_cmd=None).to_yaml())
```

    name: ci
    on:
      push:
        branches:
          - main
      pull_request:
        branches:
          - main
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Install dependencies
            run: uv sync --frozen
          - name: Test
            run: uv run pytest

## Library CI

gheasy, dockeasy, and every `*easy` package uses this pattern. Lint on
push → test (needs lint) → publish to PyPI (needs test):

``` python
from gheasy.workflow import Workflow

wfb = Workflow("ci")
wfb.on.push(branches=["main"]).pull_request()
wfb.uv_lint_job()
wfb.uv_test_job(needs="lint")
wfb.uv_pypi_job(needs="test")
print(wfb.build().to_yaml())
```

    name: ci
    on:
      push:
        branches:
          - main
      pull_request:
    jobs:
      lint:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Install dependencies
            run: uv sync --frozen
          - name: Lint
            run: uv run ruff check . && uv run ruff format --check .
      test:
        needs: lint
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Install dependencies
            run: uv sync --frozen
          - name: Test
            run: uv run pytest
      publish:
        needs: test
        runs-on: ubuntu-latest
        if: github.event_name == 'release'
        permissions:
          id-token: write
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Build
            run: uv build
          - name: Publish
            uses: pypa/gh-action-pypi-publish@release/v1

## App Pipeline

For web apps (FastHTML, Django, etc.) — test on every push, deploy to
Fly.io on main. Uses the DSL directly for custom step logic:

``` python
wfb = Workflow("deploy")
wfb.on.push(branches=["main"])

wfb.uv_test_job()

wfb.job("deploy").needs("test").runs_on("ubuntu-latest")\
    .checkout().end_step()\
    .setup_uv().end_step()\
    .step("Deploy to Fly.io").run("fly deploy --remote-only").end_job()

print(wfb.build().to_yaml())
```

    name: deploy
    on:
      push:
        branches:
          - main
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Install dependencies
            run: uv sync --frozen
          - name: Test
            run: uv run pytest
      deploy:
        needs: test
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Deploy to Fly.io
            run: fly deploy --remote-only

## Project setup: LFS & secrets

Not every project needs CI. Sometimes you just need git-lfs tracking and
GitHub secrets synced from your local `.env`. For an app like
[vedicreader](https://github.com/vedicreader) with media files and OAuth
secrets:

``` python
from gheasy.core import gh_lfs, gh_secrets_from_file, gh_push_env, GheasyConfig

# 1. Track binary/media files in git-lfs
gh_lfs(['*.mp3', '*.ogg', '*.wav', '*.png', '*.jpg', '*.webp', '*.xml', '*.db'])

# 2. Push .env values to GitHub — None-default keys become secrets, string-default become variables
cfg = GheasyConfig(app='vedicreader', env_schema={
    # variables (have sane defaults)
    'MODE': 'dev', 'PORT': '5001', 'DOMAIN': 'http://localhost:5001',
    'WANT_GOOGLE': 'true', 'WANT_GIT': 'false',
    'NEED_BACKUP': 'false', 'RC_TYPE': 's3', 'RC_PROVIDER': 'Cloudflare',
    # secrets (no default — must be set)
    'JWT_SCRT': None, 'RESEND_API_KEY': None,
    'GOOGLE_CLI': None, 'GOOGLE_SCRT': None,
    'CF_ACCESS_KEY_ID': None, 'CF_SCRT_ACCESS_KEY': None, 'CF_ENDPOINT': None,
})
cfg.save()

# reads local .env, routes each key: None-schema → gh secret set, string-schema → gh variable set
import os
gh_push_env(dict(os.environ))
```

Or push everything from as secrets (no schema needed):

``` python
# Push all KEY=VALUE pairs from .env as GitHub secrets
gh_secrets_from_file('.env')

# With dry-run to preview what would be pushed
gh_secrets_from_file('.env', dry_run=True)
```

## Cross-package pipeline

gheasy generates workflows that call your own Python code. Here: a
workflow that uses [dockeasy](https://github.com/karthik777/dockeasy) to
generate a Dockerfile, then builds and pushes to GHCR — triggered only
when the relevant source files change:

``` python
wfb = Workflow("Build caddy-sqlite image")
wfb.on.push(branches=["main"], paths=["dockeasy/proxy.py", "nbs/01_proxy.ipynb"])
wfb.on.workflow_dispatch()

wfb.job("build-and-push").runs_on("ubuntu-latest")\
    .permissions(contents="read", packages="write")\
    .checkout().end_step()\
    .setup_uv().end_step()\
    .step("Install deps").run("uv sync").end_step()\
    .step("Generate Dockerfile").run(
        'uv run python -c "\n'
        'from dockeasy.proxy import caddy_sqlite_dockerfile\n'
        'caddy_sqlite_dockerfile().save(\'Dockerfile\')\n'
        '"'
    ).end_step()\
    .step("Log in to GHCR").uses("docker/login-action@v3")\
        .with_(registry="ghcr.io",
               username="${{ github.actor }}",
               password="${{ secrets.GITHUB_TOKEN }}").end_step()\
    .step("Build and push").uses("docker/build-push-action@v5")\
        .with_(context=".", push=True,
               tags="ghcr.io/vedicreader/caddy-sqlite:latest").end_job()

print(wfb.build().to_yaml())
```

    name: Build caddy-sqlite image
    on:
      push:
        branches:
          - main
        paths:
          - dockeasy/proxy.py
          - nbs/01_proxy.ipynb
      workflow_dispatch:
    jobs:
      build-and-push:
        runs-on: ubuntu-latest
        permissions:
          contents: read
          packages: write
        steps:
          - name: Checkout
            uses: actions/checkout@v4
          - name: Setup uv
            uses: astral-sh/setup-uv@v5
          - name: Install deps
            run: uv sync
          - name: Generate Dockerfile
            run: "uv run python -c \"\nfrom dockeasy.proxy import caddy_sqlite_dockerfile\ncaddy_sqlite_dockerfile().save('Dockerfile')\n\
              \""
          - name: Log in to GHCR
            uses: docker/login-action@v3
            with:
              registry: ghcr.io
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}
          - name: Build and push
            uses: docker/build-push-action@v5
            with:
              context: .
              push: true
              tags: ghcr.io/vedicreader/caddy-sqlite:latest
