Skip to content
Merged
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
14 changes: 13 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
target-branch: "main"
commit-message:
prefix: "chore(deps)"
groups:
github-actions:
patterns:
- "*"
- package-ecosystem: "pip"
directory: "/scripts/site"
schedule:
interval: "weekly"
target-branch: "main"
commit-message:
prefix: "chore(deps)"
groups:
site-build:
patterns:
- "*"
59 changes: 59 additions & 0 deletions .github/scripts/release-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Release gate: exit non-zero unless CI evidence for $SHA is green.
#
# 1. Validate (push event) finished and succeeded on exactly this SHA.
# 2. If the SHA was merged from a PR, every check on that PR passed or was
# skipped. Blender Smoke runs on PRs only, so this is its evidence.
#
# Env: GH_TOKEN, SHA, GITHUB_REPOSITORY. Optional: GATE_EVENT (default push),
# GATE_TIMEOUT seconds (default 1500), GATE_POLL seconds (default 20).
set -euo pipefail

repo="${GITHUB_REPOSITORY:?}"
sha="${SHA:?}"
event="${GATE_EVENT:-push}"
deadline=$((SECONDS + ${GATE_TIMEOUT:-1500}))

while :; do
read -r status conclusion < <(
gh run list --repo "$repo" --workflow validate.yml --commit "$sha" \
--event "$event" --limit 1 --json status,conclusion \
--jq '.[0] // {} | "\(.status // "none") \(.conclusion // "none")"'
)
[ "$status" = "completed" ] && break
if [ "$SECONDS" -ge "$deadline" ]; then
echo "::error::Validate did not finish for $sha (last status: $status)"
exit 1
fi
echo "Validate status for $sha: $status; waiting"
sleep "${GATE_POLL:-20}"
done

if [ "$conclusion" != "success" ]; then
echo "::error::Validate concluded '$conclusion' for $sha; not releasing"
exit 1
fi
echo "Validate: success"

pr=$(gh api "repos/$repo/commits/$sha/pulls" --jq '.[0].number // empty')
if [ -z "$pr" ]; then
echo "No PR is associated with $sha (direct push); Validate alone gates this release"
exit 0
fi

# gh exits non-zero while checks are pending or failing even with --json, so
# judge the JSON itself (gh's built-in --jq, no jq binary needed) and treat
# unreadable output as a failed gate.
count=$(gh pr checks "$pr" --repo "$repo" --json name --jq 'length' 2>/dev/null) || true
case "$count" in
''|*[!0-9]*|0)
echo "::error::could not read checks for PR #$pr; not releasing"
exit 1
;;
esac
bad=$(gh pr checks "$pr" --repo "$repo" --json name,bucket --jq '[.[] | select(.bucket != "pass" and .bucket != "skipping") | "\(.name)=\(.bucket)"] | join(", ")' 2>/dev/null) || true
if [ -n "$bad" ]; then
echo "::error::PR #$pr has checks that did not pass: $bad; not releasing"
exit 1
fi
echo "PR #$pr: $count checks passed or were skipped"
2 changes: 1 addition & 1 deletion .github/workflows/blender-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
matrix:
series: ${{ fromJSON(needs.resolve-matrix.outputs.series) }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Harness protocol unit tests
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/drift-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: TMHSDigital/Developer-Tools-Directory/.github/actions/drift-check@v1.15
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: TMHSDigital/Developer-Tools-Directory/.github/actions/drift-check@9be79799df00ed42a0c4cff39e74a383a493296c # v1.15
with:
mode: self
format: gh-summary
2 changes: 1 addition & 1 deletion .github/workflows/label-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
name: Auto-label by path
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Get changed files
id: changed
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
# Full history: the landing page's "Recently added" dates come from
# the commit that first added each example (a shallow clone hides it).
fetch-depth: 0

- uses: actions/setup-python@v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
cache: pip
Expand All @@ -92,7 +92,7 @@ jobs:
- name: Check every internal link, anchor and image alt
run: python tests/check_site_links.py

- uses: actions/configure-pages@v6
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6

- name: Stage the public site (leave out internal docs and unlinked sheets)
# Everything stays in the repo; only the Pages artifact shrinks. No
Expand All @@ -103,9 +103,9 @@ jobs:
rm -rf _site/gallery/contact-sheets _site/gallery/asset-sheets _site/gallery/DESIGN_NOTES.md
rm -f _site/*.md

- uses: actions/upload-pages-artifact@v5
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: _site

- uses: actions/deploy-pages@v5
- uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
id: deployment
66 changes: 57 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,45 @@ on:
workflow_dispatch: {}

permissions:
contents: write
actions: write
contents: read

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

jobs:
gate:
name: Gate on CI evidence
# A push to main must not publish unless Validate passed on this exact SHA and,
# when it was merged from a PR, every check on that PR passed (Blender Smoke
# runs on PRs only). Direct pushes have no PR; Validate alone gates them.
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
permissions:
contents: read
actions: read
checks: read
pull-requests: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
sparse-checkout: .github/scripts
- name: Require green Validate and PR checks
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: bash .github/scripts/release-gate.sh

version-and-release:
name: Bump version, tag, and release
needs: gate
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
permissions:
contents: write
actions: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -117,7 +142,7 @@ jobs:

- name: Sync release docs
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
uses: TMHSDigital/Developer-Tools-Directory/.github/actions/release-doc-sync@v1
uses: TMHSDigital/Developer-Tools-Directory/.github/actions/release-doc-sync@7886cbe6ef93d57cc73c020b98268fa0dc0bdc98 # v1
with:
plugin-version: ${{ steps.new.outputs.version }}
previous-version: ${{ steps.current.outputs.version }}
Expand Down Expand Up @@ -156,20 +181,43 @@ jobs:
PYEOF

- name: Commit version bump
id: commit
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A

# Another PR may have merged after this run checked out (#226). The
# queued run for the newer SHA scans the whole range since the last
# tag and releases everything, so stand down cleanly instead of
# failing with a non-fast-forward push.
git fetch -q origin main
if [ "$(git rev-parse origin/main)" != "$(git rev-parse HEAD)" ]; then
echo "::notice::origin/main moved past $GITHUB_SHA; the newer release run will publish this range"
echo "stale=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# Explicit paths only: the files this workflow owns, never `git add -A`.
git add -- VERSION CHANGELOG.md CLAUDE.md ROADMAP.md \
.cursor-plugin/plugin.json .claude-plugin/plugin.json .claude-plugin/marketplace.json
if [ -n "$(git status --porcelain)" ]; then
echo "::warning::files changed that the release commit does not own:"
git status --porcelain
fi
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -s -m "chore: bump version to ${{ steps.new.outputs.version }} [skip ci]"
git push origin main
if ! git push origin main; then
echo "::notice::push rejected (main moved); the newer release run will publish this range"
echo "stale=true" >> "$GITHUB_OUTPUT"
exit 0
fi
fi

- name: Create and push tag
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true' && steps.commit.outputs.stale != 'true'
run: |
new_version="${{ steps.new.outputs.version }}"
IFS='.' read -r major minor _patch <<< "$new_version"
Expand All @@ -183,7 +231,7 @@ jobs:
git push origin "v$major.$minor" --force

- name: Create GitHub Release
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true' && steps.commit.outputs.stale != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand All @@ -192,7 +240,7 @@ jobs:
--generate-notes

- name: Dispatch Pages
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true'
if: steps.check.outputs.skip == 'false' && steps.bump.outputs.release == 'true' && steps.commit.outputs.stale != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run pages.yml --ref main
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10
with:
stale-issue-message: "This issue has been automatically marked as stale due to inactivity. It will be closed in 7 days if no further activity occurs."
stale-pr-message: "This PR has been automatically marked as stale due to inactivity. It will be closed in 7 days if no further activity occurs."
Expand Down
24 changes: 17 additions & 7 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
name: Validate structure and frontmatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check referenced paths exist
run: |
Expand Down Expand Up @@ -192,9 +192,9 @@ jobs:
# so a template error or a dead link otherwise surfaces in production.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- uses: actions/setup-python@v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.12"
cache: pip
Expand All @@ -212,7 +212,7 @@ jobs:
name: Validate plugin manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check plugin.json matches filesystem and VERSION
run: |
Expand Down Expand Up @@ -281,7 +281,7 @@ jobs:
name: Validate Claude Code packaging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check .claude-plugin manifests match VERSION and the skills on disk
run: |
Expand Down Expand Up @@ -323,6 +323,16 @@ jobs:
print('Claude Code packaging verified')
PYEOF

- name: Check every workflow action is pinned to a full commit SHA
run: |
bad=$(grep -rnE '^\s*(-\s+)?uses:\s+[^./ ]' .github/workflows \
| grep -vE 'uses:\s+\S+@[0-9a-f]{40}(\s|$)' || true)
if [ -n "$bad" ]; then
echo "$bad"
echo "::error::pin these actions to a full commit SHA with a trailing '# vX.Y' comment (#306)"
exit 1
fi

- name: Check CLAUDE.md carries no personal plugin routing block
run: |
if grep -nE 'context-mode|MANDATORY routing rules|ctx_(execute|batch_execute|search|fetch_and_index)' CLAUDE.md; then
Expand All @@ -337,7 +347,7 @@ jobs:
name: Validate content counts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check content counts match README
env:
Expand Down Expand Up @@ -440,7 +450,7 @@ jobs:
name: Validate smoke harness protocol
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Harness unit tests
run: python3 tests/smoke/test_harness.py -v
Loading