mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-14 14:23:14 -06:00
Cutting 1.9.34 produced a release page reading:
### Changes
- chore(release): v1.9.34
- Changelog: one 1.9.34 entry, and credit where it was missing
while the entry describing single sign-on, the removal of the last native image
dependency, three update failures and every outside contributor sat in
CHANGELOG.md and was never published. The notes on the release page are what most
people actually read; they should be the written ones.
The workflow now takes the section for the version being released and uses it as
the body. Commit subjects remain the fallback for a version with no entry, so a
release never publishes with no notes at all — scripts/bump-version.sh already
warns about a missing heading, and this is the same gap showing up downstream.
awk rather than sed for the extraction: the body contains regex metacharacters and
markdown that a sed range would mangle.
Verified against the real file: 1.9.34 extracts 269 lines and stops at the next
heading with all 13 contributor credits intact, 1.9.33 and 1.9.29 extract cleanly,
and a version with no entry yields nothing and takes the fallback.
v1.9.34's notes were corrected by hand after release; this is so the next one does
not need that.
238 lines
10 KiB
YAML
238 lines
10 KiB
YAML
name: Release
|
|
|
|
# Fires when a version tag is pushed (e.g. v1.8.0). Builds + publishes artifacts
|
|
# only - nothing here deploys to production.
|
|
on:
|
|
push:
|
|
tags: ['v*']
|
|
|
|
permissions:
|
|
contents: write # create the GitHub Release
|
|
packages: write # push the image to ghcr.io
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false # never cancel a release mid-publish
|
|
|
|
jobs:
|
|
# Fail-fast: a hand-pushed tag that disagrees with VERSION must not publish
|
|
# anything (the artifacts would report the wrong version). Gates everything.
|
|
verify:
|
|
name: Verify tag matches VERSION
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Assert pushed tag equals VERSION
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME#v}"
|
|
FILE="$(cat VERSION)"
|
|
echo "pushed tag: ${GITHUB_REF_NAME} (stripped: $TAG) VERSION file: $FILE"
|
|
if [ "$TAG" != "$FILE" ]; then
|
|
echo "::error::Tag ${GITHUB_REF_NAME} does not match VERSION ($FILE) - refusing to publish."
|
|
exit 1
|
|
fi
|
|
echo "OK: tag matches VERSION ($FILE)"
|
|
|
|
test:
|
|
name: Tests
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: server
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
cache: npm
|
|
cache-dependency-path: server/package-lock.json
|
|
- run: npm ci
|
|
- run: npm test
|
|
|
|
artifacts:
|
|
name: Tarball + Tizen .wgt + GitHub Release
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0 # full history, for release notes
|
|
|
|
- name: Resolve version + previous tag
|
|
id: ver
|
|
run: |
|
|
VERSION="$(cat VERSION)"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
|
|
PREV="$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)"
|
|
echo "prev=$PREV" >> "$GITHUB_OUTPUT"
|
|
# #80: a version carrying a -suffix (e.g. 1.9.0-rc1) is a pre-release.
|
|
case "$VERSION" in *-*) PRE=true ;; *) PRE=false ;; esac
|
|
echo "prerelease=$PRE" >> "$GITHUB_OUTPUT"
|
|
echo "Releasing ${GITHUB_REF_NAME} (version $VERSION, prerelease=$PRE); previous tag: ${PREV:-<none>}"
|
|
|
|
- name: Build Tizen .wgt (unsigned in CI)
|
|
run: |
|
|
chmod +x tizen/build-wgt.sh
|
|
( cd tizen && ./build-wgt.sh ) # no Tizen CLI on the runner => unsigned zip
|
|
cp tizen/ScreenTinker.wgt ScreenTinker.wgt
|
|
ls -la ScreenTinker.wgt
|
|
|
|
- name: Build BrightSign autorun.zip (single-file player installer)
|
|
run: |
|
|
chmod +x scripts/build-autorun-zip.sh
|
|
./scripts/build-autorun-zip.sh -o autorun.zip
|
|
ls -la autorun.zip
|
|
|
|
- name: Build source tarball (bundles the .wgt; the signed apk is added by scripts/finalize-release.sh)
|
|
run: |
|
|
OUT="screentinker-${{ steps.ver.outputs.version }}.tar.gz"
|
|
tar czf "$OUT" \
|
|
--exclude='node_modules' --exclude='.git' --exclude='.github' \
|
|
--exclude='*.db' --exclude='*.db-wal' --exclude='*.db-shm' --exclude='*.db.*' \
|
|
--exclude='server/uploads' --exclude='server/certs' --exclude='server/test' \
|
|
--exclude='*.apk' \
|
|
server frontend scripts docs VERSION README.md LICENSE .env.example ScreenTinker.wgt brightsign
|
|
echo "TARBALL=$OUT" >> "$GITHUB_ENV"
|
|
ls -la "$OUT"
|
|
|
|
- name: Generate release notes
|
|
run: |
|
|
PREV="${{ steps.ver.outputs.prev }}"
|
|
VERSION="${{ steps.ver.outputs.version }}"
|
|
|
|
# Prefer the hand-written CHANGELOG section for this version.
|
|
#
|
|
# The generated list is commit SUBJECTS, which describe the work, not the release: cutting
|
|
# 1.9.34 produced a page reading "chore(release): v1.9.34" and one changelog commit, while
|
|
# the entry describing single sign-on, the native-dependency removal, the update fixes and
|
|
# every outside contributor sat in CHANGELOG.md and was never published. The notes on the
|
|
# release page are what most people actually read, so they should be the written ones.
|
|
#
|
|
# awk rather than sed: the body contains regex metacharacters and markdown that a sed range
|
|
# would mangle. This takes everything between `## <version>` and the next `## ` heading.
|
|
CHANGELOG_BODY="$(awk -v v="## $VERSION" '
|
|
$0 == v {found=1; next}
|
|
found && /^## / {exit}
|
|
found {print}
|
|
' CHANGELOG.md)"
|
|
|
|
{
|
|
echo "## ScreenTinker ${{ steps.ver.outputs.tag }}"
|
|
echo
|
|
if [ -n "$(printf '%s' "$CHANGELOG_BODY" | tr -d '[:space:]')" ]; then
|
|
echo "$CHANGELOG_BODY"
|
|
else
|
|
# No entry for this version — fall back to commit subjects rather than publish a
|
|
# release with no notes at all. scripts/bump-version.sh already warns when the
|
|
# CHANGELOG has no matching heading; this is the same gap showing up downstream.
|
|
echo "_No CHANGELOG entry for $VERSION; listing commits instead._"
|
|
echo
|
|
echo "### Changes"
|
|
if [ -n "$PREV" ]; then
|
|
git log --no-merges --pretty='- %s' "${PREV}..${{ steps.ver.outputs.tag }}"
|
|
else
|
|
echo "_First tagged release. Most recent changes:_"
|
|
git log --no-merges --pretty='- %s' -n 30 "${{ steps.ver.outputs.tag }}"
|
|
fi
|
|
fi
|
|
echo
|
|
echo "### Artifacts"
|
|
echo "- \`${TARBALL}\` - bundle: server + frontend source + the Tizen .wgt (the signed Android APK is added at the root during release finalization)."
|
|
echo "- \`ScreenTinker.wgt\` - Tizen TV web app, **unsigned - for inspection only**."
|
|
echo " Sign it with your own Samsung certificate (Tizen Studio + a profile that includes"
|
|
echo " your TV's DUID) to install, or - easiest - point a Tizen TV browser / URL Launcher"
|
|
echo " at \`https://<your-instance>/player\` (no signing needed)."
|
|
echo "- \`autorun.zip\` - BrightSign player installer. Drop it on the root of a player's"
|
|
echo " storage (microSD, USB, or internal flash) and power-cycle: it unpacks itself and"
|
|
echo " reboots into the player. Edit \`screentinker.json\` inside the archive first to"
|
|
echo " point it at your own server."
|
|
if [ "${{ steps.ver.outputs.prerelease }}" = "true" ]; then
|
|
echo "- Docker image: \`ghcr.io/screentinker/screentinker:${{ steps.ver.outputs.version }}\` (pre-release - \`:latest\` is NOT moved)."
|
|
else
|
|
echo "- Docker image: \`ghcr.io/screentinker/screentinker:${{ steps.ver.outputs.version }}\` (also \`:latest\`)."
|
|
fi
|
|
echo "- \`ScreenTinker.apk\` - signed Android player (attached during release finalization)."
|
|
} > RELEASE_NOTES.md
|
|
cat RELEASE_NOTES.md
|
|
|
|
- name: Create GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# #80: pre-release tags publish as a GitHub *pre-release* (not "Latest"),
|
|
# which also keeps the /releases/latest API pointing at the last stable.
|
|
PRERELEASE_FLAG=""
|
|
[ "${{ steps.ver.outputs.prerelease }}" = "true" ] && PRERELEASE_FLAG="--prerelease"
|
|
gh release create "${{ steps.ver.outputs.tag }}" \
|
|
$PRERELEASE_FLAG \
|
|
--title "ScreenTinker ${{ steps.ver.outputs.tag }}" \
|
|
--notes-file RELEASE_NOTES.md \
|
|
"${TARBALL}" \
|
|
autorun.zip \
|
|
tizen/ScreenTinker.wgt
|
|
|
|
docker:
|
|
name: Docker image (amd64 + arm64) -> ghcr
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- id: ver
|
|
run: |
|
|
VERSION="$(cat VERSION)"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
# #80: move :latest only for final releases - a pre-release (1.9.0-rc1) must
|
|
# not repoint :latest onto untested code (anyone on :latest pulls it on restart).
|
|
TAGS="ghcr.io/screentinker/screentinker:$VERSION"
|
|
case "$VERSION" in
|
|
*-*) echo "Pre-release $VERSION: :latest will NOT be moved" ;;
|
|
*) TAGS="${TAGS}"$'\n'"ghcr.io/screentinker/screentinker:latest" ;;
|
|
esac
|
|
{ echo "tags<<__EOF__"; printf '%s\n' "$TAGS"; echo "__EOF__"; } >> "$GITHUB_OUTPUT"
|
|
- uses: docker/setup-qemu-action@v3
|
|
- uses: docker/setup-buildx-action@v3
|
|
- uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
# ghcr refused this push on the 1.9.29 release with "denied: permission_denied: Error from
|
|
# intermediary with HTTP status code 403", then accepted the identical build on a manual
|
|
# re-run minutes later. Nothing about the token, the permissions or the workflow changed in
|
|
# between — the registry simply said no once.
|
|
#
|
|
# That is worth one retry rather than a failed release, and it is worst exactly here: the
|
|
# GitHub Release job has already published by this point, so a failure leaves a tag that
|
|
# exists with no image behind it. Anyone deploying from ghcr — alpha, and every self-hoster
|
|
# pulling :latest — sees a version that is announced and unpullable, which reads as a broken
|
|
# release rather than a hiccup at a registry.
|
|
- uses: docker/build-push-action@v6
|
|
id: push
|
|
continue-on-error: true
|
|
with:
|
|
context: .
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.ver.outputs.tags }}
|
|
|
|
- name: Pause before retrying the push
|
|
if: steps.push.outcome == 'failure'
|
|
run: sleep 45
|
|
|
|
# No continue-on-error: a second refusal is a real failure and must fail the release.
|
|
- name: Retry the push
|
|
if: steps.push.outcome == 'failure'
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.ver.outputs.tags }}
|
|
|
|
# TODO (deferred): build + sign the Android APK in CI. Requires the release
|
|
# keystore + passwords as encrypted Actions secrets. For now the maintainer
|
|
# attaches a signed APK out-of-band (and self-hosters mount one at
|
|
# /data/ScreenTinker.apk).
|