The Discord announcement now carries a star ask under the download link, with the reason. The release page's ask opens on a heart and closes on a thank-you. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
326 lines
13 KiB
YAML
326 lines
13 KiB
YAML
# Puts the downloadable builds on the repository's Releases page.
|
|
#
|
|
# Nothing here leaves GitHub: a release is exactly as visible as the repository it sits
|
|
# on. This is *not* the PyPI publish — that is `release.yml`, it is a separate act, and it
|
|
# does not run from here.
|
|
#
|
|
# Triggered by pushing a version tag, which is the moment a release exists in git anyway:
|
|
#
|
|
# git tag -a v0.3.0 -m "MeshTerm 0.3.0"
|
|
# git push origin v0.3.0
|
|
#
|
|
# The binaries are built by `installers.yml`, called rather than copied, so the files
|
|
# people download are made exactly the way the ones CI checks on every push are.
|
|
#
|
|
# Once the release exists, `website` puts it on meshterm.net: it rebuilds that site from
|
|
# the tagged tree — the landing and About pages carry the version — and pushes the result
|
|
# to jpmartineau/meshterm.net, which GitHub Pages serves. It writes with a deploy key
|
|
# scoped to that one repository (secret SITE_DEPLOY_KEY); that repo's README makes one.
|
|
name: GitHub release
|
|
|
|
on:
|
|
push:
|
|
tags: ["v*"]
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Existing tag to build a release from (e.g. v0.2.0)"
|
|
required: true
|
|
type: string
|
|
prerelease:
|
|
description: "Mark as a pre-release (keeps it out of 'Latest')"
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build:
|
|
uses: ./.github/workflows/installers.yml
|
|
|
|
publish:
|
|
name: create the release
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
# The one job that needs it: creating a release writes to the repository.
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.tag || github.ref }}
|
|
|
|
# Every platform's artifact, flattened into one directory of files to attach.
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
# The build uploads one artifact nobody should ever download: the unsigned
|
|
# Windows binary it hands to SignPath. Everything meant for the release page is
|
|
# named `meshterm-…`, so the pattern is the filter.
|
|
pattern: meshterm-*
|
|
path: artifacts
|
|
merge-multiple: true
|
|
|
|
- name: Gather the binaries and their checksums
|
|
id: gather
|
|
shell: bash
|
|
run: |
|
|
tag="${{ inputs.tag || github.ref_name }}"
|
|
version="${tag#v}"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
mkdir -p release
|
|
find artifacts -type f -exec mv {} release/ \;
|
|
ls -lh release/
|
|
cd release
|
|
|
|
# A second, version-less copy of every binary. GitHub's evergreen download URL,
|
|
# `releases/latest/download/<name>`, resolves against an exact asset name and
|
|
# nothing else — so a link that always fetches the newest build needs a name
|
|
# that never moves. That is what lets the README, the website and anyone's
|
|
# install script say `meshterm-macos-arm64` once instead of naming a version
|
|
# that goes stale the moment the next release lands. The version-stamped file
|
|
# stays beside it: it is the one you want on disk when you keep several builds
|
|
# around, and it is what the release-notes install block points at, since that
|
|
# page is about this release rather than the latest one.
|
|
made=0
|
|
for versioned in "meshterm-$version"-*; do
|
|
case "$versioned" in *.sha256) continue ;; esac
|
|
[ -e "$versioned" ] || continue
|
|
stable="meshterm-${versioned#meshterm-$version-}"
|
|
cp "$versioned" "$stable"
|
|
sha256sum "$stable" > "$stable.sha256"
|
|
made=$((made + 1))
|
|
done
|
|
# Nothing matched means the tag and `meshterm.__version__` disagree, which is a
|
|
# broken release either way: the notes below are about to name files by version
|
|
# that the build did not produce. Fail here, where it is one line to read.
|
|
if [ "$made" -eq 0 ]; then
|
|
echo "::error::No binary named meshterm-$version-*; $tag and meshterm.__version__ disagree."
|
|
exit 1
|
|
fi
|
|
echo "$made version-less aliases made"
|
|
|
|
# One combined checksum file is easier to verify against than ten loose ones.
|
|
cat *.sha256 > SHA256SUMS && rm -f *.sha256
|
|
|
|
- name: Take the release notes from the changelog
|
|
id: notes
|
|
shell: bash
|
|
env:
|
|
VERSION: ${{ steps.gather.outputs.version }}
|
|
run: |
|
|
# The section for this version, between its own heading and the next one. If the
|
|
# changelog has nothing for it, say so plainly rather than inventing notes.
|
|
#
|
|
# Extracted once, into the file, and the *file* is what gets tested. It used to
|
|
# run twice — once down a `| sed | head -1 | grep -q .` pipeline to decide, then
|
|
# again for real — and this step runs under `bash -e -o pipefail`, where that
|
|
# pipeline is a race it can lose. `head -1` exits the moment it has its line and
|
|
# closes the pipe; if `sed` still has bytes to write it dies on SIGPIPE, pipefail
|
|
# promotes that to a failed condition, and a version with a perfectly good
|
|
# changelog section publishes "No changelog entry" instead. It only bites once a
|
|
# section outgrows one buffered write, which is why every entry under 4 KB was
|
|
# fine and 0.3.1 was not — while 0.3.0, the same size, happened to win.
|
|
awk -v v="$VERSION" '
|
|
$0 ~ "^## \\[" v "\\]" {found=1; next}
|
|
found && /^## \[/ {exit}
|
|
found && /^\[[^]]+\]: / {next}
|
|
found {print}
|
|
' CHANGELOG.md > notes.md
|
|
if ! grep -q '[^[:space:]]' notes.md; then
|
|
echo "No changelog entry for $VERSION." > notes.md
|
|
fi
|
|
echo "--- notes ---"; cat notes.md
|
|
|
|
# Most people who open a release page came to install the thing, so the commands go
|
|
# above the changelog rather than in a link to a README that names some other
|
|
# version. Written here rather than kept as prose anywhere means they can only ever
|
|
# name *this* release: the tag interpolates, so the URLs are the assets attached to
|
|
# the very page they are printed on, and there is no example version to go stale.
|
|
#
|
|
# The heredoc is quoted, so the markdown's own backticks stay backticks instead of
|
|
# becoming command substitution, and the two placeholders are filled by `sed`
|
|
# afterwards. Neither a tag nor a version can contain a slash, so the substitution
|
|
# needs no escaping.
|
|
- name: Open the notes with this release's install commands
|
|
shell: bash
|
|
env:
|
|
TAG: ${{ steps.gather.outputs.tag }}
|
|
VERSION: ${{ steps.gather.outputs.version }}
|
|
run: |
|
|
cat > install.md.in <<'INSTALL_MD'
|
|
## Install
|
|
|
|
One file, no Python needed.
|
|
|
|
**macOS** — paste these into Terminal. Use `curl`, **not your browser**: a browser
|
|
flags the download and macOS then refuses to open it at all, and on Sequoia and
|
|
later right-click → Open no longer gets you past it. `curl` flags nothing, so it
|
|
simply runs.
|
|
|
|
```bash
|
|
curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/download/@TAG@/meshterm-@VERSION@-macos-arm64
|
|
chmod +x meshterm
|
|
./meshterm
|
|
```
|
|
|
|
That is the build for Apple silicon Macs (M1 and later). On an Intel Mac, swap
|
|
`arm64` for `x64`.
|
|
|
|
**Linux** — the same three lines with `linux-x64`, or `linux-arm64` on a uConsole
|
|
or another 64-bit ARM handheld.
|
|
|
|
```bash
|
|
curl -fL -o meshterm https://github.com/jpmartineau/MeshTerm/releases/download/@TAG@/meshterm-@VERSION@-linux-x64
|
|
chmod +x meshterm
|
|
./meshterm
|
|
```
|
|
|
|
**Windows** — download [meshterm-@VERSION@-windows-x64.exe](https://github.com/jpmartineau/MeshTerm/releases/download/@TAG@/meshterm-@VERSION@-windows-x64.exe), then run it from Windows
|
|
Terminal or PowerShell rather than by double-clicking, so you can read anything it
|
|
says on the way up.
|
|
|
|
```powershell
|
|
.\meshterm-@VERSION@-windows-x64.exe
|
|
```
|
|
|
|
**Cardputer Zero** — install MeshTerm from M5Stack's app store on the device. The
|
|
same package is attached below as `meshterm_@VERSION@-1_arm64.deb`.
|
|
|
|
Every binary below is listed in `SHA256SUMS`. The version-less copies —
|
|
`meshterm-macos-arm64` and its four siblings — are the same binaries under a name
|
|
that never changes, for anything that wants whichever release is newest without
|
|
being told its number. [Installing with pip or pipx, and the rest of the
|
|
detail](https://github.com/jpmartineau/MeshTerm#install).
|
|
|
|
---
|
|
|
|
## What changed
|
|
INSTALL_MD
|
|
sed -e "s/@TAG@/$TAG/g" -e "s/@VERSION@/$VERSION/g" install.md.in > install.md
|
|
|
|
# One ask, at the end, with the reason. The install commands keep the top of
|
|
# the page, and the changelog stays a record of what changed.
|
|
cat > star.md <<'STAR_MD'
|
|
|
|
---
|
|
|
|
You ❤️ MeshTerm? Please give [the repository](https://github.com/jpmartineau/MeshTerm) a ⭐. It helps other MeshCore users find it, because GitHub ranks projects by stars in its search and topic pages. 🙏
|
|
STAR_MD
|
|
cat install.md notes.md star.md > body.md
|
|
echo "--- body ---"; cat body.md
|
|
|
|
- name: Create it
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ steps.gather.outputs.tag }}
|
|
PRERELEASE: ${{ inputs.prerelease && '--prerelease' || '' }}
|
|
run: |
|
|
gh release create "$TAG" release/* \
|
|
--title "MeshTerm ${TAG#v}" \
|
|
--notes-file body.md \
|
|
--verify-tag \
|
|
$PRERELEASE
|
|
echo "https://github.com/${{ github.repository }}/releases/tag/$TAG"
|
|
|
|
# The Cardputer Zero's package goes to M5Stack's app store. `czdev publish` puts it in a
|
|
# pull request on CardputerZero/packages, and the store merges an update when its checks
|
|
# pass. scripts/cardputer-zero/store-publish.py runs czdev with two corrections and stops
|
|
# if the store already has this version or a pull request for it is open. Thus a second
|
|
# run of a release opens no second pull request.
|
|
store:
|
|
name: send the Cardputer package to the app store
|
|
needs: publish
|
|
if: ${{ !inputs.prerelease }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
env:
|
|
# A token of jpmartineau with the public_repo scope: it uploads to the fork of
|
|
# CardputerZero/packages and opens the pull request as its author.
|
|
CARDPUTER_STORE_TOKEN: ${{ secrets.CARDPUTER_STORE_TOKEN }}
|
|
# The czdev that store-publish.py corrects. Change the two together.
|
|
APPBUILDER_COMMIT: 64edc2292628b436d3e624b150faeed2d60c31af
|
|
steps:
|
|
- name: Check for the token
|
|
run: |
|
|
if [ -z "$CARDPUTER_STORE_TOKEN" ]; then
|
|
echo "::error::CARDPUTER_STORE_TOKEN is not set; scripts/cardputer-zero/store-publish.py says what it needs."
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.tag || github.ref }}
|
|
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
name: meshterm-cardputer-zero
|
|
path: deb
|
|
|
|
- name: Get czdev
|
|
run: |
|
|
git init -q appbuilder
|
|
git -C appbuilder fetch -q --depth 1 \
|
|
https://github.com/m5stack/CardputerZero-AppBuilder.git "$APPBUILDER_COMMIT"
|
|
git -C appbuilder checkout -q FETCH_HEAD
|
|
|
|
- name: Publish
|
|
run: |
|
|
python3 scripts/cardputer-zero/store-publish.py --czdev appbuilder \
|
|
--deb deb/meshterm_*_arm64.deb
|
|
|
|
website:
|
|
name: put the version on meshterm.net
|
|
needs: publish
|
|
# A pre-release stays out of "Latest", so it stays off the landing page too.
|
|
if: ${{ !inputs.prerelease }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Check for the deploy key
|
|
env:
|
|
SITE_DEPLOY_KEY: ${{ secrets.SITE_DEPLOY_KEY }}
|
|
run: |
|
|
if [ -z "$SITE_DEPLOY_KEY" ]; then
|
|
echo "::error::SITE_DEPLOY_KEY is not set; the meshterm.net README says how to make it."
|
|
exit 1
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ inputs.tag || github.ref }}
|
|
path: meshterm
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
repository: jpmartineau/meshterm.net
|
|
ssh-key: ${{ secrets.SITE_DEPLOY_KEY }}
|
|
path: site
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.14"
|
|
|
|
- name: Rebuild the site from this release
|
|
run: |
|
|
python -m pip install --quiet -r site/requirements.txt
|
|
python site/build.py --meshterm meshterm
|
|
|
|
- name: Publish it
|
|
working-directory: site
|
|
env:
|
|
TAG: ${{ inputs.tag || github.ref_name }}
|
|
run: |
|
|
git add -A
|
|
if git diff --cached --quiet; then
|
|
echo "meshterm.net already shows ${TAG#v}."
|
|
exit 0
|
|
fi
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898341+github-actions[bot]@users.noreply.github.com"
|
|
git commit -m "MeshTerm ${TAG#v}"
|
|
git push
|