mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-10 18:59:56 +00:00
Clean release snapshot
This commit is contained in:
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user