installers.yml gains a cardputer-zero job: build-deb.sh in a Debian 13 container on an arm64 runner, the same system as the Cardputer. The release attaches the .deb with the other downloads and lists it in SHA256SUMS, and its notes say where Cardputer users get MeshTerm. github-release.yml gains a store job after the release. It runs scripts/cardputer-zero/store-publish.py, which calls czdev publish with two corrections: the manifest's email is johnputer@meshterm.net, not the GitHub profile's, and the branch name stays the same between czdev's two calls (for a fork, the second call named a branch that was never pushed, and GitHub refused the PR). It also sends czdev's git over HTTPS with the token, and it publishes nothing when the store has the version or a PR for it is open, so a rerun opens no second PR. AppBuilder is pinned to the commit whose functions it corrects. The job needs the CARDPUTER_STORE_TOKEN secret (public_repo scope). The release skill now checks the store PR and counts fifteen assets. 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'
|
|
|
|
---
|
|
|
|
If MeshTerm is useful to you, 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
|