Skip to content

fix: retry stalled Bun installs #216

fix: retry stalled Bun installs

fix: retry stalled Bun installs #216

name: Nightly desktop app build
permissions:
contents: write
on:
push:
branches:
- main
schedule:
# 04:00 UTC daily — offset from the 00:00 UTC daily-ci-checks run so
# they don't compete for runners. Produces fresh downloadable bundles
# off latest main each night.
- cron: '0 4 * * *'
# Lets maintainers kick off a build on demand (handy before tagging a release).
workflow_dispatch:
inputs:
update_track:
description: "Desktop update track to build"
type: choice
options:
- nightly
- stable
default: nightly
app_version:
description: "Stable semver to publish, required when update_track=stable"
type: string
default: ""
publish_updates:
description: "Publish Velopack feed assets when running on main"
type: boolean
default: true
# Run on PRs that touch the bundling plumbing so reviewers get downloadable
# test builds without waiting for a nightly. Scoped to avoid wasting CI
# on unrelated PRs.
pull_request:
paths:
- 'lively.app/**'
- '.github/workflows/build-desktop-app.yml'
- 'install.sh'
- 'lively.installer/packages-config.json'
- 'flatn/**'
concurrency:
group: build-desktop-app-${{ github.ref_name }}
cancel-in-progress: true
env:
VELOPACK_VERSION: 0.0.1444-gc245055
jobs:
metadata:
name: Desktop update metadata
runs-on: ubuntu-latest
outputs:
version: ${{ steps.meta.outputs.version }}
track: ${{ steps.meta.outputs.track }}
release_tag: ${{ steps.meta.outputs.release_tag }}
release_title: ${{ steps.meta.outputs.release_title }}
update_url: ${{ steps.meta.outputs.update_url }}
publish_updates: ${{ steps.meta.outputs.publish_updates }}
steps:
- name: Compute update metadata
id: meta
env:
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
RUN_NUMBER: ${{ github.run_number }}
RUN_ATTEMPT: ${{ github.run_attempt }}
INPUT_TRACK: ${{ inputs.update_track }}
INPUT_VERSION: ${{ inputs.app_version }}
INPUT_PUBLISH_UPDATES: ${{ inputs.publish_updates }}
run: |
set -euo pipefail
track="${INPUT_TRACK:-nightly}"
if [ "$EVENT_NAME" != "workflow_dispatch" ]; then
track="nightly"
fi
if [ "$track" != "nightly" ] && [ "$track" != "stable" ]; then
echo "Unsupported update track: $track" >&2
exit 1
fi
if [ "$track" = "stable" ]; then
version="$INPUT_VERSION"
if [ -z "$version" ]; then
echo "workflow_dispatch input app_version is required for stable builds" >&2
exit 1
fi
else
version="0.1.0-nightly.${RUN_NUMBER}.${RUN_ATTEMPT}"
fi
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "Velopack requires a semver2 version, got: $version" >&2
exit 1
fi
publish_updates=false
if [ "$REF_NAME" = "main" ] && [ "$EVENT_NAME" != "pull_request" ]; then
publish_updates=true
fi
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$INPUT_PUBLISH_UPDATES" = "false" ]; then
publish_updates=false
fi
release_tag="lively-next-$track"
release_title="lively.next $track"
update_url=""
if [ "$publish_updates" = "true" ]; then
update_url="https://github.com/$REPOSITORY/releases/download/$release_tag"
fi
{
echo "version=$version"
echo "track=$track"
echo "release_tag=$release_tag"
echo "release_title=$release_title"
echo "update_url=$update_url"
echo "publish_updates=$publish_updates"
} >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.bundle }}
needs: metadata
strategy:
# One platform failing shouldn't abort the others — each bundle is
# independent, and we want whatever OSes build to still publish.
fail-fast: false
matrix:
include:
- os: ubuntu-latest
bundle: lively.next-linux-x64
target_platform: linux
target_arch: x64
archive_ext: tar.gz
- os: ubuntu-latest
# Cross-compile Windows from Linux. build.mjs just downloads
# the Windows NW.js + Node.js binaries and stages them; no
# native Windows runner involved (install.sh doesn't cleanly
# run on Windows due to Git Bash path mangling).
bundle: lively.next-win-x64
target_platform: win
target_arch: x64
archive_ext: zip
- os: macos-latest
bundle: lively.next-osx-arm64
target_platform: osx
target_arch: arm64
archive_ext: tar.gz
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '25'
- name: Setup Python
# node-gyp@9 (pulled in by leveldown etc.) imports
# distutils.version, which was removed in Python 3.12. macOS
# runners ship Python 3.14 by default, so pin an older version
# that still has distutils. Ubuntu's default is currently fine
# but the pin keeps it future-proof.
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1
- name: Restore lively.next dep cache
id: cache-lively-deps
uses: actions/cache/restore@v3
env:
cache-name: lively-deps
with:
path: |
lively.next-node_modules/
.puppeteer-browser-cache/
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('lively*/package.json', 'flatn/package.json', 'mocha-es6/package.json') }}
- name: Install lively.next
uses: nick-fields/retry@v3
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
shell: bash
# --no-desktop skips the NW.js SDK fetch inside install.sh.
# The build step below fetches its own Normal-flavor NW.js for
# the matrix-target platform directly into dist/.
command: chmod +x ./install.sh && ./install.sh --no-desktop
- name: Save lively.next dep cache
if: ${{ steps.cache-lively-deps.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
env:
cache-name: lively-deps
with:
path: |
lively.next-node_modules/
.puppeteer-browser-cache/
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('lively*/package.json', 'flatn/package.json', 'mocha-es6/package.json') }}
- name: Install icon-generation tools
# librsvg2-bin → rsvg-convert (SVG rasterizer)
# imagemagick → `magick`/`convert` for .ico
# .icns is built by scripts/build-icns.mjs — pure Node, no system dep
# needed (icnsutils / libicns-utils was removed from Ubuntu 24.04).
run: |
if [ "${{ runner.os }}" = "Linux" ]; then
sudo apt-get update
sudo apt-get install -y librsvg2-bin imagemagick xvfb
else
brew install librsvg imagemagick
fi
- name: Generate app icons
run: bash ./lively.app/scripts/generate-icons.sh
- name: Build freezer pages
# Build landing-page and loading-screen through the unified freezer
# pipeline so the desktop bundle uses the SWC freezer transform path.
run: env CI=true npm --silent --prefix lively.freezer run build
- name: Pre-build library snapshot
# Ship the tar.gz that dav.js would otherwise regenerate on every
# server launch (~3-5s on a fresh machine). Start-server.cjs sets
# LIVELY_PREBUILT_LIBRARY_SNAPSHOT and dav.js reads the file into
# memStore directly.
run: node ./lively.server/scripts/build-library-snapshot.cjs
- name: Build desktop bundle
env:
PACK: '1'
# SDK flavor ships Chromium DevTools. Costs ~20% more bundle
# size but makes every "blank screen, no clue why" bug
# inspectable via Cmd+Opt+I / right-click → Inspect. Switch
# back to FLAVOR=normal once the app is stable.
FLAVOR: sdk
# Stamp the bundle with the commit so user bug reports can
# tell us which version they tested without guessing. For PR
# builds use the head SHA, not GitHub's synthetic merge SHA.
LIVELY_APP_BUILD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
LIVELY_APP_VERSION: ${{ needs.metadata.outputs.version }}
LIVELY_APP_UPDATE_CHANNEL: ${{ needs.metadata.outputs.track }}-${{ matrix.target_platform }}-${{ matrix.target_arch }}
LIVELY_APP_UPDATE_URL: ${{ needs.metadata.outputs.update_url }}
run: node ./lively.app/scripts/build.mjs --platform=${{ matrix.target_platform }} --arch=${{ matrix.target_arch }}
- name: Smoke desktop app launch
if: ${{ matrix.target_platform != 'win' }}
run: |
if [ "${{ matrix.target_platform }}" = "linux" ]; then
xvfb-run -a --server-args="-screen 0 1440x900x24" \
node ./lively.app/scripts/smoke-desktop-bundle.mjs \
--bundleDir="dist/${{ matrix.bundle }}" \
--platform=${{ matrix.target_platform }}
else
node ./lively.app/scripts/smoke-desktop-bundle.mjs \
--bundleDir="dist/${{ matrix.bundle }}" \
--platform=${{ matrix.target_platform }}
fi
- name: Setup .NET for Velopack
# vpk pack is platform-native. Windows packaging is handled by the
# package-windows-velopack job from the cross-built raw Windows bundle.
if: ${{ matrix.target_platform != 'win' }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Install Velopack CLI
if: ${{ matrix.target_platform != 'win' }}
run: |
dotnet tool install --global vpk --version "$VELOPACK_VERSION"
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
- name: Build Velopack package
if: ${{ matrix.target_platform != 'win' }}
env:
LIVELY_APP_VERSION: ${{ needs.metadata.outputs.version }}
LIVELY_APP_UPDATE_CHANNEL: ${{ needs.metadata.outputs.track }}-${{ matrix.target_platform }}-${{ matrix.target_arch }}
run: node ./lively.app/scripts/build-velopack.mjs --platform=${{ matrix.target_platform }} --arch=${{ matrix.target_arch }}
- name: Smoke Velopack update check
if: ${{ matrix.target_platform == 'osx' }}
run: |
node ./lively.app/scripts/smoke-velopack-updates.mjs \
--artifactDir=dist/velopack/${{ matrix.bundle }} \
--packagesDir="$RUNNER_TEMP/lively-next-velopack-packages"
- name: Upload bundle artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.bundle }}
path: dist/${{ matrix.bundle }}.${{ matrix.archive_ext }}
# Free-tier retention; bump or cut per project policy.
retention-days: 30
if-no-files-found: error
- name: Upload Velopack feed artifact
if: ${{ matrix.target_platform != 'win' }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.bundle }}-velopack-feed
path: |
dist/velopack/${{ matrix.bundle }}/*.nupkg
dist/velopack/${{ matrix.bundle }}/RELEASES-*
dist/velopack/${{ matrix.bundle }}/assets.*.json
dist/velopack/${{ matrix.bundle }}/releases.*.json
retention-days: 30
if-no-files-found: error
- name: Upload installer artifact
if: ${{ matrix.target_platform != 'win' }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.bundle }}-installer
path: |
dist/velopack/${{ matrix.bundle }}/*.AppImage
dist/velopack/${{ matrix.bundle }}/*-Setup.pkg
retention-days: 30
if-no-files-found: error
package-windows-velopack:
name: Package lively.next-win-x64 Velopack
needs:
- metadata
- build
# The raw Windows bundle is cross-built by one matrix leg. Do not block
# native Windows packaging/smoke on unrelated Linux/macOS bundle failures;
# if the Windows artifact is missing, the download step below will fail.
if: ${{ always() && needs.metadata.result == 'success' && needs.build.result != 'cancelled' }}
runs-on: windows-latest
defaults:
run:
shell: pwsh
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '25'
- name: Setup .NET for Velopack
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Install Velopack CLI
run: |
dotnet tool install --global vpk --version "$env:VELOPACK_VERSION"
"$env:USERPROFILE\.dotnet\tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Download raw Windows bundle
uses: actions/download-artifact@v4
with:
name: lively.next-win-x64
path: dist
- name: Extract raw Windows bundle
run: |
tar -xf dist/lively.next-win-x64.zip -C dist
if (-not (Test-Path dist/lively.next-win-x64/lively.next.exe)) {
throw "Extracted Windows bundle is missing lively.next.exe"
}
- name: Smoke desktop app launch
run: |
node ./lively.app/scripts/smoke-desktop-bundle.mjs `
--bundleDir="$PWD/dist/lively.next-win-x64" `
--platform=win
- name: Build Windows Velopack package
env:
LIVELY_APP_VERSION: ${{ needs.metadata.outputs.version }}
LIVELY_APP_UPDATE_CHANNEL: ${{ needs.metadata.outputs.track }}-win-x64
run: |
node ./lively.app/scripts/build-velopack.mjs `
--platform=win `
--arch=x64 `
--bundleDir="$PWD/dist/lively.next-win-x64"
- name: Upload Windows Velopack feed artifact
uses: actions/upload-artifact@v4
with:
name: lively.next-win-x64-velopack-feed
path: |
dist/velopack/lively.next-win-x64/*.nupkg
dist/velopack/lively.next-win-x64/RELEASES-*
dist/velopack/lively.next-win-x64/assets.*.json
dist/velopack/lively.next-win-x64/releases.*.json
retention-days: 30
if-no-files-found: error
- name: Upload Windows installer artifact
uses: actions/upload-artifact@v4
with:
name: lively.next-win-x64-installer
path: |
dist/velopack/lively.next-win-x64/*Setup*.exe
dist/velopack/lively.next-win-x64/*Setup*.msi
retention-days: 30
if-no-files-found: error
publish-velopack-feed:
name: Publish Velopack update feed
needs:
- metadata
- build
- package-windows-velopack
if: ${{ needs.metadata.outputs.publish_updates == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Download Velopack feed artifacts
uses: actions/download-artifact@v4
with:
pattern: lively.next-*-velopack-feed
path: dist/velopack-release
merge-multiple: true
- name: Download installer artifacts
uses: actions/download-artifact@v4
with:
pattern: lively.next-*-installer
path: dist/velopack-release
merge-multiple: true
- name: Publish rolling update release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ needs.metadata.outputs.release_tag }}
RELEASE_TITLE: ${{ needs.metadata.outputs.release_title }}
UPDATE_TRACK: ${{ needs.metadata.outputs.track }}
APP_VERSION: ${{ needs.metadata.outputs.version }}
UPDATE_URL: ${{ needs.metadata.outputs.update_url }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
mapfile -d '' assets < <(find dist/velopack-release -type f -print0 | sort -z)
if [ "${#assets[@]}" -eq 0 ]; then
echo "No Velopack assets found to publish" >&2
exit 1
fi
notes=$(printf 'Rolling %s desktop update feed.\n\nVersion: %s\nCommit: %s\nFeed URL: %s\n' \
"$UPDATE_TRACK" "$APP_VERSION" "$GITHUB_SHA" "$UPDATE_URL")
release_flags=()
if [ "$UPDATE_TRACK" = "nightly" ]; then
release_flags=(--prerelease --latest=false)
else
release_flags=(--latest)
fi
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$RELEASE_TITLE" \
--notes "$notes" \
"${release_flags[@]}"
else
gh release create "$RELEASE_TAG" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "$RELEASE_TITLE" \
--notes "$notes" \
"${release_flags[@]}"
fi
gh release upload "$RELEASE_TAG" "${assets[@]}" \
--repo "$GITHUB_REPOSITORY" \
--clobber
- name: Verify published feed files
env:
UPDATE_TRACK: ${{ needs.metadata.outputs.track }}
UPDATE_URL: ${{ needs.metadata.outputs.update_url }}
run: |
set -euo pipefail
for channel in "$UPDATE_TRACK-linux-x64" "$UPDATE_TRACK-osx-arm64" "$UPDATE_TRACK-win-x64"; do
for file in "releases.$channel.json" "assets.$channel.json" "RELEASES-$channel"; do
curl --retry 3 --retry-delay 2 --fail --silent --show-error --location \
--output /dev/null "$UPDATE_URL/$file"
done
done