mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 16:02:24 +00:00
The last platform from #306: a Linux install running as an AppImage can now download, verify, and apply a release from inside the app, through the same tty7-updater helper the macOS (#309) and Windows (#330) paths use. Tarball and distro installs are deliberately untouched — they keep the named-package hint and the release page, because replacing a file a package manager may own is not this code's call to make. The installed artifact is one file, the path $APPIMAGE names, so the install is the simplest of the three platforms: stage the download beside the image (two renames only stay atomic on one filesystem), verify, swap, relaunch, and restore the preserved previous image if the new one does not survive its launch grace. What is Linux-shaped about it is the mount: the image the GUI runs from is FUSE-mounted by the AppImage runtime and torn down when the app exits, which is the moment the installer starts working — so the GUI copies the helper out of the mount into staging and runs the copy, the way the Windows path runs a private copy because Setup replaces the installed one. The daemon is left running throughout, as on macOS: nothing on Linux locks a running executable's file, and the panes it serves are the reason the update restarts only the GUI. The swap also carries the installed image's own mode onto its replacement, so a 0700 image stays private and the download's missing execute bit never reaches the installation. Verification holds the issue's requirements with what an unsigned ELF can offer: the bytes must match the release's checksums.txt, the file must actually be a type-2 AppImage — a mis-published asset fails with a name instead of at launch — and the image must state the version it claims. That statement is new: bundle-appimage.sh stamps X-AppImage-Version into the desktop entry, and the updater reads it back with one --appimage-extract, answered by the runtime before any application code and without FUSE. The same pass requires the new image to bundle its own tty7-updater, because an image without one would install fine and then be the last version that ever could. release.yml and nightly.yml now build the updater on the Linux leg and bundle it into the AppImage, and both check the packaged image for the same facts the updater checks on a user's machine — helper present, version stamped — so a packaging mistake fails the workflow instead of the update. The first release carrying this can only bootstrap: images already installed predate the helper and keep the manual hint, so the first complete in-app update is the release after it.
506 lines
20 KiB
YAML
506 lines
20 KiB
YAML
name: Nightly
|
|
|
|
# Unattended nightly channel: build `main` every night, stamp a pre-release
|
|
# CalVer version (<next-stable>-nightly.<YYYYMMDDHHMM>), and publish everything
|
|
# to a single rolling `nightly` prerelease. Stable releases (release.yml) are
|
|
# untouched.
|
|
#
|
|
# This is a real update channel, not just a build: an installation set to
|
|
# Nightly reads /releases/tags/nightly and rolls from one of these to the next.
|
|
# Stable reads /releases/latest, which excludes prereleases, so it never sees
|
|
# them. See `core::update::release_endpoint`.
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "0 18 * * *" # 18:00 UTC = 02:00 Beijing
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: nightly
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# Decide whether tonight needs a build, and compute the version once.
|
|
plan:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
build: ${{ steps.plan.outputs.build }}
|
|
version: ${{ steps.plan.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0 # full history + tags — needed for the skip check and version math
|
|
|
|
- id: plan
|
|
run: |
|
|
# Skip when HEAD is already published: last night's nightly points at
|
|
# it (main hasn't moved) or a stable tag does (tonight would just
|
|
# rebuild the release under a nightly name).
|
|
if git tag --points-at HEAD | grep -qE '^(nightly$|v[0-9])'; then
|
|
echo "nothing new since the last published build — skipping"
|
|
echo "build=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# Nightly version = next stable patch + timestamp suffix, so semver
|
|
# ordering lands between the previous and the next stable release:
|
|
# 26.7.0 < 26.7.1-nightly.202607161800 < 26.7.1.
|
|
#
|
|
# To the minute, not to the day: two builds on one day is what
|
|
# workflow_dispatch is for, and a date alone gives them the same
|
|
# version, which the updater reads as "already up to date" and never
|
|
# offers to the people who took the morning build. Still one numeric
|
|
# identifier, which is what `parse_version` orders nightlies by — it
|
|
# compares them all, so this stays sound if a segment is ever added.
|
|
LAST=$(git tag -l 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
|
BASE=${LAST#v}
|
|
NEXT="${BASE%.*}.$(( ${BASE##*.} + 1 ))"
|
|
VERSION="${NEXT}-nightly.$(date -u +%Y%m%d%H%M)"
|
|
echo "building $VERSION from ${GITHUB_SHA::7}"
|
|
echo "build=true" >> "$GITHUB_OUTPUT"
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
# Mirrors release.yml's build matrix; keep the two in sync when editing.
|
|
build:
|
|
# Also behind server-musl: the Windows installer embeds its Linux musl
|
|
# binary for WSL. See the same note in release.yml.
|
|
needs: [plan, server-musl]
|
|
if: needs.plan.outputs.build == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- runner: macos-14
|
|
os: macos
|
|
arch: arm64
|
|
target: aarch64-apple-darwin
|
|
- runner: macos-15-intel
|
|
os: macos
|
|
arch: x86_64
|
|
target: x86_64-apple-darwin
|
|
- runner: windows-latest
|
|
os: windows
|
|
arch: x86_64
|
|
target: x86_64-pc-windows-msvc
|
|
- runner: ubuntu-latest
|
|
os: linux
|
|
arch: x86_64
|
|
target: x86_64-unknown-linux-gnu
|
|
runs-on: ${{ matrix.runner }}
|
|
steps:
|
|
- name: Checkout tty7
|
|
uses: actions/checkout@v4
|
|
with:
|
|
path: tty7
|
|
|
|
# Everything versioned — the binary (CARGO_PKG_VERSION), asset names,
|
|
# DMG plist, Inno installer — reads Cargo.toml, so stamping it is the only
|
|
# edit needed. Shared with the server-musl job below, and it fails loudly
|
|
# if the manifest's shape ever moves the version line out from under it.
|
|
- name: Stamp nightly version
|
|
working-directory: tty7
|
|
shell: bash
|
|
run: bash .github/scripts/stamp-version.sh "${{ needs.plan.outputs.version }}"
|
|
|
|
- name: Install Linux system dependencies
|
|
if: matrix.os == 'linux'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y pkg-config cmake clang libxkbcommon-dev \
|
|
libxkbcommon-x11-dev libfontconfig1-dev libfreetype6-dev \
|
|
libwayland-dev libx11-dev libxcb1-dev libzstd-dev libssl-dev \
|
|
libkrb5-dev libfuse2 file imagemagick
|
|
echo "LIBGSSAPI_IMPL=mit" >> "$GITHUB_ENV"
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: tty7
|
|
|
|
# The updater ships on every GUI platform now: inside the macOS bundle,
|
|
# beside the Windows app, and inside the Linux AppImage.
|
|
- name: Build
|
|
working-directory: tty7
|
|
shell: bash
|
|
run: |
|
|
cargo build --release --target "${{ matrix.target }}"
|
|
cargo build --release --features updater \
|
|
--bin tty7-updater --target "${{ matrix.target }}"
|
|
|
|
- name: Bundle macOS DMG
|
|
if: matrix.os == 'macos'
|
|
working-directory: tty7
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: bash .github/scripts/bundle-macos.sh "${{ matrix.target }}" "${{ matrix.arch }}"
|
|
|
|
- name: Verify macOS Nightly update archive
|
|
if: matrix.os == 'macos'
|
|
working-directory: tty7
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${{ needs.plan.outputs.version }}"
|
|
ZIP="dist/tty7-${VERSION}-macos-${{ matrix.arch }}.zip"
|
|
VERIFY_ROOT="$RUNNER_TEMP/tty7-nightly-update-verify"
|
|
rm -rf "$VERIFY_ROOT"
|
|
mkdir -p "$VERIFY_ROOT"
|
|
/usr/bin/ditto -x -k "$ZIP" "$VERIFY_ROOT"
|
|
APP="$VERIFY_ROOT/tty7.app"
|
|
test -x "$APP/Contents/MacOS/tty7-updater"
|
|
ACTUAL_VERSION="$(/usr/libexec/PlistBuddy \
|
|
-c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")"
|
|
test "$ACTUAL_VERSION" = "$VERSION"
|
|
/usr/bin/codesign --verify --deep --strict --verbose=2 "$APP"
|
|
|
|
- name: Package Linux tarball
|
|
if: matrix.os == 'linux'
|
|
working-directory: tty7
|
|
run: bash .github/scripts/bundle-linux.sh "${{ matrix.target }}" "${{ matrix.arch }}"
|
|
|
|
- name: Package Linux AppImage
|
|
if: matrix.os == 'linux'
|
|
working-directory: tty7
|
|
run: bash .github/scripts/bundle-appimage.sh "${{ matrix.target }}" "${{ matrix.arch }}"
|
|
|
|
# The same facts the in-app updater checks on the user's machine,
|
|
# checked here so a packaging mistake fails the nightly instead. See
|
|
# the twin step in release.yml.
|
|
- name: Verify Linux AppImage update package
|
|
if: matrix.os == 'linux'
|
|
working-directory: tty7
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION="${{ needs.plan.outputs.version }}"
|
|
IMAGE="$PWD/dist/tty7-${VERSION}-linux-${{ matrix.arch }}.AppImage"
|
|
VERIFY_ROOT="$RUNNER_TEMP/tty7-appimage-update-verify"
|
|
rm -rf "$VERIFY_ROOT"
|
|
mkdir -p "$VERIFY_ROOT"
|
|
(cd "$VERIFY_ROOT" && "$IMAGE" --appimage-extract >/dev/null)
|
|
test -x "$VERIFY_ROOT/squashfs-root/usr/bin/tty7-app"
|
|
test -x "$VERIFY_ROOT/squashfs-root/usr/bin/tty7-updater"
|
|
grep -Fxq "X-AppImage-Version=${VERSION}" \
|
|
"$VERIFY_ROOT/squashfs-root/usr/share/applications/tty7.desktop"
|
|
|
|
# See release.yml for why this is best-effort rather than required.
|
|
- name: Fetch the bundled Linux server
|
|
if: matrix.os == 'windows'
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: nightly-tty7-server-linux-x86_64-musl
|
|
path: tty7/bundled-server
|
|
|
|
- name: Package Windows installer + zip
|
|
if: matrix.os == 'windows'
|
|
working-directory: tty7
|
|
shell: pwsh
|
|
run: '& ./.github/scripts/bundle-windows.ps1 "${{ matrix.target }}" "${{ matrix.arch }}"'
|
|
|
|
# Nightly builds the same packages as release.yml, so it gets the same
|
|
# check. Nightly is not an update channel — nobody updates *into* these
|
|
# artifacts — but a marker or version regression shows up here a night
|
|
# before it would reach a stable release.
|
|
- name: Verify Windows update package
|
|
if: matrix.os == 'windows'
|
|
working-directory: tty7
|
|
shell: pwsh
|
|
run: >-
|
|
& ./.github/scripts/verify-windows-package.ps1
|
|
"${{ matrix.arch }}" "${{ needs.plan.outputs.version }}"
|
|
|
|
# Same glob list as release.yml's Release step: the bundle scripts leave
|
|
# intermediates in dist/ (tty7.app, entitlements.plist, the Windows
|
|
# staging dir) that must not reach the release assets.
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: nightly-${{ matrix.os }}-${{ matrix.arch }}
|
|
path: |
|
|
tty7/dist/*.dmg
|
|
tty7/dist/*.tar.gz
|
|
tty7/dist/*.zip
|
|
tty7/dist/*-setup.exe
|
|
tty7/dist/*.AppImage
|
|
if-no-files-found: error
|
|
|
|
# Mirrors release.yml's server-musl job; keep the two in sync when editing.
|
|
# Nightly carries the server binaries too so the remote-install path can
|
|
# be exercised against the rolling channel instead of waiting for a tag.
|
|
server-musl:
|
|
needs: plan
|
|
if: needs.plan.outputs.build == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
# `target` is the build triple; `asset` is the published filename. Kept
|
|
# apart for the reason release.yml spells out: the triple's vendor field is
|
|
# `unknown`, and a download name has no business carrying it.
|
|
matrix:
|
|
include:
|
|
- target: x86_64-unknown-linux-musl
|
|
asset: tty7-server-linux-x86_64-musl
|
|
- target: aarch64-unknown-linux-musl
|
|
asset: tty7-server-linux-aarch64-musl
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
RUSTFLAGS: -C strip=symbols
|
|
steps:
|
|
- name: Checkout tty7
|
|
uses: actions/checkout@v4
|
|
with:
|
|
path: tty7
|
|
|
|
# Stamped for the same reason the GUI builds are: the server reports
|
|
# CARGO_PKG_VERSION over the wire during the version handshake, and a
|
|
# nightly server claiming the last stable version would make that
|
|
# negotiation lie. The asset *name* is version-free either way.
|
|
- name: Stamp nightly version
|
|
working-directory: tty7
|
|
run: bash .github/scripts/stamp-version.sh "${{ needs.plan.outputs.version }}"
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- uses: mlugg/setup-zig@v2
|
|
with:
|
|
version: 0.16.0
|
|
|
|
- uses: taiki-e/install-action@v2
|
|
with:
|
|
tool: cargo-zigbuild
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: tty7
|
|
key: ${{ matrix.target }}
|
|
|
|
- name: Look for the tty7-server package
|
|
id: probe
|
|
working-directory: tty7
|
|
run: |
|
|
set -euo pipefail
|
|
if cargo metadata --no-deps --format-version 1 \
|
|
| jq -e '[.packages[].name] | index("tty7-server")' >/dev/null; then
|
|
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
echo "::warning::tty7-server is not a workspace member yet — tonight's nightly carries no remote-server assets"
|
|
fi
|
|
|
|
# No `--locked` here, matching the rest of nightly: the version stamp above
|
|
# rewrites Cargo.toml, and cargo has to be free to refresh the root
|
|
# package's own lock entry.
|
|
- name: Build static tty7-server
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: cargo zigbuild --release -p tty7-server --target ${{ matrix.target }}
|
|
|
|
- name: Assert the binary is static
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: bash .github/scripts/assert-static.sh "target/${{ matrix.target }}/release/tty7-server"
|
|
|
|
- name: Stage the asset
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
cp "target/${{ matrix.target }}/release/tty7-server" \
|
|
"dist/${{ matrix.asset }}"
|
|
chmod +x "dist/${{ matrix.asset }}"
|
|
|
|
- uses: actions/upload-artifact@v7
|
|
if: steps.probe.outputs.present == 'true'
|
|
with:
|
|
name: nightly-${{ matrix.asset }}
|
|
path: tty7/dist/${{ matrix.asset }}
|
|
if-no-files-found: error
|
|
|
|
# Mirrors release.yml's server-macos job; keep the two in sync when editing.
|
|
# Nightly carries the macOS servers for the same reason it carries the Linux
|
|
# ones: so the remote-install path onto a Mac can be exercised against the
|
|
# rolling channel instead of waiting for a tag.
|
|
server-macos:
|
|
needs: plan
|
|
if: needs.plan.outputs.build == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- target: aarch64-apple-darwin
|
|
arch: arm64
|
|
asset: tty7-server-macos-aarch64
|
|
- target: x86_64-apple-darwin
|
|
arch: x86_64
|
|
asset: tty7-server-macos-x86_64
|
|
runs-on: macos-latest
|
|
env:
|
|
RUSTFLAGS: -C strip=symbols
|
|
steps:
|
|
- name: Checkout tty7
|
|
uses: actions/checkout@v4
|
|
with:
|
|
path: tty7
|
|
|
|
# Stamped for the same reason the musl servers are: the version the server
|
|
# reports during the handshake has to be tonight's, not the last stable.
|
|
- name: Stamp nightly version
|
|
working-directory: tty7
|
|
run: bash .github/scripts/stamp-version.sh "${{ needs.plan.outputs.version }}"
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: tty7
|
|
key: ${{ matrix.target }}
|
|
|
|
- name: Look for the tty7-server package
|
|
id: probe
|
|
working-directory: tty7
|
|
run: |
|
|
set -euo pipefail
|
|
if cargo metadata --no-deps --format-version 1 \
|
|
| jq -e '[.packages[].name] | index("tty7-server")' >/dev/null; then
|
|
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
echo "::warning::tty7-server is not a workspace member yet — tonight's nightly carries no macOS remote-server assets"
|
|
fi
|
|
|
|
# No `--locked`, matching the rest of nightly: the version stamp above
|
|
# rewrites Cargo.toml and cargo has to be free to refresh its lock entry.
|
|
- name: Build tty7-server
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: cargo build --release -p tty7-server --target ${{ matrix.target }}
|
|
|
|
# Ad-hoc, for the reason release.yml spells out: it is what arm64 requires
|
|
# before it will execute anything, the linker only applies it to the arm64
|
|
# slice, and x86_64 is what a Rosetta shell asks for.
|
|
- name: Ad-hoc sign the binary
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: codesign --force --sign - "target/${{ matrix.target }}/release/tty7-server"
|
|
|
|
- name: Assert the binary is self-contained
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: |
|
|
bash .github/scripts/assert-macho.sh \
|
|
"target/${{ matrix.target }}/release/tty7-server" "${{ matrix.arch }}"
|
|
|
|
- name: Stage the asset
|
|
if: steps.probe.outputs.present == 'true'
|
|
working-directory: tty7
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
cp "target/${{ matrix.target }}/release/tty7-server" \
|
|
"dist/${{ matrix.asset }}"
|
|
chmod +x "dist/${{ matrix.asset }}"
|
|
|
|
- uses: actions/upload-artifact@v7
|
|
if: steps.probe.outputs.present == 'true'
|
|
with:
|
|
name: nightly-${{ matrix.asset }}
|
|
path: tty7/dist/${{ matrix.asset }}
|
|
if-no-files-found: error
|
|
|
|
# Single publish step after all platforms succeed, so the rolling release is
|
|
# always complete — a failed platform means tonight's nightly is skipped
|
|
# entirely and users keep yesterday's, never a partial asset set.
|
|
publish:
|
|
needs: [plan, build, server-musl, server-macos]
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
VERSION: ${{ needs.plan.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
# The version has to be readable by a machine, and the tag cannot carry
|
|
# it: `nightly` is force-moved to a new commit every night, so the release
|
|
# object's `tag_name` is the literal string "nightly". Without this the
|
|
# updater is left reverse-engineering the version out of asset filenames.
|
|
#
|
|
# Written before checksums.txt so the manifest is hashed along with
|
|
# everything else. The updater reads it straight from the release without
|
|
# checking that hash — it only decides which version is on offer, and the
|
|
# package it then selects is verified the usual way — but the entry has to
|
|
# be there for anyone auditing the release by hand.
|
|
- name: Generate nightly.json
|
|
run: |
|
|
set -euo pipefail
|
|
jq -n \
|
|
--arg version "$VERSION" \
|
|
--arg commit "$GITHUB_SHA" \
|
|
--arg published_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
'{version: $version, commit: $commit, published_at: $published_at}' \
|
|
> dist/nightly.json
|
|
cat dist/nightly.json
|
|
|
|
# Same contract as release.yml — see `install::asset`. Written
|
|
# into dist/ before the upload below so it ships as an asset like any
|
|
# other, and so the prune step at the end sees it as current.
|
|
- name: Generate checksums.txt
|
|
run: |
|
|
set -euo pipefail
|
|
cd dist
|
|
rm -f checksums.txt
|
|
# Built in $RUNNER_TEMP and moved in: a redirect straight into dist/
|
|
# creates the file before find walks the directory, so it would hash
|
|
# itself as a zero-byte entry. `xargs -r` so an empty dist/ fails
|
|
# instead of hanging on stdin.
|
|
find . -type f -printf '%P\n' \
|
|
| LC_ALL=C sort | xargs -r sha256sum > "$RUNNER_TEMP/checksums.txt"
|
|
[ -s "$RUNNER_TEMP/checksums.txt" ] || { echo "::error::no assets to checksum"; exit 1; }
|
|
mv "$RUNNER_TEMP/checksums.txt" checksums.txt
|
|
sha256sum -c checksums.txt
|
|
cat checksums.txt
|
|
|
|
- name: Update rolling nightly release
|
|
run: |
|
|
# Move the tag first so the release object follows it to this SHA.
|
|
git push -f origin "$GITHUB_SHA:refs/tags/nightly"
|
|
|
|
TITLE="Nightly $VERSION"
|
|
NOTES="Automated nightly build of \`main\` @ ${GITHUB_SHA::7} ($(date -u +%F)). Rolling prerelease — assets are replaced every night; for the latest stable release see https://github.com/${GITHUB_REPOSITORY}/releases/latest."
|
|
|
|
if gh release view nightly >/dev/null 2>&1; then
|
|
gh release edit nightly --prerelease --title "$TITLE" --notes "$NOTES"
|
|
else
|
|
gh release create nightly --prerelease --title "$TITLE" --notes "$NOTES"
|
|
fi
|
|
|
|
# Upload before deleting: date-suffixed names never collide across
|
|
# nights, so both sets briefly coexist — if an upload dies midway,
|
|
# yesterday's complete nightly is still intact.
|
|
gh release upload nightly dist/* --clobber
|
|
|
|
# Now prune the previous night's assets (anything we didn't upload).
|
|
gh release view nightly --json assets -q '.assets[].name' |
|
|
while read -r name; do
|
|
[ -e "dist/$name" ] || gh release delete-asset nightly "$name" -y
|
|
done
|