Skip to content
Open
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
89 changes: 43 additions & 46 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,69 @@
## Description

<!-- Provide a clear and concise description of what this PR does. -->
<!-- What this PR does and why. One paragraph is fine. -->

## Type of Change

<!-- Mark the relevant option(s) with an 'x' -->
<!-- Keep ONE line below; delete the others. Must match the conventional-commit type. -->

- [ ] `feat` -- New feature
- [ ] `fix` -- Bug fix
- [ ] `docs` -- Documentation only
- [ ] `chore` -- Maintenance task (deps, config, etc.)
- [ ] `refactor` -- Code restructuring (no behavior change)
- [ ] `test` -- Adding or updating tests
- [ ] `ci` -- CI/CD pipeline changes
- [ ] `build` -- Build system or dependency changes
- [ ] `revert` -- Reverts a previous commit
- [ ] `style` -- Code style (formatting, whitespace)
- [ ] `feat` — New feature (minor version bump)
- [ ] `fix` — Bug fix (patch version bump)
- [ ] `feat!` / `fix!` / `BREAKING CHANGE:` — Breaking change (major version bump)
- [ ] `docs` — Documentation only (no release)
- [ ] `chore` / `refactor` / `test` / `ci` / `build` / `style` — No release

### Modifiers
## Required

- [ ] Breaking change (`!`) -- This change breaks backward compatibility
<!-- All boxes below MUST be checked. CI fails the PR otherwise. -->

## Changes Made
- [ ] Tests pass locally (`uv run pytest`)
- [ ] Self-reviewed the diff
- [ ] No new warnings or errors in the changed code
- [ ] Linked to an issue in the `Refs:` line at the bottom (or explained why none)

<!-- Describe the changes in detail -->
## If Applicable

## Changelog Entry

<!-- Paste the exact entry you added to CHANGELOG.md under ## Unreleased.
If no changelog update is needed, write "No changelog needed" and explain why.
Example:
### Added
- **SSH agent forwarding** ([#42](https://github.com/vig-os/devcontainer/issues/42))
- Forward host SSH agent into devcontainer for seamless git authentication
<!--
DELETE the entire section(s) below that don't apply.
Any unchecked box left in the body will fail the PR Hygiene check.
-->

## Testing
### Documentation

- [ ] Updated `docs/templates/` and ran `just docs`
- [ ] Updated `CHANGELOG.md` under `## Unreleased` (release-please will move it to a versioned section on release)
- [ ] Updated `README.md` if user-facing API changed

### Tests

- [ ] Added new tests covering the change
- [ ] Manual testing performed (steps in **Manual Testing Details** below)

#### Manual Testing Details

<!-- Describe the tests you ran and how to verify your changes -->
- [ ] Tests pass locally (`just test`)
- [ ] Manual testing performed (describe below)
<!-- Keep this section only if the box above is checked. Steps to reproduce. -->

### Manual Testing Details
### Dependencies

<!-- If applicable, describe manual testing steps -->
- [ ] Updated `pyproject.toml` and re-locked (`uv lock`)
- [ ] Updated `mat-rs/Cargo.toml` and re-locked (`cargo update`)
- [ ] Verified no breakage with downstream consumers (build123d, ocp_vscode, mat-vis-client)

## Checklist
### Breaking Change

<!-- Mark completed items with an 'x' -->
- [ ] My code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have updated the documentation accordingly (edit `docs/templates/`, then run `just docs`)
- [ ] I have updated `CHANGELOG.md` in the `[Unreleased]` section (and pasted the entry above)
- [ ] My changes generate no new warnings or errors
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published
- [ ] Migration notes added to `docs/migration/`
- [ ] `BREAKING CHANGE:` footer in commit message body
- [ ] Open issue / PR coordination with known downstream consumers

## Additional Notes

<!-- Any additional information, screenshots, or context that reviewers should know -->
<!-- Screenshots, design rationale, links — anything that helps the reviewer. -->

Refs:

<!--
Please include GitHub issue references in the "Refs:" line above (e.g., `Refs: #42`).
Every change must be traceable to an issue, per project rules.
If not related to a specific issue, explain why (chore/documentation only).
See [commit-messages.mdc](../../rules/commit-messages.mdc) for more details.
Required: link a GitHub issue (e.g., `Refs: #42`).
If there's no related issue, replace with a one-line explanation
(e.g., `Refs: N/A — pure CI tooling change`). The commit-msg hook
enforces a `Refs:` line; see docs/COMMIT_MESSAGE_STANDARD.md.
-->
31 changes: 30 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,29 @@ jobs:
with:
fail-on-severity: high

pr-hygiene:
# Enforces the PR template checklist. Any unchecked `- [ ]` left in the
# PR body fails the build. The template is designed for the user to
# DELETE sections that don't apply (see .github/pull_request_template.md);
# leaving them unchecked is treated as "you forgot", not "N/A".
#
# Skipped for release-please bot PRs because the bot owns the body and the
# release-reviewer checklist embedded there is for human reviewers, not for
# CI to gate (the gate is the human merging the PR).
name: PR Hygiene
runs-on: ubuntu-22.04
timeout-minutes: 5
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.user.login != 'github-actions[bot]' &&
!startsWith(github.head_ref, 'release-please--')

steps:
- name: Require checklist items
uses: mheap/require-checklist-action@46d2ca1a0f90144bd081fd13a80b1dc581759365 # v2.5.0
with:
requireChecklist: true

rust:
name: Rust (mat-rs)
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -243,7 +266,7 @@ jobs:
name: CI Summary
runs-on: ubuntu-22.04
timeout-minutes: 5
needs: [lint, test, security, dependency-review, rust]
needs: [lint, test, security, dependency-review, pr-hygiene, rust]
if: always()

steps:
Expand All @@ -256,6 +279,7 @@ jobs:
echo "Test: ${{ needs.test.result }}"
echo "Security: ${{ needs.security.result }}"
echo "Dependency Review: ${{ needs.dependency-review.result }}"
echo "PR Hygiene: ${{ needs.pr-hygiene.result }}"
echo "Rust (mat-rs): ${{ needs.rust.result }}"
echo ""

Expand All @@ -281,6 +305,11 @@ jobs:
FAILED=true
fi

if [ "${{ needs.pr-hygiene.result }}" = "failure" ]; then
echo "ERROR: PR Hygiene failed (unchecked checklist items in PR body)"
FAILED=true
fi

if [ "${{ needs.rust.result }}" = "failure" ]; then
echo "ERROR: Rust checks failed"
FAILED=true
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Release Please
#
# Drives the release pipeline from conventional commits.
#
# On every push to main, release-please scans commits since the last tag and
# either:
# 1. Updates / opens a per-package "Release PR" (chore(main): release X.Y.Z)
# that bumps versions + appends to CHANGELOG.md.
# 2. If the just-merged commit IS a Release PR, pushes the tag — which fires
# release.yml (Python → PyPI) or release-rs-materials.yml (Rust →
# crates.io). Release-please also creates the GitHub Release with notes
# generated from the conventional-commit history.
#
# Tag formats (intentional, match existing history):
# - py-materials → vX.Y.Z → triggers release.yml
# - rs-materials → rs-materials/vX.Y.Z → triggers release-rs-materials.yml
#
# Auth: uses the RELEASE_APP GitHub App so the tag push it performs triggers
# downstream workflows. Tags pushed by GITHUB_TOKEN are intentionally inert
# under GitHub's recursion protection — that would silently break PyPI/crates
# publishing.
#
# Triggers: push to main · workflow_dispatch
#
# Docs: https://github.com/googleapis/release-please

name: release-please

on: # yamllint disable-line rule:truthy
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-please
cancel-in-progress: false

jobs:
release-please:
name: Release Please
runs-on: ubuntu-22.04
timeout-minutes: 5
permissions:
contents: write
pull-requests: write

steps:
- name: Generate Release App Token
id: release-app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Run release-please
uses: googleapis/release-please-action@8b8fd2cc23b2e18957157a9d923d75aa0c6f6ad5 # v4.4.1
with:
token: ${{ steps.release-app-token.outputs.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
23 changes: 10 additions & 13 deletions .github/workflows/release-rs-materials.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
name: Release rs-materials

# Triggered by tags pushed by release-please (release-please.yml).
# Tests + publishes the Rust crate to crates.io. The GitHub Release is created
# by release-please — do not duplicate it here.

on: # yamllint disable-line rule:truthy
push:
tags: ["rs-materials/v*"]

permissions:
contents: write
contents: read

jobs:
test:
Expand All @@ -19,22 +23,15 @@ jobs:
publish-crates-io:
needs: test
runs-on: ubuntu-latest
# Gated until `CARGO_REGISTRY_TOKEN` is configured and downstream consumers
# are coordinated. Flip on via:
# gh variable set CRATES_IO_PUBLISH_ENABLED --body true
# Tracking issue: MorePET/mat (search label:release-infra)
if: vars.CRATES_IO_PUBLISH_ENABLED == 'true'
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Publish to crates.io
run: cargo publish --manifest-path mat-rs/Cargo.toml --allow-dirty
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

github-release:
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
24 changes: 6 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
name: Release

# Triggered by tags pushed by release-please (release-please.yml).
# Builds the wheel + sdist and publishes to PyPI via trusted publishing (OIDC).
# The GitHub Release itself is created by release-please — do not duplicate it
# here.

on: # yamllint disable-line rule:truthy
push:
tags: ["v*"]

permissions:
contents: write
contents: read

jobs:
build:
Expand Down Expand Up @@ -38,20 +43,3 @@ jobs:
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

github-release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: true
4 changes: 4 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
".": "3.0.0",
"mat-rs": "0.2.0"
}
46 changes: 20 additions & 26 deletions RELEASE_PROCESS.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
# Release Process for mat

## Creating a New Release
Releases are driven by [release-please](https://github.com/googleapis/release-please) — see `.github/workflows/release-please.yml`. **Do not bump versions or push tags by hand.**

When releasing a new version of mat:
## How a release happens

1. **Update version numbers:**
1. Land conventional commits on `dev` (`feat:`, `fix:`, `feat!:`, etc. — the commit-msg hook enforces the format).
2. Open a `release/x.y.z → main` PR (manual gate; CI runs the full check matrix).
3. Merge to `main`. On every push to main, release-please re-evaluates the commit history since the last tag and (per package) opens or updates a **Release PR** titled `chore(main): release X.Y.Z`. The PR bumps `pyproject.toml` + `src/pymat/__init__.py` (Python) or `mat-rs/Cargo.toml` (Rust) and prepends a CHANGELOG section generated from commits.
4. Review the Release PR — version computation:
- `feat:` → minor bump
- `fix:` → patch bump
- `feat!:` or `BREAKING CHANGE:` footer → major bump
- `chore:`, `docs:`, `ci:`, `test:`, `style:` → no release
5. Merge the Release PR. Release-please pushes the tag (`vX.Y.Z` for Python, `rs-materials/vX.Y.Z` for Rust). The tag push triggers `release.yml` (PyPI) or `release-rs-materials.yml` (crates.io). Release-please also creates the GitHub Release with auto-generated notes.

```bash
cd /Users/larsgerchow/Projects/mat
## Two independent packages

# Edit version in:
# - pyproject.toml
# - src/pymat/__init__.py
```
Each package has its own Release PR, version, and tag:

2. **Commit and tag:**
| Package | Path | Tag format | Publishes to |
|----------------|----------|------------------------|--------------|
| `py-materials` | `.` | `vX.Y.Z` | PyPI |
| `rs-materials` | `mat-rs` | `rs-materials/vX.Y.Z` | crates.io |

```bash
git add -A
git commit -m "Release vX.Y.Z - Description"
git tag -a vX.Y.Z -m "Release vX.Y.Z"
```
A `feat:` touching only `mat-rs/**` triggers a Rust Release PR; a `feat:` touching `src/pymat/**` triggers a Python Release PR. Commits affecting both produce two Release PRs.

3. **Update the 'latest' tag (force overwrite):**
## Auth

```bash
git tag -f -a latest -m "Latest release"
```

4. **Push everything:**

```bash
git push origin main --tags
git push -f origin latest # Force push to update 'latest' tag
```
`release-please.yml` uses the `RELEASE_APP` GitHub App (same App used by `sync-main-to-dev.yml`). This is required — tags pushed by `GITHUB_TOKEN` are inert under GitHub's recursion-protection and would silently skip the publish workflows.

## Why This Works

Expand Down
Loading
Loading