From bb1bd5e276d6c1258a9f8e55530dfca248334563 Mon Sep 17 00:00:00 2001 From: CoderDeltaLan Date: Thu, 18 Sep 2025 21:08:49 +0100 Subject: [PATCH 1/3] ci: add publish-pypi & dependabot automations to main (hotfix) --- .github/dependabot.yml | 20 +++++++++ .github/workflows/auto-merge-deps.yml | 26 +++++++++++ .github/workflows/dependabot-auto-merge.yml | 30 +++++++++++++ .github/workflows/publish-pypi.yml | 49 +++++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/auto-merge-deps.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .github/workflows/publish-pypi.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a9a7615 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + rebase-strategy: auto + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 diff --git a/.github/workflows/auto-merge-deps.yml b/.github/workflows/auto-merge-deps.yml new file mode 100644 index 0000000..3f29112 --- /dev/null +++ b/.github/workflows/auto-merge-deps.yml @@ -0,0 +1,26 @@ +name: Auto-merge Dependabot when green +on: + pull_request: + types: [labeled, synchronize, reopened, ready_for_review] + check_suite: + types: [completed] +permissions: + contents: write + pull-requests: write +jobs: + enable-automerge: + runs-on: ubuntu-latest + steps: + - name: Find green Dependabot PRs + uses: peter-evans/find-pull-request@v3 + id: find + with: + author: dependabot[bot] + state: open + base: main + - name: Enable auto-merge (squash) + if: steps.find.outputs.pull-requests != '' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + pull-request-number: ${{ fromJson(steps.find.outputs.pull-requests)[0].number }} + merge-method: squash diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..1ce3f7d --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,30 @@ +name: dependabot-auto-merge + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + if: ${{ github.actor == 'dependabot[bot]' }} + runs-on: ubuntu-latest + steps: + - name: Fetch metadata + id: meta + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable PR auto-merge (patch/minor only) + if: | + steps.meta.outputs.update-type == 'version-update:semver-patch' || + steps.meta.outputs.update-type == 'version-update:semver-minor' + uses: peter-evans/enable-pull-request-automerge@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pull-request-number: ${{ github.event.pull_request.number }} + merge-method: squash diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..4e1cde8 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,49 @@ +name: publish-pypi + +on: + workflow_dispatch: + push: + tags: + - "v*" + +permissions: + contents: read + id-token: write + +jobs: + build-publish: + name: Build & publish to PyPI + runs-on: ubuntu-latest + environment: pypi + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install build backend + run: python -m pip install --upgrade pip build + + - name: Build sdist & wheel + run: python -m build --sdist --wheel --outdir dist/ + + - name: Check tag matches version (only on tags) + if: startsWith(github.ref, 'refs/tags/') + shell: bash + run: | + PYPROJECT_VERSION=$(python - <<'PY' + import tomllib + print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"]) + PY + ) + TAG="${GITHUB_REF_NAME#v}" + echo "pyproject.toml: $PYPROJECT_VERSION / tag: $TAG" + test "$TAG" = "$PYPROJECT_VERSION" + + - name: Publish to PyPI (Trusted Publisher) + if: startsWith(github.ref, 'refs/tags/') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print_hash: true From a6fe934f02ffb8100cd6f9d9213fe2ea69065045 Mon Sep 17 00:00:00 2001 From: CoderDeltaLan Date: Thu, 18 Sep 2025 21:39:14 +0100 Subject: [PATCH 2/3] style(ci): Prettier normalize & poetry-friendly version check --- .github/workflows/publish-pypi.yml | 19 +++++++++++-------- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 4e1cde8..1837ac9 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,15 +1,11 @@ name: publish-pypi - on: workflow_dispatch: push: - tags: - - "v*" - + tags: ["v*"] permissions: contents: read id-token: write - jobs: build-publish: name: Build & publish to PyPI @@ -17,6 +13,8 @@ jobs: environment: pypi steps: - uses: actions/checkout@v4 + with: + fetch-tags: true - uses: actions/setup-python@v5 with: @@ -34,12 +32,17 @@ jobs: shell: bash run: | PYPROJECT_VERSION=$(python - <<'PY' - import tomllib - print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"]) + import tomllib, sys + d = tomllib.load(open("pyproject.toml","rb")) + v = (d.get("project") or {}).get("version") \ + or (d.get("tool", {}).get("poetry") or {}).get("version") + if not v: + sys.exit(2) + print(v) PY ) TAG="${GITHUB_REF_NAME#v}" - echo "pyproject.toml: $PYPROJECT_VERSION / tag: $TAG" + echo "pyproject/poetry: $PYPROJECT_VERSION / tag: $TAG" test "$TAG" = "$PYPROJECT_VERSION" - name: Publish to PyPI (Trusted Publisher) diff --git a/pyproject.toml b/pyproject.toml index 1c1726d..1c7361f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ci-matrix-starter" -version = "0.1.0" +version = "0.1.4" description = "Reusable CI workflows for Py/TS with SBOM & signatures." authors = ["CoderDeltaLAN "] readme = "README.md" From 85737ce31f7fc114730b77df77e318ded4847baa Mon Sep 17 00:00:00 2001 From: CoderDeltaLan Date: Thu, 18 Sep 2025 21:41:37 +0100 Subject: [PATCH 3/3] ci(pypi): robust tag/version check (supports [project] & [tool.poetry]); ignore *.yml.bak --- .github/workflows/publish-pypi.yml | 29 +++++--- .github/workflows/release.yml | 109 +++++++++++++++++++++++++++++ .gitignore | 3 + 3 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 1837ac9..3170c7a 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,11 +1,15 @@ name: publish-pypi + on: workflow_dispatch: push: - tags: ["v*"] + tags: + - "v*" + permissions: contents: read id-token: write + jobs: build-publish: name: Build & publish to PyPI @@ -13,8 +17,6 @@ jobs: environment: pypi steps: - uses: actions/checkout@v4 - with: - fetch-tags: true - uses: actions/setup-python@v5 with: @@ -27,23 +29,30 @@ jobs: - name: Build sdist & wheel run: python -m build --sdist --wheel --outdir dist/ - - name: Check tag matches version (only on tags) - if: startsWith(github.ref, 'refs/tags/') + - name: Read version from pyproject + id: ver shell: bash run: | - PYPROJECT_VERSION=$(python - <<'PY' + PY_VER=$(python - <<'PY' import tomllib, sys - d = tomllib.load(open("pyproject.toml","rb")) + with open("pyproject.toml","rb") as f: + d = tomllib.load(f) v = (d.get("project") or {}).get("version") \ or (d.get("tool", {}).get("poetry") or {}).get("version") if not v: - sys.exit(2) + sys.exit("No version in [project] or [tool.poetry]") print(v) PY ) + echo "py_ver=$PY_VER" >> "$GITHUB_OUTPUT" + + - name: Check tag matches version (only on tags) + if: startsWith(github.ref, 'refs/tags/') + shell: bash + run: | TAG="${GITHUB_REF_NAME#v}" - echo "pyproject/poetry: $PYPROJECT_VERSION / tag: $TAG" - test "$TAG" = "$PYPROJECT_VERSION" + echo "pyproject: ${{ steps.ver.outputs.py_ver }} / tag: $TAG" + test "$TAG" = "${{ steps.ver.outputs.py_ver }}" - name: Publish to PyPI (Trusted Publisher) if: startsWith(github.ref, 'refs/tags/') diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..46c8c2f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,109 @@ +name: release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + id-token: write + +env: + PKG_NAME: ci-matrix-starter + +jobs: + verify: + name: Verify tag == project version (and package.json if exists) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Read version from pyproject (project/tool.poetry) + id: pyver + shell: bash + run: | + python - <<'PY' > version.txt + import sys, tomllib, pathlib + d = tomllib.loads(pathlib.Path("pyproject.toml").read_bytes()) + v = (d.get("project") or {}).get("version") \ + or (d.get("tool",{}).get("poetry") or {}).get("version") + if not v: sys.exit("No version in [project] or [tool.poetry]") + print(v) + PY + echo "ver=$(cat version.txt)" >> "$GITHUB_OUTPUT" + + - name: Check tag matches pyproject version + shell: bash + run: | + TAG="${GITHUB_REF_NAME#v}" + echo "pyproject: ${{ steps.pyver.outputs.ver }} | tag: $TAG" + test "$TAG" = "${{ steps.pyver.outputs.ver }}" + + - name: If Node workspace exists, check package.json version too (best-effort) + shell: bash + run: | + if [ -f package.json ]; then + VPKG=$(node -pe "require('./package.json').version || ''") + TAG="${GITHUB_REF_NAME#v}" + echo "package.json: $VPKG | tag: $TAG" + [ -n "$VPKG" ] && [ "$VPKG" = "$TAG" ] + else + echo "no package.json -> skip" + fi + + build_publish: + name: Build & publish PyPI (+ attach assets to GitHub Release) + needs: verify + runs-on: ubuntu-latest + environment: pypi + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install build backend + run: python -m pip install --upgrade pip build + + - name: Build sdist & wheel + run: python -m build --sdist --wheel --outdir dist/ + + - name: Publish to PyPI (Trusted Publisher) + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print_hash: true + + - name: Create GitHub Release (auto notes) and upload assets + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: | + dist/*.whl + dist/*.tar.gz + + post_verify: + name: Cross-check PyPI & Release tag + needs: build_publish + runs-on: ubuntu-latest + steps: + - name: Verify published version on PyPI + shell: bash + run: | + TAG="${GITHUB_REF_NAME#v}" + got=$(curl -fsSL "https://pypi.org/pypi/${PKG_NAME}/json" \ + | python -c 'import sys,json; print(json.load(sys.stdin)["info"]["version"])') + echo "PyPI: $got | tag: $TAG" + test "$got" = "$TAG" + + - name: Verify GitHub Release tag exists + shell: bash + run: | + TAG="${GITHUB_REF_NAME}" + gh release view "$TAG" >/dev/null diff --git a/.gitignore b/.gitignore index c26d5af..a6c9418 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ node_modules/ .mypy_cache/ .pytest_cache/ sbom-*.json + +# editor backups +*.yml.bak