Merge pull request #286 from peerloomllc/build/seeder-copy-forward

build(release): re-attach an unchanged seeder instead of rebuilding it
This commit is contained in:
Tim
2026-07-31 17:29:00 -05:00
committed by GitHub
5 changed files with 332 additions and 28 deletions
+192 -17
View File
@@ -10,6 +10,9 @@
# --skip-nostr Skip Nostr announcement even if selected
# --skip-desktop Skip desktop builds (Linux/Mac/Win) even if the Mac Mini is reachable
# --skip-seeder Skip blind-seeder installer builds (Linux/Win/macOS) even if buildable
# --rebuild-seeder Force a seeder installer REBUILD even when nothing under the
# seeder changed (default in that case is to re-attach the previous
# release's installers unchanged)
# --skip-seeder-store Skip seeder STORE artifacts (ghcr image + Start9 .s9pk + registry)
#
# Required env vars (or set in scripts/.env):
@@ -533,6 +536,7 @@ SKIP_PLAY=false
SKIP_NOSTR=false
SKIP_DESKTOP=false
SKIP_SEEDER=false
REBUILD_SEEDER=false
SKIP_SEEDER_STORE=false
SEEDER_IMAGE="${SEEDER_IMAGE:-ghcr.io/peerloomllc/pearcal-seeder}"
@@ -544,6 +548,7 @@ for arg in "$@"; do
--skip-nostr) SKIP_NOSTR=true ;;
--skip-desktop) SKIP_DESKTOP=true ;;
--skip-seeder) SKIP_SEEDER=true ;;
--rebuild-seeder) REBUILD_SEEDER=true ;;
--skip-seeder-store) SKIP_SEEDER_STORE=true ;;
v[0-9]*.[0-9]*.[0-9]*)
if [[ ! "$arg" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
@@ -926,6 +931,97 @@ _seeder_configured() {
}
_seeder_configured && PUBLISH_SEEDER=true
# ---- seeder copy-forward ---------------------------------------------------
# Most releases change the mobile/desktop app and nothing the seeder is built
# from. Rebuilding the seeder anyway costs three cross-builds plus a Mac Mini
# trip, and produces installers byte-for-byte equivalent to the ones already
# published. But the seeder CANNOT simply be left off a release: its update
# check reads the repo's LATEST release and requires a seeder installer to be
# attached to it (see src/lib/seederUpdateCheck.js), so a release with no seeder
# asset silently switches off updates for every self-managed seeder in the field
# and hides the download from anyone browsing.
#
# So when nothing under the seeder changed, re-attach the PREVIOUS release's
# installers to this one instead of rebuilding them. They keep their own version
# in their filenames, and the update check compares against that rather than
# against the release tag, so a 1.0.37 seeder correctly sees "nothing new" on a
# v1.0.40 release carrying the 1.0.37 build.
SEEDER_REUSE_TAG="" # non-empty => attach this tag's installers instead of building
SEEDER_REUSE_DIR="" # where they were downloaded to
SEEDER_CHANGED_FILES=() # seeder-relevant paths changed since SEEDER_REUSE_TAG
# Paths the seeder payload is actually built from. Derived from
# seeder-launcher/scripts/stage-payload.sh: the worklet is bare-packed from
# src/seed.js, whose require graph is entirely within src/lib/, and the host is
# copied out of seeder-launcher/ with two assets pulled from elsewhere.
_seeder_input_path() {
case "$1" in
seeder-launcher/dist/*) return 1 ;; # build output, not input
seeder-launcher/*) return 0 ;;
src/seed.js|src/lib/*) return 0 ;;
src/ui/fonts.js) return 0 ;; # staged as host/fonts.css
assets/images/icon.png) return 0 ;; # staged as host/brand.png
package.json|package-lock.json) return 0 ;; # dependency + prebuild versions
patches/*) return 0 ;; # patch-package runs in the payload build
.dockerignore) return 0 ;;
esac
return 1
}
# Paths KNOWN not to reach the seeder. Anything in NEITHER list counts as a
# change, so a path nobody has classified errs toward rebuilding rather than
# toward shipping a stale seeder.
_seeder_irrelevant_path() {
case "$1" in
android/*|ios/*|app/*|electron/*|modules/*|pearcal-sync-notif/*) return 0 ;;
seeder-launcher/dist/*) return 0 ;;
src/ui/*|src/ui-desktop/*|src/ui-shared/*) return 0 ;;
src/bare.js|src/db.js|src/sync.js|src/index.js) return 0 ;;
src/invite.js|src/notifications.js|src/widget-cache.js) return 0 ;;
assets/*) return 0 ;;
metadata/*|proposals/*|docs/*|data/*|test/*|tools/*) return 0 ;;
scripts/*|.github/*) return 0 ;;
*.md|.gitignore|.gitattributes|app.json|tsconfig.json) return 0 ;;
babel.config.js|.editorconfig|.watchmanconfig) return 0 ;;
esac
return 1
}
# Populate SEEDER_CHANGED_FILES with the seeder-relevant paths changed between
# $1 and HEAD. Returns 0 when something changed (rebuild), 1 when nothing did.
# An unresolvable ref counts as changed.
_seeder_changed_since() {
local ref="$1" f
SEEDER_CHANGED_FILES=()
git rev-parse --verify --quiet "${ref}^{commit}" >/dev/null 2>&1 || return 0
while IFS= read -r f; do
[ -z "$f" ] && continue
if _seeder_input_path "$f"; then
SEEDER_CHANGED_FILES+=("$f")
elif ! _seeder_irrelevant_path "$f"; then
SEEDER_CHANGED_FILES+=("$f (unclassified)")
fi
done < <(git diff --name-only "$ref" HEAD 2>/dev/null)
[ "${#SEEDER_CHANGED_FILES[@]}" -gt 0 ]
}
# Most recent published release that carries a seeder INSTALLER (not the .s9pk,
# which is a store artifact with its own lifecycle). Echoes the tag. One API call
# rather than one per release — /releases returns assets inline, `gh release
# view` does not.
_last_seeder_release() {
local tag
while IFS= read -r tag; do
[ -z "$tag" ] && continue
[ "$tag" = "$RELEASE_TAG" ] && continue
echo "$tag"; return 0
done < <(gh api 'repos/{owner}/{repo}/releases?per_page=20' \
-q '.[] | select(.draft | not) | select([.assets[].name
| select(test("^(PearCalSeeder-|pearcal-seeder_)"))
| select(endswith(".sha256") | not)] | length > 0) | .tag_name' 2>/dev/null)
return 1
}
# Blind-seeder STORE artifacts (Docker image → ghcr + Start9 .s9pk + the combined
# StartOS community registry on the website). Heavier + slower than the installers
# (a multi-arch image build/push + a ~hundreds-of-MB s9pk pack), so it's a separate
@@ -1055,17 +1151,49 @@ if ! $ZAPSTORE_ONLY && ! $CHECK_VERSIONS_ONLY; then
PUBLISH_SEEDER=false
echo " - Seeder installers (skipped via --skip-seeder)"
elif _seeder_configured; then
_seeder_mac_note=""
ssh -o ConnectTimeout=5 -o BatchMode=yes "${MAC_MINI_HOST:-Tims-Mac-mini.local}" exit 2>/dev/null \
|| _seeder_mac_note=" (macOS .pkg skipped — ${MAC_MINI_HOST:-Tims-Mac-mini.local} unreachable)"
while true; do
read -rp " Publish blind-seeder installers (Linux/Win/macOS) to GitHub Release?${_seeder_mac_note} [Y/n] " _r
case "${_r:-y}" in
[Yy]) PUBLISH_SEEDER=true; echo " ✓ Seeder installers"; break ;;
[Nn]) PUBLISH_SEEDER=false; echo " Seeder installers (skipped)"; break ;;
*) echo " Please enter y or n." ;;
esac
done
# Decide rebuild-vs-re-attach BEFORE prompting, so the prompt states what
# will actually happen. --rebuild-seeder forces the build path.
SEEDER_REUSE_TAG=""
if ! $REBUILD_SEEDER; then
_prev_seeder_tag="$(_last_seeder_release || true)"
if [ -n "$_prev_seeder_tag" ]; then
if _seeder_changed_since "$_prev_seeder_tag"; then
echo " Seeder inputs changed since $_prev_seeder_tag (${#SEEDER_CHANGED_FILES[@]} path(s)) — rebuilding:"
printf ' %s\n' "${SEEDER_CHANGED_FILES[@]:0:6}"
[ "${#SEEDER_CHANGED_FILES[@]}" -gt 6 ] && echo " … and $(( ${#SEEDER_CHANGED_FILES[@]} - 6 )) more"
else
SEEDER_REUSE_TAG="$_prev_seeder_tag"
fi
else
echo " No previous release carries seeder installers — building."
fi
fi
if [ -n "$SEEDER_REUSE_TAG" ]; then
echo " Nothing the seeder is built from has changed since $SEEDER_REUSE_TAG."
echo " Its installers can be re-attached to this release unchanged — no rebuild, no Mac Mini trip."
while true; do
read -rp " Re-attach the $SEEDER_REUSE_TAG seeder installers? [Y/n] (n = no seeder assets at all; --rebuild-seeder to force a build) " _r
case "${_r:-y}" in
[Yy]) PUBLISH_SEEDER=true; echo " ✓ Seeder installers (re-attached from $SEEDER_REUSE_TAG)"; break ;;
[Nn]) PUBLISH_SEEDER=false; SEEDER_REUSE_TAG=""
echo " ✗ Seeder installers (skipped — self-managed seeders will see no update from this release)"; break ;;
*) echo " Please enter y or n." ;;
esac
done
else
_seeder_mac_note=""
ssh -o ConnectTimeout=5 -o BatchMode=yes "${MAC_MINI_HOST:-Tims-Mac-mini.local}" exit 2>/dev/null \
|| _seeder_mac_note=" (macOS .pkg skipped — ${MAC_MINI_HOST:-Tims-Mac-mini.local} unreachable)"
while true; do
read -rp " Publish blind-seeder installers (Linux/Win/macOS) to GitHub Release?${_seeder_mac_note} [Y/n] " _r
case "${_r:-y}" in
[Yy]) PUBLISH_SEEDER=true; echo " ✓ Seeder installers"; break ;;
[Nn]) PUBLISH_SEEDER=false; echo " ✗ Seeder installers (skipped)"; break ;;
*) echo " Please enter y or n." ;;
esac
done
fi
else
PUBLISH_SEEDER=false
echo " - Seeder installers (not configured — need dpkg-deb and/or makensis locally)"
@@ -1716,17 +1844,64 @@ fi
# Signing: build-macos-remote.sh inherits APP_SIGN_ID/PKG_SIGN_ID/NOTARY_PROFILE
# from the environment (set them in scripts/.env to ship a signed+notarized .pkg;
# unset = unsigned, install via right-click → Open).
if $PUBLISH_SEEDER; then
# Re-attach path: pull the previous release's installers and hand them to the
# same upload loop the built ones go through. Nothing is rebuilt, so nothing can
# come out different — these are the exact bytes already published and already
# covered by their own .sha256 sidecars, which the seeder verifies before
# applying. A download failure falls back to building rather than to shipping a
# release with no seeder asset.
if $PUBLISH_SEEDER && [ -n "$SEEDER_REUSE_TAG" ]; then
echo "==> Re-attaching blind-seeder installers from $SEEDER_REUSE_TAG (nothing under the seeder changed)..."
SEEDER_REUSE_DIR="$(mktemp -d /tmp/pearcal-seeder-reuse.XXXXXX)"
# Installer name families only. The .s9pk is deliberately NOT copied forward:
# it is a store artifact whose registry pins its own version.
if gh release download "$SEEDER_REUSE_TAG" -D "$SEEDER_REUSE_DIR" \
-p 'PearCalSeeder-*' -p 'pearcal-seeder_*' 2>/dev/null; then
# Re-attaching is only safe when every installer states its own version in
# its filename: that is what the update check compares against, and an
# unversioned one falls back to the release tag — which is precisely the
# "update to yourself, forever" loop this whole path has to avoid. AppImages
# built before 1.0.38 carry no version, so refuse and rebuild instead.
_unversioned=()
while IFS= read -r _f; do
case "$_f" in *.s9pk|*.s9pk.sha256) continue ;; esac
case "$(basename "$_f")" in
*[0-9].[0-9]*.[0-9]*) ;;
*) _unversioned+=("$(basename "$_f")") ;;
esac
SEEDER_ARTIFACTS+=("$_f")
echo "$(basename "$_f")"
done < <(find "$SEEDER_REUSE_DIR" -maxdepth 1 -type f | sort)
if [ "${#_unversioned[@]}" -gt 0 ]; then
echo "${#_unversioned[@]} asset(s) carry no version in their name — cannot re-attach safely:"
printf ' %s\n' "${_unversioned[@]}"
echo " (a versionless installer would re-offer itself on every check). Rebuilding instead."
SEEDER_ARTIFACTS=()
SEEDER_REUSE_TAG=""
fi
fi
if [ -n "$SEEDER_REUSE_TAG" ] && [ "${#SEEDER_ARTIFACTS[@]}" -eq 0 ]; then
echo " ✗ Could not download $SEEDER_REUSE_TAG's seeder installers — falling back to a rebuild."
SEEDER_REUSE_TAG=""
elif [ -n "$SEEDER_REUSE_TAG" ]; then
_n_inst=0
for _f in "${SEEDER_ARTIFACTS[@]}"; do case "$_f" in *.sha256) ;; *) _n_inst=$((_n_inst+1)) ;; esac; done
echo " $_n_inst installer(s) + sidecars re-attached, $(du -sh "$SEEDER_REUSE_DIR" | cut -f1) total"
fi
fi
if $PUBLISH_SEEDER && [ -z "$SEEDER_REUSE_TAG" ]; then
echo "==> Building blind-seeder installers (Linux + Windows local; macOS via Mac Mini)..."
SEEDER_DIR="$REPO_ROOT/seeder-launcher"
rm -f /tmp/pearcal-seeder-{linux,windows,macos}.log
# Clear this run's expected outputs first. Collection below is existence-based
# (so a partial build still ships what it produced), which is only safe if a
# FAILED build can't leave a stale file to be mistaken for fresh — critical for
# the AppImage, whose name carries no version (a prior release's leftover would
# otherwise be re-shipped under this tag).
# FAILED build can't leave a stale file to be mistaken for fresh. Every seeder
# installer now carries its version in its name, so a prior release's leftover
# can no longer be re-shipped under this tag, but clearing is still the cheap
# guard against a re-run of the SAME version picking up a half-written file.
rm -f \
"$SEEDER_DIR/dist/linux/PearCalSeeder-x86_64.AppImage"{,.sha256} \
"$SEEDER_DIR/dist/linux/PearCalSeeder-${APP_VERSION}-x86_64.AppImage"{,.sha256} \
"$SEEDER_DIR/dist/linux/pearcal-seeder_${APP_VERSION}_amd64.deb"{,.sha256} \
"$SEEDER_DIR/dist/windows/PearCalSeeder-Setup-${APP_VERSION}.exe"{,.sha256} \
"$SEEDER_DIR/dist/macos/PearCalSeeder-${APP_VERSION}"-{arm64,x64}.pkg{,.sha256} 2>/dev/null || true
@@ -1795,7 +1970,7 @@ if $PUBLISH_SEEDER; then
echo " ✗ Linux seeder build reported exit $SSTATUS_LINUX — see /tmp/pearcal-seeder-linux.log"
tail -15 /tmp/pearcal-seeder-linux.log | sed 's/^/ /'
}
_collect_seeder "Linux AppImage" "$SEEDER_DIR/dist/linux/PearCalSeeder-x86_64.AppImage"
_collect_seeder "Linux AppImage" "$SEEDER_DIR/dist/linux/PearCalSeeder-${APP_VERSION}-x86_64.AppImage"
_collect_seeder "Linux deb" "$SEEDER_DIR/dist/linux/pearcal-seeder_${APP_VERSION}_amd64.deb"
if [ -n "$PID_SWIN" ]; then
+8 -5
View File
@@ -12,21 +12,24 @@ Two artifacts, both built from `seeder-launcher/scripts/`:
## AppImage — portable, no install
`scripts/build-appimage-linux.sh``dist/linux/PearCalSeeder-<arch>.AppImage`
`scripts/build-appimage-linux.sh``dist/linux/PearCalSeeder-<version>-<arch>.AppImage`
The version is in the filename, so substitute the one you downloaded (e.g.
`PearCalSeeder-1.0.37-x86_64.AppImage`) for `$APPIMAGE` below.
```sh
chmod +x PearCalSeeder-x86_64.AppImage
chmod +x "$APPIMAGE"
# Foreground (CLI / debugging) — prints a token-authed dashboard URL:
./PearCalSeeder-x86_64.AppImage
"./$APPIMAGE"
# Register a background systemd USER service (survives logout/reboot via linger):
./PearCalSeeder-x86_64.AppImage --install-service
"./$APPIMAGE" --install-service
# …or just double-click it in a file manager, which sets the service up and
# opens the dashboard.
# Remove the service (keeps the data dir; add --purge to wipe the identity too):
./PearCalSeeder-x86_64.AppImage --uninstall-service [--purge]
"./$APPIMAGE" --uninstall-service [--purge]
```
The service's `ExecStart` points back at the `.AppImage` file, so leave it in
@@ -77,7 +77,13 @@ fi
# --- pack -------------------------------------------------------------------
OUT_DIR="$LAUNCHER/dist/linux"
APPIMAGE="$OUT_DIR/PearCalSeeder-$ARCH.AppImage"
# The version belongs IN the name, like every other seeder installer. The update
# check reads a candidate installer's version out of its filename (a release may
# carry a seeder older than its own tag when nothing under the seeder changed),
# and an unversioned AppImage would fall back to the tag and re-offer itself
# forever. It also stops a failed build's leftover from being re-shipped under a
# later tag, which release.sh previously had to guard against by hand.
APPIMAGE="$OUT_DIR/PearCalSeeder-$VERSION-$ARCH.AppImage"
mkdir -p "$OUT_DIR"
rm -f "$APPIMAGE"
echo "--> appimagetool"
+38 -4
View File
@@ -103,6 +103,27 @@ function selectAsset (assets, platform, arch, installKind) {
return null
}
// Pull the version out of a seeder installer's filename, or null when it carries
// none. Every seeder installer stamps its version into its name
// (`pearcal-seeder_1.0.37_amd64.deb`, `PearCalSeeder-1.0.37-arm64.pkg`,
// `PearCalSeeder-Setup-1.0.37.exe`, `PearCalSeeder-1.0.37-x86_64.AppImage`).
//
// This exists because a release does NOT necessarily carry a seeder built at its
// own tag. When nothing under the seeder's inputs changed, the release script
// re-attaches the previous seeder build rather than rebuilding it, so tag v1.0.40
// may legitimately ship a 1.0.37 seeder. Comparing against the TAG in that case
// would tell every 1.0.37 seeder that 1.0.40 is available, hand it back the same
// 1.0.37 build, and — because the installed version never moves — repeat that on
// every check, forever.
//
// Arch tokens are not mistaken for versions: `x86_64` and `amd64` have no dotted
// triple, so the pattern cannot match them.
function versionFromAssetName (name) {
if (typeof name !== 'string') return null
const m = name.match(/(\d+\.\d+\.\d+)/)
return m ? m[1] : null
}
// Find the `<assetName>.sha256` sidecar for a chosen asset, or null.
function selectSha256For (assets, assetName) {
if (!Array.isArray(assets) || typeof assetName !== 'string') return null
@@ -111,17 +132,27 @@ function selectSha256For (assets, assetName) {
// Evaluate a GitHub `/releases/latest` JSON against the running version for a
// platform. Returns a stable shape the host route + UI consume. `updateAvailable`
// is true only when the release is a strictly newer tag AND carries an installer
// asset for this platform (see the header note on the PearCircle divergence).
// is true only when the installer this platform would actually GET is strictly
// newer than what is running, and the release carries such an installer at all
// (see the header note on the PearCircle divergence).
//
// `latestVersion` is the version of that installer, not of the release tag — it
// is what the seeder would be running after applying, which is what the banner
// promises and what updateApply stamps. `releaseVersion` keeps the tag available
// for anyone who wants to name the release itself. They differ whenever a release
// re-attaches an unchanged seeder build; see versionFromAssetName.
function evaluateRelease (release, { currentVersion, platform, arch, installKind } = {}) {
const latestVersion = typeof release?.tag_name === 'string'
const releaseVersion = typeof release?.tag_name === 'string'
? release.tag_name.replace(/^v/i, '')
: null
const asset = selectAsset(release?.assets, platform, arch, installKind)
const sha = asset ? selectSha256For(release?.assets, asset.name) : null
// Fall back to the tag only for an asset whose name carries no version at all.
const latestVersion = (asset && versionFromAssetName(asset.name)) || releaseVersion
return {
currentVersion: currentVersion ?? null,
latestVersion,
releaseVersion,
updateAvailable: !!(latestVersion && asset && isNewer(latestVersion, currentVersion)),
releaseUrl: typeof release?.html_url === 'string' ? release.html_url : null,
assetName: asset?.name ?? null,
@@ -130,4 +161,7 @@ function evaluateRelease (release, { currentVersion, platform, arch, installKind
}
}
module.exports = { parseVersion, compareVersions, isNewer, selectAsset, selectSha256For, evaluateRelease }
module.exports = {
parseVersion, compareVersions, isNewer, selectAsset, selectSha256For,
versionFromAssetName, evaluateRelease,
}
+87 -1
View File
@@ -5,7 +5,8 @@
const test = require('node:test')
const assert = require('node:assert/strict')
const {
parseVersion, compareVersions, isNewer, selectAsset, selectSha256For, evaluateRelease,
parseVersion, compareVersions, isNewer, selectAsset, selectSha256For,
versionFromAssetName, evaluateRelease,
} = require('../src/lib/seederUpdateCheck')
test('parseVersion: v-prefixed, plain, partial, suffixed, junk', () => {
@@ -175,3 +176,88 @@ test('evaluateRelease: malformed release is safe', () => {
assert.equal(r.latestVersion, null)
assert.equal(r.assetUrl, null)
})
// ---- copy-forward: a release may carry a seeder OLDER than its own tag -------
// When nothing under the seeder changed, release.sh re-attaches the previous
// release's installers rather than rebuilding them. The version that matters is
// then the one in the asset's filename, not the release tag.
test('versionFromAssetName: every installer family, and the non-versions', () => {
assert.equal(versionFromAssetName('pearcal-seeder_1.0.37_amd64.deb'), '1.0.37')
assert.equal(versionFromAssetName('PearCalSeeder-1.0.37-arm64.pkg'), '1.0.37')
assert.equal(versionFromAssetName('PearCalSeeder-1.0.37-x64.pkg'), '1.0.37')
assert.equal(versionFromAssetName('PearCalSeeder-Setup-1.0.37.exe'), '1.0.37')
assert.equal(versionFromAssetName('PearCalSeeder-1.0.37-x86_64.AppImage'), '1.0.37')
// Arch tokens carry digits but no dotted triple, so they can't be mistaken
// for a version - this is what makes filename parsing safe here.
assert.equal(versionFromAssetName('PearCalSeeder-x86_64.AppImage'), null)
assert.equal(versionFromAssetName('pearcal-seeder_amd64.deb'), null)
assert.equal(versionFromAssetName(null), null)
})
test('evaluateRelease: a re-attached older seeder does NOT flag an update', () => {
// The bug this guards: tag v1.0.40 carrying the unchanged 1.0.37 installers.
// Comparing against the TAG would tell a 1.0.37 seeder that 1.0.40 is out,
// hand it back the same 1.0.37 build, and repeat forever.
const release = {
tag_name: 'v1.0.40',
html_url: 'https://example/r/v1.0.40',
assets: [
{ name: 'pearcal-seeder_1.0.37_amd64.deb', browser_download_url: 'u/deb' },
{ name: 'pearcal-seeder_1.0.37_amd64.deb.sha256', browser_download_url: 'u/deb.sha' },
],
}
const r = evaluateRelease(release, { currentVersion: '1.0.37', platform: 'linux', arch: 'x64', installKind: 'deb' })
assert.equal(r.latestVersion, '1.0.37', 'reports the installable version, not the tag')
assert.equal(r.releaseVersion, '1.0.40', 'the tag stays available separately')
assert.equal(r.updateAvailable, false)
})
test('evaluateRelease: a re-attached seeder still updates a genuinely older one', () => {
const release = {
tag_name: 'v1.0.40',
assets: [
{ name: 'pearcal-seeder_1.0.37_amd64.deb', browser_download_url: 'u/deb' },
{ name: 'pearcal-seeder_1.0.37_amd64.deb.sha256', browser_download_url: 'u/deb.sha' },
],
}
const r = evaluateRelease(release, { currentVersion: '1.0.35', platform: 'linux', arch: 'x64', installKind: 'deb' })
assert.equal(r.latestVersion, '1.0.37')
assert.equal(r.updateAvailable, true)
assert.equal(r.sha256Url, 'u/deb.sha', 'sidecar still resolves for the reused asset')
})
test('evaluateRelease: a freshly built seeder matching its tag is unaffected', () => {
const release = {
tag_name: 'v1.0.40',
assets: [
{ name: 'PearCalSeeder-1.0.40-x86_64.AppImage', browser_download_url: 'u/app' },
{ name: 'PearCalSeeder-1.0.40-x86_64.AppImage.sha256', browser_download_url: 'u/app.sha' },
],
}
const r = evaluateRelease(release, { currentVersion: '1.0.37', platform: 'linux', arch: 'x64' })
assert.equal(r.latestVersion, '1.0.40')
assert.equal(r.releaseVersion, '1.0.40')
assert.equal(r.updateAvailable, true)
})
test('evaluateRelease: an unversioned asset name still falls back to the tag', () => {
// Pre-1.0.38 AppImages carried no version. They must keep behaving as before
// rather than losing their version entirely.
const release = {
tag_name: 'v1.0.40',
assets: [{ name: 'PearCalSeeder-x86_64.AppImage', browser_download_url: 'u/app' }],
}
const r = evaluateRelease(release, { currentVersion: '1.0.37', platform: 'linux', arch: 'x64' })
assert.equal(r.latestVersion, '1.0.40')
assert.equal(r.updateAvailable, true)
})
test('selectAsset: the versioned AppImage name still matches arch', () => {
const assets = [
{ name: 'PearCalSeeder-1.0.38-x86_64.AppImage', browser_download_url: 'u/x64' },
{ name: 'PearCalSeeder-1.0.38-aarch64.AppImage', browser_download_url: 'u/arm' },
]
assert.equal(selectAsset(assets, 'linux', 'x64').browser_download_url, 'u/x64')
assert.equal(selectAsset(assets, 'linux', 'arm64').browser_download_url, 'u/arm')
})