From 6da2cc0e31bacce799bc55113d6e55a57318f86f Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:05:46 +0000 Subject: [PATCH 1/2] chore: remove vestigial setup.cfg, add [tool.wads.ci], migrate CI to the stub setup.cfg was pure legacy cruft: pyproject.toml has been the SSOT (hatchling backend, full [project] metadata) for a while, nothing in the repo referenced setup.cfg except a stale CI comment, and its only activity was the CI bot's version-bump push-back keeping it in sync with a value nothing reads. Removed. [tool.wads.ci] was entirely absent even though ci.yml already reads it -- added project_name + testing.python_versions explicitly. Then converted the already-green Modern-uv-inline workflow to the reusable stub (sanctioned per wads-migrate once CI is green). --- .github/workflows/ci.yml | 278 +++++++-------------------------------- pyproject.toml | 6 + setup.cfg | 23 ---- 3 files changed, 57 insertions(+), 250 deletions(-) delete mode 100644 setup.cfg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b587de..a09825a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,232 +1,56 @@ -name: Continuous Integration (uv) -# MIGRATION NOTE: PyPI auth changed: set secrets.PYPI_PASSWORD to a PyPI API token (uv publish uses UV_PUBLISH_TOKEN, mapped from PYPI_PASSWORD) -# MIGRATION NOTE: Old CI references setup.cfg - ensure project is migrated to pyproject.toml first (use: wads-migrate setup-to-pyproject) +# wads CI — calls the reusable workflow hosted in i2mint/wads. +# +# All configuration comes from this repo's pyproject.toml [tool.wads.ci.*]. +# To customize the workflow itself (rare), replace this file with the +# full inline template `wads/data/github_ci_uv.yml` from i2mint/wads. +# +# Pinning: `@master` floats with wads. If you need version stability for +# a release-sensitive repo, change `@master` to a wads tag (e.g. `@0.2.15`; +# tags have no `v` prefix). A stub whose `secrets:` block passes the JSON +# transport (the default below) needs a tag from a release after 0.2.14 — +# older tags don't declare that secret and GitHub then rejects the +# workflow at parse time. +# CI failure does not block a published release — it blocks the publish +# step itself — so floating master is generally safe. +# +# Permissions: GitHub validates that the caller grants AT LEAST the +# permissions any job in the called workflow requests — at workflow-parse +# time, not at run-time, even if the job would be skipped via `if:`. +# The reusable workflow needs: +# contents: write for the publish job's version-bump push-back +# and for the github-pages job's gh-pages branch push +# pages: write for the github-pages job's REST API Pages config +# Both default to `write` on org-account GITHUB_TOKEN and need to be +# granted explicitly on personal-account callers (where the default is +# read-only). No `id-token: write` needed — the publish-github-pages +# action uses peaceiris/actions-gh-pages (branch-based) + REST API, +# not the OIDC `actions/deploy-pages` flow. +name: Continuous Integration on: [push, pull_request] - -# Note: Environment variables (PROJECT_NAME and vars from [tool.wads.ci.env]) -# are set by the read-ci-config action in the setup job and made available -# to all subsequent jobs via GITHUB_ENV - jobs: - # First job: Read configuration from pyproject.toml - setup: - name: Read Configuration - runs-on: ubuntu-latest - outputs: - project-name: ${{ steps.config.outputs.project-name }} - python-versions: ${{ steps.config.outputs.python-versions }} - pytest-args: ${{ steps.config.outputs.pytest-args }} - coverage-enabled: ${{ steps.config.outputs.coverage-enabled }} - exclude-paths: ${{ steps.config.outputs.exclude-paths }} - test-on-windows: ${{ steps.config.outputs.test-on-windows }} - build-sdist: ${{ steps.config.outputs.build-sdist }} - build-wheel: ${{ steps.config.outputs.build-wheel }} - metrics-enabled: ${{ steps.config.outputs.metrics-enabled }} - metrics-config-path: ${{ steps.config.outputs.metrics-config-path }} - metrics-storage-branch: ${{ steps.config.outputs.metrics-storage-branch }} - metrics-python-version: ${{ steps.config.outputs.metrics-python-version }} - metrics-force-run: ${{ steps.config.outputs.metrics-force-run }} - ruff-enabled: ${{ steps.config.outputs.ruff-enabled }} - black-enabled: ${{ steps.config.outputs.black-enabled }} - mypy-enabled: ${{ steps.config.outputs.mypy-enabled }} - docs-enabled: ${{ steps.config.outputs.docs-enabled }} - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - run: uv python install 3.11 - - - name: Read CI Config - id: config - uses: i2mint/wads/actions/read-ci-config@master - with: - pyproject-path: . - - # Second job: Validation using the config - validation: - name: Validation - if: "!contains(github.event.head_commit.message, '[skip ci]')" - needs: setup - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ${{ fromJson(needs.setup.outputs.python-versions) }} - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Set up Python ${{ matrix.python-version }} - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ matrix.python-version }} - - - name: Install System Dependencies - uses: i2mint/wads/actions/install-system-deps@master - with: - pyproject-path: . - - - name: Install Dependencies - uses: i2mint/wads/actions/install-deps-uv@master - - - name: Format Source Code - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff format . - - - name: Format Source Code (black) - if: needs.setup.outputs.black-enabled == 'true' - run: uvx black . - - - name: Lint Validation - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff check --output-format=github ${{ needs.setup.outputs.project-name }} - - - name: Type Check (mypy) - if: needs.setup.outputs.mypy-enabled == 'true' - run: uvx mypy ${{ needs.setup.outputs.project-name }} - - - name: Run Tests - uses: i2mint/wads/actions/run-tests-uv@master - with: - root-dir: ${{ needs.setup.outputs.project-name }} - pytest-args: ${{ needs.setup.outputs.pytest-args }} - exclude-paths: ${{ needs.setup.outputs.exclude-paths }} - coverage: ${{ needs.setup.outputs.coverage-enabled }} - - - name: Track Code Metrics - if: needs.setup.outputs.metrics-enabled == 'true' - uses: i2mint/umpyre/actions/track-metrics@master - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - config-path: ${{ needs.setup.outputs.metrics-config-path }} - storage-branch: ${{ needs.setup.outputs.metrics-storage-branch }} - python-version: ${{ needs.setup.outputs.metrics-python-version }} - force-run: ${{ needs.setup.outputs.metrics-force-run }} - - # Optional Windows testing (if enabled in config) - windows-validation: - name: Windows Tests - if: "!contains(github.event.head_commit.message, '[skip ci]') && needs.setup.outputs.test-on-windows == 'true'" - needs: setup - runs-on: windows-latest - continue-on-error: true - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Set up Python - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} - - - name: Install System Dependencies - uses: i2mint/wads/actions/install-system-deps@master - with: - pyproject-path: . - - - name: Install Dependencies - uses: i2mint/wads/actions/install-deps-uv@master - - - name: Run Tests - uses: i2mint/wads/actions/run-tests-uv@master - with: - root-dir: ${{ needs.setup.outputs.project-name }} - pytest-args: ${{ needs.setup.outputs.pytest-args }} - exclude-paths: ${{ needs.setup.outputs.exclude-paths }} - - # Publishing job - publish: - name: Publish - permissions: - contents: write - if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')" - needs: [setup, validation] - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} - create-venv: "false" - - - name: Format Source Code - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff format . - - - name: Format Source Code (black) - if: needs.setup.outputs.black-enabled == 'true' - run: uvx black . - - - name: Update Version Number - id: version - uses: i2mint/isee/actions/bump-version-number@master - - - name: Build Distribution - uses: i2mint/wads/actions/build-dist-uv@master - with: - sdist: ${{ needs.setup.outputs.build-sdist }} - wheel: ${{ needs.setup.outputs.build-wheel }} - - - name: Publish to PyPI - uses: i2mint/wads/actions/pypi-publish-uv@master - with: - pypi-token: ${{ secrets.PYPI_PASSWORD }} - - - name: Force SSH for git remote - run: git remote set-url origin git@github.com:${{ github.repository }}.git - - - name: Commit Changes - uses: i2mint/wads/actions/git-commit@master - with: - commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]" - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - push: true - - - name: Tag Repository - uses: i2mint/wads/actions/git-tag@master - with: - tag: ${{ env.VERSION }} - message: "Release version ${{ env.VERSION }}" - push: true - - # Optional GitHub Pages (skipped when [tool.wads.ci.docs].enabled = false) - github-pages: - name: Publish GitHub Pages + ci: + uses: i2mint/wads/.github/workflows/uv-ci.yml@master permissions: contents: write pages: write - id-token: write - if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.setup.outputs.docs-enabled != 'false'" - needs: [setup, publish] - runs-on: ubuntu-latest - - steps: - - uses: i2mint/epythet/actions/publish-github-pages@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - ignore: "tests/,scrap/,examples/" - # WP5 pilot: opt into epythet v2 ahead of the fleet default (i2mint/epythet#16) - epythet-spec: "epythet>=0.2,<0.3" + # Transport: this repo's whole `secrets` context, serialized into the one + # secret the reusable workflow declares. Double-encoded (toJSON twice) so + # the value is a single line — a multiline secret would register its `{` + # and `}` lines as global log masks. Any secret name works; there is no + # fixed list to fall outside of. (Cross-owner `secrets: inherit` does not + # propagate secrets, so it cannot replace this.) + # + # Note this hands EVERY secret this repo can read — including org-level + # ones — to the called workflow. For a minimal secret surface (only the + # names you list), regenerate with + # wads-migrate ci-to-stub --transport named + # + # *Which* of these become job env vars — and which are required — is + # driven entirely by [tool.wads.ci.env] in pyproject.toml; nothing is + # exported unless declared there (`wads-secrets add VAR_NAME` declares + # one and can set its value). Non-sensitive values don't need a secret: + # use [tool.wads.ci.env].defaults (committed literals) or a repository + # *variable* (`gh variable set NAME`) — declared names fall back to + # repo variables automatically. + secrets: + WADS_CI_SECRETS_JSON: ${{ toJSON(toJSON(secrets)) }} diff --git a/pyproject.toml b/pyproject.toml index e127acd..d6ae809 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,3 +82,9 @@ testpaths = ["config2py"] # overrides this setting. Diverging here means doctests that pass locally can # fail in CI (and vice versa) -- notably NORMALIZE_WHITESPACE, which CI does not set. doctest_optionflags = ["ELLIPSIS", "IGNORE_EXCEPTION_DETAIL"] + +[tool.wads.ci] +project_name = "config2py" + +[tool.wads.ci.testing] +python_versions = ["3.10", "3.12"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b58edba..0000000 --- a/setup.cfg +++ /dev/null @@ -1,23 +0,0 @@ -[metadata] -name = config2py -version = 0.1.53 -url = https://github.com/i2mint/config2py -platforms = any -description_file = README.md -root_url = https://github.com/i2mint/ -license = apache-2.0 -description = Simplified reading and writing configurations from various sources and formats -long_description = file:README.md -long_description_content_type = text/markdown -keywords = -display_name = config2py - -[options] -packages = find: -include_package_data = True -zip_safe = False -install_requires = - dol - i2 - importlib_resources; python_version < '3.9' - From 356f6cb7cfa419c94a58f0899e00149bacd74c1b Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 13 Oct 2026 03:06:02 +0000 Subject: [PATCH 2/2] ci: pass secrets by name in the stub (wads#106) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a09825a..11eac02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,7 @@ # # Pinning: `@master` floats with wads. If you need version stability for # a release-sensitive repo, change `@master` to a wads tag (e.g. `@0.2.15`; -# tags have no `v` prefix). A stub whose `secrets:` block passes the JSON -# transport (the default below) needs a tag from a release after 0.2.14 — -# older tags don't declare that secret and GitHub then rejects the -# workflow at parse time. +# tags have no `v` prefix). # CI failure does not block a published release — it blocks the publish # step itself — so floating master is generally safe. # @@ -33,24 +30,26 @@ jobs: permissions: contents: write pages: write - # Transport: this repo's whole `secrets` context, serialized into the one - # secret the reusable workflow declares. Double-encoded (toJSON twice) so - # the value is a single line — a multiline secret would register its `{` - # and `}` lines as global log masks. Any secret name works; there is no - # fixed list to fall outside of. (Cross-owner `secrets: inherit` does not - # propagate secrets, so it cannot replace this.) + # Transport (named): passes ONLY the secrets listed below — PYPI_PASSWORD + # (for the publish job) plus the secret behind each env var declared in + # [tool.wads.ci.env]. `wads-secrets add VAR_NAME` declares a var and adds + # its secret here. Every listed name must be declared by the reusable + # workflow (the superset in wads/ci_secrets.py) or GitHub rejects this + # workflow at parse time; a missing name is a one-line addition there. + # (Cross-owner `secrets: inherit` does not propagate secrets, so it + # cannot replace this list.) # - # Note this hands EVERY secret this repo can read — including org-level - # ones — to the called workflow. For a minimal secret surface (only the - # names you list), regenerate with - # wads-migrate ci-to-stub --transport named + # Opt-in alternative: `wads-migrate ci-to-stub --transport json` passes + # this repo's WHOLE secrets context — every secret it can read, including + # org-level ones — as one WADS_CI_SECRETS_JSON secret. GitHub's + # malicious-workflow scanner may hold runs that do that: `action_required`, + # zero jobs, no log (i2mint/wads#74, #101). # # *Which* of these become job env vars — and which are required — is # driven entirely by [tool.wads.ci.env] in pyproject.toml; nothing is - # exported unless declared there (`wads-secrets add VAR_NAME` declares - # one and can set its value). Non-sensitive values don't need a secret: + # exported unless declared there. Non-sensitive values don't need a secret: # use [tool.wads.ci.env].defaults (committed literals) or a repository # *variable* (`gh variable set NAME`) — declared names fall back to # repo variables automatically. secrets: - WADS_CI_SECRETS_JSON: ${{ toJSON(toJSON(secrets)) }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}