Clean release snapshot

This commit is contained in:
byrongamatos
2026-06-16 18:47:13 +02:00
commit 6c110398b4
574 changed files with 162566 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
name: ci
# Runs only as a reusable workflow invoked by ship-ci.yml (for PRs into main
# and release/**). It deliberately has no standalone pull_request trigger: a
# direct run would publish unprefixed "<job>" checks, but the org rulesets
# require the "ci / <job>" names produced when ship-ci.yml calls this workflow.
on:
workflow_call:
permissions:
contents: read
pull-requests: read
checks: read
jobs:
test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Guard against print() / traceback.print_exc() in server.py, lib/, and bundled plugin routes
run: |
# git grep: tracked files only — no .pyc / __pycache__ noise from
# later pytest runs. Covers both audited patterns from #155 / #242.
# `(^|[^A-Za-z0-9_])` anchor avoids matching suffixes like `myprint(`;
# POSIX leaves `\b` undefined, so we use an explicit character class.
hits=$(git grep -nE '(^|[^A-Za-z0-9_])(print|traceback\.print_exc)[[:space:]]*\(' \
-- server.py lib/ \
$(git ls-files 'plugins/*/routes.py') || true)
if [ -n "$hits" ]; then
echo "$hits"
first=$(printf '%s\n' "$hits" | head -n1)
file=$(printf '%s' "$first" | cut -d: -f1)
line=$(printf '%s' "$first" | cut -d: -f2)
echo "::error file=${file},line=${line}::print() or traceback.print_exc() found in server.py, lib/, or a bundled plugin routes.py. Use the slopsmith logger (lib/logging_setup.py) — see issues #155 / #242."
exit 1
fi
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-test.txt
- name: Run pytest
run: pytest
- name: Run JS plugin-API tests
run: node --test tests/js/*.test.js 'tests/plugins/*/js/*.test.js'
tailwind-fresh:
# Guard that the committed static/tailwind.min.css is in sync with source.
# The Play CDN's runtime JIT was removed (slopsmith-desktop#110); a prebuilt
# stylesheet only contains classes the scanner saw at build time, so stale
# CSS silently ships unstyled elements. Rebuild and fail on any diff.
name: tailwind-fresh
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Rebuild Tailwind CSS
run: bash scripts/build-tailwind.sh
- name: Verify committed static/tailwind.min.css is fresh
run: |
# Hard-fail (matches the print() guard convention) — do NOT auto-commit.
if ! git diff --quiet -- static/tailwind.min.css; then
echo "::error file=static/tailwind.min.css::static/tailwind.min.css is stale. Run 'bash scripts/build-tailwind.sh' and commit the regenerated file."
git diff -- static/tailwind.min.css
exit 1
fi
manifest-validation:
name: manifest-validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Validate plugin manifests
run: |
python - <<'EOF'
import json, sys
from pathlib import Path
errors = []
manifests = sorted(Path("plugins").glob("*/plugin.json"))
for manifest in manifests:
try:
data = json.loads(manifest.read_text())
except json.JSONDecodeError as e:
errors.append(f"{manifest}: invalid JSON — {e}")
continue
for field in ("id", "name"):
if field not in data:
errors.append(f"{manifest}: missing required field '{field}'")
pid = data.get("id", "")
if pid and pid != pid.lower():
errors.append(f"{manifest}: 'id' must be lowercase, got '{pid}'")
# The plugin loader treats each plugins/<dir> as a Python module,
# so the manifest 'id' must match its directory name.
dirname = manifest.parent.name
if pid and pid != dirname:
errors.append(f"{manifest}: 'id' ({pid!r}) must match directory name ({dirname!r})")
if errors:
for e in errors:
print(f"::error::{e}")
sys.exit(1)
print(f"Validated {len(manifests)} manifest(s) — OK")
EOF
+70
View File
@@ -0,0 +1,70 @@
name: Nightly
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
setup:
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.branch.outputs.branch }}
date: ${{ steps.date.outputs.date }}
steps:
- name: Find active release branch
id: branch
env:
GH_TOKEN: ${{ github.token }}
run: |
branch=$(gh api "repos/${{ github.repository }}/git/matching-refs/heads/release/v" \
--jq '[.[].ref | ltrimstr("refs/heads/")] | map(ltrimstr("refs/heads/")) | .[]' \
| sort -V | tail -1 || true)
if [[ -z "$branch" ]]; then
branch="main"
fi
echo "branch=$branch" >> "$GITHUB_OUTPUT"
echo "Active branch: $branch"
- name: Get date
id: date
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
build-docker:
needs: setup
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.setup.outputs.branch }}
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/slopsmith/slopsmith:nightly
ghcr.io/slopsmith/slopsmith:nightly-${{ needs.setup.outputs.date }}
cache-from: type=gha
cache-to: type=gha,mode=max
+50
View File
@@ -0,0 +1,50 @@
name: release
on:
push:
tags: ['v*']
permissions:
contents: read
packages: write
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: meta
run: |
tag="${GITHUB_REF_NAME}" # e.g. v0.3.0 or v0.3.0-beta.1
version="${tag#v}" # strip leading v
echo "version=$version" >> "$GITHUB_OUTPUT"
# Emit the full tag list here so the build step never receives a
# blank tag line (the previous inline `… || ''` conditional left an
# empty entry for pre-release tags). :latest is added only for
# stable releases (no pre-release suffix).
{
echo "tags<<TAGS_EOF"
echo "ghcr.io/${GITHUB_REPOSITORY}:${version}"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "ghcr.io/${GITHUB_REPOSITORY}:latest"
fi
echo "TAGS_EOF"
} >> "$GITHUB_OUTPUT"
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
+19
View File
@@ -0,0 +1,19 @@
name: ship-ci
# PRs into both main and release/** run CI through this wrapper so the check
# runs are named "ci / <job>" (reusable-workflow caller prefix), matching the
# org rulesets' required contexts. ci.yml itself only triggers via
# workflow_call — it never runs standalone, which would emit unprefixed
# "<job>" checks that the rulesets can't match.
on:
pull_request:
branches: [main, 'release/**']
permissions:
contents: read
pull-requests: read
checks: read
jobs:
ci:
uses: ./.github/workflows/ci.yml
+98
View File
@@ -0,0 +1,98 @@
name: Sync VERSION from desktop release
# Updates the VERSION file in this repo whenever slopsmith-desktop
# publishes a new tagged release. slopsmith-desktop's build.yml
# dispatches the `desktop-released` event at the end of a successful
# tag build (see docs in CLAUDE.md). A `workflow_dispatch` trigger is
# kept for manual testing / recovery.
#
# Related issue: #81.
on:
repository_dispatch:
types: [desktop-released]
workflow_dispatch:
inputs:
version:
description: 'Version to sync (vX.Y.Z or X.Y.Z)'
required: true
permissions:
contents: write
# Serialize runs so a rapid-fire pair of dispatches can't produce a
# non-fast-forward push race. Later runs queue behind earlier ones.
concurrency:
group: sync-version
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
steps:
# Always operate on main regardless of trigger branch. repository_dispatch
# already runs against the default branch, but workflow_dispatch can be
# launched from any branch in the UI — pinning ref: main keeps both
# paths committing to the same place.
- uses: actions/checkout@v4
with:
ref: main
- name: Determine target version
id: v
# Pass untrusted payloads through env vars instead of ${{ }}
# expansion inside the shell body — inline expansion makes the
# script vulnerable to command injection if a dispatch client
# sent a value like `$(...)` or a quote break.
env:
RAW_DISPATCH: ${{ github.event.client_payload.version }}
RAW_INPUT: ${{ inputs.version }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
raw="$RAW_INPUT"
else
raw="$RAW_DISPATCH"
fi
# Accept both "vX.Y.Z" and "X.Y.Z" on the wire.
version="${raw#v}"
# Anchored semver guard — rejects payloads like
# "soundfonts-v1" (seen in the desktop release list) or any
# stray text. The emitter side validates too, but we don't
# trust cross-repo inputs.
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Invalid version payload: '$raw'"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Compare with current VERSION
id: cmp
env:
TARGET: ${{ steps.v.outputs.version }}
run: |
current=$(tr -d '[:space:]' < VERSION)
if [ "$current" = "$TARGET" ]; then
echo "No change (already at $current)."
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "Bumping $current -> $TARGET."
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "previous=$current" >> "$GITHUB_OUTPUT"
fi
- name: Commit and push
if: steps.cmp.outputs.changed == 'true'
env:
TARGET: ${{ steps.v.outputs.version }}
run: |
printf '%s\n' "$TARGET" > VERSION
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git add VERSION
git commit -m "chore: sync VERSION to $TARGET (desktop release)"
# Explicit HEAD:main push — if workflow_dispatch was somehow
# launched with ref: main overridden in the UI, this still
# lands on main rather than pushing to whatever the tracking
# branch was.
git push origin HEAD:main