mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-01 14:59:19 +08:00
856fe0ffb6
* fix(docker): upgrade bun to 1.3.14 to unbreak the Next 16.3.0 server
Bun 1.3.13 cannot load Next 16.3.0's compiled server runtime. The app container
runs the Next server under Bun (`oven/bun:1.3.13-slim`, `bun apps/sim/bootstrap.js`),
so every app-page render threw and `/api/health` returned 500:
⨯ Error: Failed to load external module
next/dist/compiled/next-server/app-page-turbo.runtime.prod.js:
TypeError: Expected CommonJS module to have a function wrapper.
If you weren't messing around with Bun's internals, this is a bug in Bun
Isolated to Bun, not Next, by loading that exact module in the real images:
Next 16.2.12 + Bun 1.3.13 -> loads (why staging was fine before)
Next 16.3.0 + Bun 1.3.13 -> CJS wrapper error
Next 16.3.0 + Bun 1.3.14 -> loads
Bun 1.3.14 is the current stable and already fixes it, so this bumps every pin
rather than reverting the framework upgrade, which would only defer the same
latent Bun bug to the next attempt.
Why no gate caught it: local dev machines and this bump's own verification run
Bun 1.3.14, while the container and CI pinned 1.3.13 — and CI only *builds* the
image, it never boots one and probes `/api/health`. A container smoke test in CI
would have caught this before merge; that is worth adding separately.
* fix(docker): align the remaining bun pins with 1.3.14
Two pins were missed in the first pass because the search was scoped to
docker/, package.json and .github/workflows/:
- .devcontainer/Dockerfile still built on oven/bun:1.3.13-alpine
- PI_BUN_VERSION in apps/sim/scripts/pi-sandbox-packages.ts was still 1.3.13,
despite being documented as mirroring the root packageManager field, so Pi
sandbox images would have kept installing the Bun release that cannot load
the Next 16.3.0 server runtime.
Fixed surgically rather than with a repo-wide replace: "1.3.13" also appears
inside SVG path data in apps/sim/components/icons.tsx and
apps/docs/components/icons.tsx, which a blind sed would have corrupted.
213 lines
7.9 KiB
YAML
213 lines
7.9 KiB
YAML
name: Desktop Release (macOS)
|
|
|
|
# Builds, signs, notarizes, and uploads the desktop app to an existing GitHub
|
|
# release. Ordering is load-bearing: scripts/create-single-release.ts skips
|
|
# creation when the tag already exists, so this workflow must never create the
|
|
# release itself — it only uploads assets after create-release ran (wired via
|
|
# workflow_call from ci.yml with needs: [create-release]).
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: Release tag (vX.Y.Z) to attach desktop artifacts to
|
|
required: true
|
|
type: string
|
|
publish:
|
|
description: Upload artifacts to the GitHub release
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
sign:
|
|
description: Sign and notarize with the Apple Developer identity. When
|
|
false (prerelease testing before the signing secrets exist) the build
|
|
is packaged unsigned; installed shells detect this and offer manual
|
|
downloads instead of Squirrel installs.
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: Release tag (vX.Y.Z) to attach desktop artifacts to
|
|
required: true
|
|
type: string
|
|
publish:
|
|
description: Upload artifacts to the GitHub release
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
sign:
|
|
description: Sign and notarize with the Apple Developer identity
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
build-sign-notarize:
|
|
name: Build, Sign, Notarize
|
|
runs-on: macos-14
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
|
with:
|
|
bun-version: 1.3.14
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Cache Electron binaries
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/Library/Caches/electron
|
|
~/Library/Caches/electron-builder
|
|
key: electron-cache-${{ runner.os }}-${{ hashFiles('apps/desktop/package.json') }}
|
|
|
|
- name: Install dependencies
|
|
run: bun install --frozen-lockfile
|
|
|
|
- name: Inject release version
|
|
env:
|
|
VERSION: ${{ inputs.version }}
|
|
run: |
|
|
SEMVER="${VERSION#v}"
|
|
if ! [[ "$SEMVER" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.].+)?$ ]]; then
|
|
echo "Refusing to build: '$VERSION' is not a vX.Y.Z release tag" >&2
|
|
exit 1
|
|
fi
|
|
npm pkg set version="$SEMVER" --prefix apps/desktop
|
|
INJECTED="$(node -p "require('./apps/desktop/package.json').version")"
|
|
if [ "$INJECTED" != "$SEMVER" ]; then
|
|
echo "Version injection mismatch: wanted $SEMVER got $INJECTED" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Prerelease versions carry their environment in the tag: -alpha.N is a
|
|
# dev build, -beta.N a staging build. The channel decides the app's
|
|
# identity (name/bundle id — a separate app per environment, installable
|
|
# side by side) and the default origin baked into the bundle, which in
|
|
# turn selects the update feed the installed app polls.
|
|
- name: Resolve channel identity
|
|
id: channel
|
|
env:
|
|
VERSION: ${{ inputs.version }}
|
|
run: |
|
|
case "$VERSION" in
|
|
*-alpha.*)
|
|
NAME='Sim Dev'; APP_ID=ai.sim.desktop.dev; ORIGIN=https://www.dev.sim.ai ;;
|
|
*-beta.*)
|
|
NAME='Sim Staging'; APP_ID=ai.sim.desktop.staging; ORIGIN=https://www.staging.sim.ai ;;
|
|
*)
|
|
NAME='Sim'; APP_ID=ai.sim.desktop; ORIGIN='' ;;
|
|
esac
|
|
{
|
|
echo "name=$NAME"
|
|
echo "app_id=$APP_ID"
|
|
echo "origin=$ORIGIN"
|
|
} >> "$GITHUB_OUTPUT"
|
|
echo "Building $NAME ($APP_ID) default origin: ${ORIGIN:-production}"
|
|
|
|
- name: Bundle main and preload
|
|
working-directory: apps/desktop
|
|
env:
|
|
SIM_DESKTOP_DEFAULT_ORIGIN: ${{ steps.channel.outputs.origin }}
|
|
run: bun run build
|
|
|
|
- name: Write App Store Connect API key
|
|
if: ${{ inputs.sign }}
|
|
env:
|
|
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
|
|
run: |
|
|
mkdir -p "$RUNNER_TEMP/appstoreconnect"
|
|
printf '%s' "$APPLE_API_KEY_P8" > "$RUNNER_TEMP/appstoreconnect/AuthKey.p8"
|
|
chmod 600 "$RUNNER_TEMP/appstoreconnect/AuthKey.p8"
|
|
|
|
- name: Package, sign, and notarize
|
|
if: ${{ inputs.sign }}
|
|
working-directory: apps/desktop
|
|
env:
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
# Absolute path — @electron/notarize reads this via Node fs, which
|
|
# does not expand a leading '~'.
|
|
APPLE_API_KEY: ${{ runner.temp }}/appstoreconnect/AuthKey.p8
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
PRODUCT_NAME: ${{ steps.channel.outputs.name }}
|
|
APP_ID: ${{ steps.channel.outputs.app_id }}
|
|
run: >
|
|
bunx electron-builder --mac --publish never
|
|
-c.productName="$PRODUCT_NAME" -c.appId="$APP_ID"
|
|
|
|
# Unsigned prerelease path: no Developer ID, no notarization. The
|
|
# binaries end up ad-hoc/linker-signed, which runs locally but gets
|
|
# quarantined when downloaded — fine for testing the update pipeline.
|
|
- name: Package unsigned
|
|
if: ${{ !inputs.sign }}
|
|
working-directory: apps/desktop
|
|
env:
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
|
PRODUCT_NAME: ${{ steps.channel.outputs.name }}
|
|
APP_ID: ${{ steps.channel.outputs.app_id }}
|
|
run: >
|
|
bunx electron-builder --mac --publish never -c.mac.notarize=false
|
|
-c.productName="$PRODUCT_NAME" -c.appId="$APP_ID"
|
|
|
|
- name: Validate signature and notarization
|
|
if: ${{ inputs.sign }}
|
|
run: |
|
|
DMG="$(ls apps/desktop/release/*.dmg | head -1)"
|
|
xcrun stapler validate "$DMG"
|
|
hdiutil attach "$DMG" -mountpoint /tmp/sim-dmg -nobrowse -quiet
|
|
spctl --assess --type execute --verbose /tmp/sim-dmg/*.app
|
|
codesign --verify --deep --strict /tmp/sim-dmg/*.app
|
|
hdiutil detach /tmp/sim-dmg -quiet
|
|
|
|
- name: Upload artifacts to the release
|
|
if: ${{ inputs.publish }}
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
VERSION: ${{ inputs.version }}
|
|
run: |
|
|
# electron-builder's GitHub provider always names the manifest
|
|
# latest-mac.yml (channels are a generic-provider concept), and the
|
|
# update feed expects exactly that asset name on every release —
|
|
# normalize defensively in case a config change ever produces a
|
|
# channel-named manifest.
|
|
YML="$(find apps/desktop/release -maxdepth 1 -name '*-mac.yml' | head -1)"
|
|
if [ -z "$YML" ]; then
|
|
echo "::error::No *-mac.yml updater manifest found in apps/desktop/release"
|
|
exit 1
|
|
fi
|
|
if [ "$(basename "$YML")" != "latest-mac.yml" ]; then
|
|
mv "$YML" apps/desktop/release/latest-mac.yml
|
|
fi
|
|
gh release upload "$VERSION" \
|
|
apps/desktop/release/*.dmg \
|
|
apps/desktop/release/*.zip \
|
|
apps/desktop/release/*.blockmap \
|
|
apps/desktop/release/latest-mac.yml \
|
|
--clobber
|
|
|
|
- name: Upload artifacts to the workflow run
|
|
if: ${{ !inputs.publish }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: sim-desktop-${{ inputs.version }}
|
|
path: |
|
|
apps/desktop/release/*.dmg
|
|
apps/desktop/release/*.zip
|
|
apps/desktop/release/*.blockmap
|
|
apps/desktop/release/*-mac.yml
|
|
retention-days: 7
|