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..3170c7a --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,61 @@ +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: Read version from pyproject + id: ver + shell: bash + run: | + PY_VER=$(python - <<'PY' + import tomllib, sys + 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("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: ${{ 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/') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + print_hash: true 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 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"