Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,27 +399,61 @@ jobs:
arch: x64
- os: ubuntu-latest
arch: x64
# GitHub-hosted runners are x64-only; Linux arm64 is emulated via
# QEMU inside a node:22-trixie container so pnpm deploys the
# platform-specific native SDKs (e.g. longbridge's glibc prebuild)
# for arm64.
- os: ubuntu-latest
arch: arm64
runs-on: ${{ matrix.os }}
permissions:
contents: read

steps:
- uses: actions/checkout@v7

- name: Set up QEMU for arm64 emulation
uses: docker/setup-qemu-action@v3
if: matrix.arch == 'arm64'

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm

# arm64 has no native GitHub-hosted runner, so the whole install, build,
# and upgrade-smoke sequence runs inside a linux/arm64 container. The
# workspace is bind-mounted, so dist/broker-packs lands on the runner for
# the artifact step below.
- name: Build Broker Packs (arm64 emulated)
if: matrix.arch == 'arm64'
env:
PREVIOUS_TAG: ${{ needs.release.outputs.previous_tag }}
run: |
docker run --rm --platform linux/arm64 \
--workdir /workspace \
--volume "$PWD:/workspace" \
--env COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
--env PREVIOUS_TAG="$PREVIOUS_TAG" \
node:22-trixie bash -euo pipefail -c '
corepack enable && corepack prepare pnpm@11.7.0 --activate
pnpm install --frozen-lockfile
pnpm broker-packs:build
pnpm broker-packs:upgrade-smoke -- --from "$PREVIOUS_TAG"
'

- name: Install dependencies
if: matrix.arch != 'arm64'
run: pnpm install --frozen-lockfile

- name: Build optional Broker Packs
if: matrix.arch != 'arm64'
run: pnpm broker-packs:build

- name: Prove previous-release Broker Pack upgrade
if: matrix.arch != 'arm64'
run: pnpm broker-packs:upgrade-smoke -- --from "${{ needs.release.outputs.previous_tag }}"

- name: Preserve Broker Packs
Expand Down
7 changes: 4 additions & 3 deletions docs/broker-packs.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ OpenAlice-Broker-Packs-<version>-<platform>-<arch>.json
OpenAlice-Broker-<engine>-<version>-<platform>-<arch>.tgz
```

The release workflow runs this on macOS arm64, macOS x64, Windows x64, and
Linux x64; publishes the files with the desktop release; mirrors them to the
download CDN; and verifies every catalog and referenced archive.
The release workflow runs this on macOS arm64, macOS x64, Windows x64, Linux
x64, and Linux arm64 (emulated via QEMU on an x64 runner); publishes the files
with the desktop release; mirrors them to the download CDN; and verifies every
catalog and referenced archive.

Before a candidate can publish, each platform runner downloads the real Broker
Packs from the previous GitHub Release, activates them in an isolated
Expand Down
26 changes: 23 additions & 3 deletions scripts/broker-pack-upgrade-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ async function main(): Promise<void> {
const candidateCatalog = await readCatalog(
resolve(candidateRoot, brokerPackCatalogFileName(currentVersion)),
)
const previousCatalog = await fetchJson<BrokerPackReleaseCatalog>(
releaseAssetUrl(previousTag, brokerPackCatalogFileName(previousVersion)),
)
// A platform that is new in this release (e.g. Linux arm64) has no
// previous-release catalog to upgrade from. Treat that as a first-release
// skip rather than a failure; any other HTTP error is still fatal.
const previousCatalog = await fetchPreviousCatalog(previousTag, previousVersion)
if (!previousCatalog) return
assertCatalog(previousCatalog, previousVersion)
assertCatalog(candidateCatalog, currentVersion)

Expand Down Expand Up @@ -227,6 +229,24 @@ async function fetchJson<T>(url: string): Promise<T> {
return JSON.parse((await fetchBytes(url)).toString('utf8')) as T
}

async function fetchPreviousCatalog(
tag: string,
version: string,
): Promise<BrokerPackReleaseCatalog | null> {
const url = releaseAssetUrl(tag, brokerPackCatalogFileName(version))
const response = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(120_000) })
if (response.status === 404) {
console.log(
`[broker-pack-upgrade-smoke] previous release ${tag} has no ` +
`${process.platform}-${process.arch} catalog; platform is new in this release — ` +
'skipping previous-release upgrade smoke',
)
return null
}
if (!response.ok) throw new Error(`GET ${url} failed: HTTP ${response.status}`)
return JSON.parse(await response.text()) as BrokerPackReleaseCatalog
}

async function fetchBytes(url: string): Promise<Buffer> {
const response = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(120_000) })
if (!response.ok) throw new Error(`GET ${url} failed: HTTP ${response.status}`)
Expand Down
1 change: 1 addition & 0 deletions scripts/release-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ describe('Release workflow critical path', () => {
{ os: 'macos-15-intel', arch: 'x64' },
{ os: 'windows-latest', arch: 'x64' },
{ os: 'ubuntu-latest', arch: 'x64' },
{ os: 'ubuntu-latest', arch: 'arm64' },
])
expect(step(brokerPacks, 'Preserve Broker Packs').with?.['name']).toBe(
'broker-packs-${{ runner.os }}-${{ matrix.arch }}',
Expand Down