mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
* chore(endpoints): call orca.dev for feedback, plugin lists and update notes The site moved to orca.dev. Feedback, connection diagnostics, plugin install stats, the plugin kill list, update nudges and the changelog feed now use orca.dev, as do the diagnostics token URL baked into builds and the release download mirror (downloads.orca.dev). The old hosts serve the same paths, so released builds keep working. Skill share links on share.orca.dev are accepted, and the mobile scanner recognizes the orca.dev Android download code. The old hosts stay accepted. * chore(ci): leave the release JavaScript benchmark workflow unchanged Editing it triggers the benchmark, whose parity check currently fails on arm64 because max-quality brotli output for the mobile page differs by architecture. The benchmark only needs some diagnostics URL, and the old host still serves it.
670 lines
31 KiB
YAML
670 lines
31 KiB
YAML
name: Adhoc macOS + Windows Dev Build
|
||
|
||
# Build trusted repo refs on demand: signed/notarized macOS and unsigned Windows, with qualified orcad slots; PR/release pipelines own other gates. Publish to orca-adhoc so branch experiments cannot enter main’s hourly channel or crowd stable/RC release feeds. Dispatch main’s workflow with -f ref=my-branch to avoid stale YAML. Writes use the hourly GitHub App installed on orca-adhoc (setup-hourly-release-token.sh); GITHUB_TOKEN cannot publish cross-repo. Ref vetting rejects PR refs and unreachable SHAs before source runs beside signing secrets.
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
ref:
|
||
# Default empty ref to the dispatch picker; prefer main’s workflow plus explicit branch input to build old branches with current YAML.
|
||
description: 'Branch, tag, or SHA to build — must live in stablyai/orca; PR refs are refused (default: the branch selected above)'
|
||
required: false
|
||
default: ''
|
||
type: string
|
||
label:
|
||
description: 'Short name shown in the release title (default: the ref)'
|
||
required: false
|
||
default: ''
|
||
type: string
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
concurrency:
|
||
# Serialize per ref: queue repeat builds while independent branches avoid each other’s notary wait.
|
||
group: adhoc-mac-build-${{ inputs.ref || github.ref_name }}
|
||
cancel-in-progress: false
|
||
|
||
env:
|
||
ORCA_BACKGROUND_LAUNCH: '1'
|
||
ADHOC_REPO: stablyai/orca-adhoc
|
||
# Retain by month rather than count so bursts cannot evict recent experiments or sparse periods keep them forever.
|
||
ADHOC_RETAIN_DAYS: 30
|
||
|
||
jobs:
|
||
# Resolve once before source execution so concurrent pushes cannot mix slot/template/desktop commits.
|
||
resolve-ref:
|
||
if: github.repository == 'stablyai/orca'
|
||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||
timeout-minutes: 5
|
||
permissions:
|
||
contents: read
|
||
outputs:
|
||
sha: ${{ steps.resolve.outputs.sha }}
|
||
steps:
|
||
- name: Resolve the requested ref to one commit
|
||
id: resolve
|
||
shell: bash
|
||
env:
|
||
REQUESTED_REF: ${{ inputs.ref || github.ref_name }}
|
||
REPO_URL: https://github.com/${{ github.repository }}
|
||
run: &vet-repository-ref |
|
||
set -euo pipefail
|
||
case "$REQUESTED_REF" in
|
||
refs/pull/*|pull/*)
|
||
echo "::error::Refusing to build PR ref '$REQUESTED_REF': this workflow signs with release credentials, so it only builds branches, tags, or commits of stablyai/orca. Push the code to a branch of this repo instead."
|
||
exit 1
|
||
;;
|
||
esac
|
||
# Bare avoids fetching over a checked-out branch; tree:0 fetches only the commit graph.
|
||
# Reftable preserves case-only branch names that make the files backend fail on case-insensitive disks.
|
||
scratch="$RUNNER_TEMP/vet-requested-ref"
|
||
git init -q --bare --ref-format=reftable "$scratch"
|
||
git -C "$scratch" fetch -q --filter=tree:0 "$REPO_URL" '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'
|
||
# Resolve branches before tags, matching actions/checkout’s tie-break.
|
||
sha=""
|
||
case "$REQUESTED_REF" in
|
||
refs/heads/*|refs/tags/*) candidates=("$REQUESTED_REF") ;;
|
||
*) candidates=("refs/heads/$REQUESTED_REF" "refs/tags/$REQUESTED_REF") ;;
|
||
esac
|
||
if [[ "$REQUESTED_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||
candidates+=("$REQUESTED_REF")
|
||
fi
|
||
for cand in "${candidates[@]}"; do
|
||
if ! [[ "$cand" =~ ^[0-9a-fA-F]{40}$ ]] && ! git check-ref-format "$cand"; then
|
||
continue
|
||
fi
|
||
if sha="$(git -C "$scratch" rev-parse --verify --quiet --end-of-options "$cand^{commit}")"; then
|
||
break
|
||
fi
|
||
sha=""
|
||
done
|
||
if [[ -z "$sha" ]]; then
|
||
echo "::error::'$REQUESTED_REF' does not resolve to a branch, tag, or commit of stablyai/orca."
|
||
exit 1
|
||
fi
|
||
# Lazy-fetchable SHAs can be PR-only; require reachability from a trusted branch/tag, not local object presence.
|
||
if [[ -z "$(git -C "$scratch" for-each-ref --count=1 --format='%(refname)' --contains "$sha" refs/heads refs/tags)" ]]; then
|
||
echo "::error::Commit $REQUESTED_REF is not reachable from any branch or tag of stablyai/orca; refusing to build it with release credentials."
|
||
exit 1
|
||
fi
|
||
echo "Vetted $REQUESTED_REF -> $sha"
|
||
echo "sha=$sha" >>"$GITHUB_OUTPUT"
|
||
|
||
# Resolve shared build identity while detecting whether this ref ships a template.
|
||
orcad-template-support:
|
||
needs: resolve-ref
|
||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||
timeout-minutes: 5
|
||
permissions:
|
||
contents: read
|
||
outputs:
|
||
ships: ${{ steps.detect.outputs.ships }}
|
||
version: ${{ steps.adhoc.outputs.version }}
|
||
commit: ${{ steps.adhoc.outputs.commit }}
|
||
name: ${{ steps.adhoc.outputs.name }}
|
||
steps:
|
||
- name: Checkout template detection and version scripts
|
||
uses: actions/checkout@v6
|
||
with:
|
||
ref: ${{ needs.resolve-ref.outputs.sha }}
|
||
sparse-checkout: |
|
||
/config/scripts/
|
||
/package.json
|
||
/config/tsconfig.relay.json
|
||
sparse-checkout-cone-mode: false
|
||
persist-credentials: false
|
||
# Why: a relay-era ref (before #27215) needs the Windows relay addon this build no longer compiles.
|
||
- name: Refuse a ref that still ships the SSH relay
|
||
shell: bash
|
||
run: |
|
||
if [[ -e config/tsconfig.relay.json ]]; then
|
||
echo "::error::This ref predates #27215 and still ships the SSH relay; build a commit at or after 2b4b60a87e."
|
||
exit 1
|
||
fi
|
||
- name: Detect whether the ref ships the orcad template
|
||
id: detect
|
||
shell: bash
|
||
run: |
|
||
if [[ -f config/scripts/orcad-distribution/template/packaged-orcad-template.cjs || -f config/scripts/packaged-orcad-template.cjs ]]; then
|
||
echo "ships=true" >>"$GITHUB_OUTPUT"
|
||
else
|
||
echo "ships=false" >>"$GITHUB_OUTPUT"
|
||
echo "::notice::This ref predates the orcad template; the build ships without it."
|
||
fi
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v6
|
||
with:
|
||
node-version-file: package.json
|
||
check-latest: true
|
||
package-manager-cache: false
|
||
|
||
- name: Compute adhoc version
|
||
id: adhoc
|
||
shell: bash
|
||
env:
|
||
REF: ${{ inputs.ref || github.ref_name }}
|
||
LABEL: ${{ inputs.label }}
|
||
MAIN_REPO_TOKEN: ${{ github.token }}
|
||
run: |
|
||
set -euo pipefail
|
||
# Old branch refs may predate adhoc version scripts; report that explicitly.
|
||
for script in adhoc-build-version dev-channel-base-version; do
|
||
if [[ ! -f "config/scripts/release/versioning/$script.mjs" && ! -f "config/scripts/$script.mjs" ]]; then
|
||
echo "::error::$REF has no config/scripts/$script.mjs; rebase it onto a main that has the adhoc channel."
|
||
exit 1
|
||
fi
|
||
done
|
||
# Unpublishing deletes releases but leaves version-owning tags; use tags to prevent updater regression.
|
||
# On lookup failure the script falls back to package.json.
|
||
published="$(GH_TOKEN="$MAIN_REPO_TOKEN" gh api \
|
||
"repos/$GITHUB_REPOSITORY/git/matching-refs/tags/v" \
|
||
--jq '.[].ref | sub("^refs/tags/"; "")' || true)"
|
||
ORCA_PUBLISHED_VERSIONS="$published" ORCA_ADHOC_LABEL="${LABEL:-$REF}" \
|
||
node "$(if [ -f config/scripts/release/versioning/adhoc-build-version.mjs ]; then printf %s config/scripts/release/versioning/adhoc-build-version.mjs; else printf %s config/scripts/adhoc-build-version.mjs; fi)" \
|
||
>"$RUNNER_TEMP/adhoc-identity.txt"
|
||
if ! grep -q '^name=' "$RUNNER_TEMP/adhoc-identity.txt"; then
|
||
echo "::error::adhoc-build-version.mjs emitted no release name; $REF's copy of the script is out of sync with this workflow."
|
||
exit 1
|
||
fi
|
||
cat "$RUNNER_TEMP/adhoc-identity.txt" >>"$GITHUB_OUTPUT"
|
||
|
||
# Ship managed-SSH orcad template from qualified per-target lanes at the vetted ref, as release-cut does.
|
||
orcad-template:
|
||
needs: [resolve-ref, orcad-template-support]
|
||
if: needs.orcad-template-support.outputs.ships == 'true'
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
uses: ./.github/workflows/qualified-orcad-template.yml
|
||
with:
|
||
ref: ${{ needs.resolve-ref.outputs.sha }}
|
||
fast_runners: true
|
||
|
||
release-javascript:
|
||
needs: [resolve-ref, orcad-template-support]
|
||
uses: ./.github/workflows/release-javascript.yml
|
||
with:
|
||
ref: ${{ needs.resolve-ref.outputs.sha }}
|
||
dev_build: true
|
||
runner: blacksmith-8vcpu-ubuntu-2404
|
||
|
||
prepare-adhoc-mac-release:
|
||
needs: [resolve-ref, orcad-template-support, release-javascript]
|
||
if: >-
|
||
!cancelled() && github.repository == 'stablyai/orca' &&
|
||
needs.orcad-template-support.result == 'success' &&
|
||
needs.release-javascript.result == 'success'
|
||
environment: adhoc-mac-build
|
||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||
timeout-minutes: 10
|
||
permissions:
|
||
contents: read
|
||
outputs:
|
||
tag: ${{ steps.release.outputs.tag }}
|
||
steps:
|
||
- name: Mint adhoc repo token
|
||
id: app_token
|
||
uses: actions/create-github-app-token@v2
|
||
with:
|
||
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
|
||
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
|
||
owner: stablyai
|
||
repositories: orca-adhoc
|
||
|
||
- name: Create adhoc release
|
||
id: release
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
TAG: v${{ needs.orcad-template-support.outputs.version }}
|
||
NAME: ${{ needs.orcad-template-support.outputs.name }}
|
||
SHA: ${{ needs.resolve-ref.outputs.sha }}
|
||
REF: ${{ inputs.ref || github.ref_name }}
|
||
# Pass expressions via env so bash parses data rather than substituted script text (zizmor: template-injection).
|
||
ACTOR: ${{ github.actor }}
|
||
run: |
|
||
set -euo pipefail
|
||
short_sha="${SHA:0:12}"
|
||
# Precreate a known tag as draft so builder cannot infer it from package.json.
|
||
# Keep incomplete assets invisible until manifest verification, even if timeout kills the job without cleanup.
|
||
gh release create "$TAG" \
|
||
--repo "$ADHOC_REPO" \
|
||
--title "$NAME" \
|
||
--draft \
|
||
--notes "Adhoc macOS and Windows dev build of \`$REF\` at commit \`$short_sha\`.
|
||
|
||
Built from [\`stablyai/orca@$short_sha\`](https://github.com/stablyai/orca/commit/$SHA), cut by @$ACTOR.
|
||
|
||
**Unlanded and unvetted.** This is somebody's branch, not main. No tests
|
||
ran. The macOS build is signed and notarized like a release, so it installs
|
||
through Orca's in-app updater and opens without a Gatekeeper prompt — but the
|
||
branch may never merge, and this build is deleted after $ADHOC_RETAIN_DAYS days.
|
||
|
||
**Windows builds are unsigned.** They install and update normally once you
|
||
are on one, but a signed Stable or RC build cannot install one through the
|
||
in-app updater — download \`orca-windows-setup.exe\` below and run it once
|
||
(SmartScreen will warn about an unknown publisher). Every later switch,
|
||
including back to Stable, works in-app from there."
|
||
echo "tag=$TAG" >>"$GITHUB_OUTPUT"
|
||
|
||
build-adhoc-mac-arch:
|
||
needs:
|
||
[
|
||
resolve-ref,
|
||
orcad-template-support,
|
||
orcad-template,
|
||
release-javascript,
|
||
prepare-adhoc-mac-release
|
||
]
|
||
# Start after template qualification: overlapping ~2.5 min of preparation left both Macs idle ~7 min.
|
||
if: >-
|
||
!cancelled() && github.repository == 'stablyai/orca' &&
|
||
needs.orcad-template-support.result == 'success' &&
|
||
(needs.orcad-template.result == 'success' || needs.orcad-template-support.outputs.ships != 'true') &&
|
||
needs.release-javascript.result == 'success' &&
|
||
needs.prepare-adhoc-mac-release.result == 'success'
|
||
# Signing environment must restrict deployments to main after MAC_CERTS/password, APPLE_ID/app password/team and hourly App secrets move there; referencing it alone enforces nothing.
|
||
environment: adhoc-mac-build
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
arch: [x64, arm64]
|
||
# Notary waits dominate; 12 vCPUs packaged no faster than the hourly build’s 6.
|
||
runs-on: blacksmith-6vcpu-macos-15
|
||
# Allow 150min for install 3×10 + publish 2×45 + ~25 setup/build/verify; outer timeout would prevent cleanup.
|
||
timeout-minutes: 150
|
||
env:
|
||
JOBS: max
|
||
NODE_OPTIONS: --max-old-space-size=4096
|
||
steps:
|
||
# Vet branch/tag reachability and reject refs/pull before checkout can access signing credentials; checkout the resolved SHA to prevent ref-swap races.
|
||
- name: Vet the requested ref
|
||
id: vetted
|
||
shell: bash
|
||
env:
|
||
# The commit resolve-ref pinned for every job; vetting it keeps the reachability test.
|
||
REQUESTED_REF: ${{ needs.resolve-ref.outputs.sha }}
|
||
REPO_URL: https://github.com/${{ github.repository }}
|
||
run: *vet-repository-ref
|
||
|
||
- name: Checkout the requested ref
|
||
uses: actions/checkout@v6
|
||
with:
|
||
# Explicit source input lets main’s workflow build an unmerged branch rather than run that branch’s stale workflow.
|
||
ref: ${{ steps.vetted.outputs.sha }}
|
||
# Version helpers only read HEAD; published versions come from the release API.
|
||
fetch-depth: 1
|
||
# Read this repo without persisted checkout credentials; all writes use an env-passed App token for orca-adhoc (zizmor: artipacked).
|
||
persist-credentials: false
|
||
|
||
- name: Setup pnpm
|
||
uses: pnpm/setup@v2
|
||
with:
|
||
install: false
|
||
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@v6
|
||
with:
|
||
node-version-file: package.json
|
||
check-latest: true
|
||
cache: pnpm
|
||
cache-dependency-path: |
|
||
pnpm-lock.yaml
|
||
mobile/pnpm-lock.yaml
|
||
|
||
# Cache bundled host Node by exact pin, without restore keys, so bumps cannot restore old archives.
|
||
- name: Locate the pinned Node runtime cache
|
||
shell: bash
|
||
run: echo "ORCA_NODE_RUNTIME_CACHE_DIR=$HOME/Library/Caches/orca-node-runtime" >>"$GITHUB_ENV"
|
||
|
||
- name: Cache the pinned Node runtime
|
||
uses: actions/cache@v5
|
||
with:
|
||
path: ~/Library/Caches/orca-node-runtime
|
||
key: orca-node-runtime-mac-${{ hashFiles('src/shared/node-runtime-pin.ts') }}
|
||
|
||
- name: Cache electron-builder downloads
|
||
uses: actions/cache@v5
|
||
with:
|
||
path: |
|
||
~/Library/Caches/electron
|
||
~/Library/Caches/electron-builder
|
||
key: electron-builder-mac-${{ hashFiles('pnpm-lock.yaml') }}
|
||
restore-keys: |
|
||
electron-builder-mac-
|
||
|
||
# Install both x64/arm64 optional natives because this arm64 runner packages both.
|
||
- name: Install dependencies
|
||
uses: nick-fields/retry@v4
|
||
with:
|
||
timeout_minutes: 10
|
||
max_attempts: 3
|
||
retry_wait_seconds: 30
|
||
command: pnpm install --frozen-lockfile --cpu=current,x64,arm64
|
||
|
||
# Build required out/mobile-web after installing its React Native/Expo dependencies.
|
||
- uses: ./.github/actions/install-mobile-dependencies
|
||
if: needs.release-javascript.outputs.supported != 'true'
|
||
|
||
# Require signing cert before expensive build; unsigned macOS cannot update an existing install correctly.
|
||
- name: Verify macOS signing environment
|
||
run: node config/scripts/desktop-distribution/macos/verify-macos-release-env.mjs
|
||
env:
|
||
CSC_LINK: ${{ secrets.MAC_CERTS }}
|
||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
|
||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
|
||
- name: Download shared JavaScript
|
||
if: needs.release-javascript.outputs.supported == 'true'
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
name: release-javascript
|
||
path: .build/release-javascript
|
||
|
||
- name: Build app
|
||
run: |
|
||
if [[ "$ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA" != '' ]]; then
|
||
RELEASE_JAVASCRIPT_ARTIFACT='config/scripts/release/javascript-artifacts/release-javascript-artifact.mjs'
|
||
if [ ! -f "$RELEASE_JAVASCRIPT_ARTIFACT" ]; then
|
||
RELEASE_JAVASCRIPT_ARTIFACT='config/scripts/release-javascript-artifact.mjs'
|
||
fi
|
||
node "$RELEASE_JAVASCRIPT_ARTIFACT" restore
|
||
pnpm run build:release:host
|
||
else
|
||
pnpm build:release
|
||
fi
|
||
env:
|
||
ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ needs.release-javascript.outputs.supported == 'true' && needs.release-javascript.outputs.source_sha || '' }}
|
||
ORCA_RELEASE_JAVASCRIPT_BUILD_IDENTITY: dev
|
||
NODE_OPTIONS: --max-old-space-size=4096
|
||
# Leave build identity unset: telemetry accepts only official stable/rc, so branch artifacts stay silent.
|
||
ORCA_DIAGNOSTICS_TOKEN_URL: https://orca.dev/diagnostics/token
|
||
|
||
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
|
||
- name: Download the orcad deployment template
|
||
if: needs.orcad-template-support.outputs.ships == 'true'
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
name: orcad-template
|
||
path: out/orcad-template
|
||
|
||
# Mint the one-hour write token after build, just before potentially long notarization/publish.
|
||
- name: Checkout workflow packaging scripts
|
||
uses: actions/checkout@v6
|
||
with:
|
||
ref: ${{ github.workflow_sha }}
|
||
path: .workflow-tools
|
||
sparse-checkout: |
|
||
config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs
|
||
config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs
|
||
src/shared/macos-darwin-floor.ts
|
||
sparse-checkout-cone-mode: false
|
||
persist-credentials: false
|
||
|
||
- name: Package and notarize this Mac architecture
|
||
uses: nick-fields/retry@v4
|
||
with:
|
||
# Allow two 45min pack/notarize attempts; an adhoc user can redispatch rather than pay for a third.
|
||
timeout_minutes: 45
|
||
max_attempts: 2
|
||
retry_wait_seconds: 30
|
||
command: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON "$(if [ -f config/scripts/orcad-distribution/prebuilds/pinned-node-downloads.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/pinned-node-downloads.mjs; else printf %s config/scripts/pinned-node-downloads.mjs; fi)" darwin-arm64 darwin-x64 && node "$(if [ -f config/scripts/native-build/node-addons/ensure-native-runtime.mjs ]; then printf %s config/scripts/native-build/node-addons/ensure-native-runtime.mjs; else printf %s config/scripts/ensure-native-runtime.mjs; fi)" --runtime=electron && ORCA_MAC_ADHOC=1 pnpm exec electron-builder --config config/electron-builder.config.cjs --mac dmg zip --${{ matrix.arch }} --publish never
|
||
env:
|
||
ORCA_ADHOC_BUILD_VERSION: ${{ needs.orcad-template-support.outputs.version }}
|
||
ORCA_BUILD_COMMIT: ${{ needs.orcad-template-support.outputs.commit }}
|
||
# beforePack and afterPack fail the package when the template is absent.
|
||
ORCA_REQUIRE_ORCAD_TEMPLATE: ${{ needs.orcad-template-support.outputs.ships == 'true' && '1' || '' }}
|
||
CSC_LINK: ${{ secrets.MAC_CERTS }}
|
||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
|
||
# Require Apple id/app-specific password/team: notarization authenticates separately from signing cert.
|
||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||
|
||
- name: Verify signed and stapled app
|
||
shell: bash
|
||
env:
|
||
MAC_ARCH: ${{ matrix.arch }}
|
||
run: |
|
||
set -euo pipefail
|
||
app_dir=dist/mac
|
||
if [[ "$MAC_ARCH" == arm64 ]]; then app_dir=dist/mac-arm64; fi
|
||
codesign --verify --deep --strict "$app_dir/Orca.app"
|
||
xcrun stapler validate "$app_dir/Orca.app"
|
||
|
||
- name: Verify this architecture's updater files
|
||
env:
|
||
MAC_ARCH: ${{ matrix.arch }}
|
||
MAC_VERSION: ${{ needs.orcad-template-support.outputs.version }}
|
||
run: |
|
||
set -euo pipefail
|
||
# Builder cannot write this field, so older macOS updaters would install a build that won't launch.
|
||
node .workflow-tools/config/scripts/desktop-distribution/macos/gate-mac-update-manifests.mjs dist
|
||
node .workflow-tools/config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs stage "$MAC_ARCH" "$MAC_VERSION" dist .build/mac-manifests
|
||
|
||
- name: Mint adhoc repo token
|
||
id: app_token
|
||
uses: actions/create-github-app-token@v2
|
||
with:
|
||
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
|
||
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
|
||
owner: stablyai
|
||
repositories: orca-adhoc
|
||
|
||
- name: Upload verified architecture assets to the draft
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
TAG: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
MAC_ARCH: ${{ matrix.arch }}
|
||
run: |
|
||
set -euo pipefail
|
||
files=()
|
||
while IFS= read -r file; do files+=("dist/$file"); done < ".build/mac-manifests/$MAC_ARCH-assets.txt"
|
||
gh release upload "$TAG" "${files[@]}" --repo "$ADHOC_REPO" --clobber
|
||
|
||
- name: Stage verified architecture manifest
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: adhoc-mac-manifest-${{ matrix.arch }}
|
||
path: .build/mac-manifests/${{ matrix.arch }}.json
|
||
if-no-files-found: error
|
||
retention-days: 1
|
||
|
||
build-adhoc-mac:
|
||
needs: [resolve-ref, orcad-template-support, prepare-adhoc-mac-release, build-adhoc-mac-arch]
|
||
if: always() && needs.prepare-adhoc-mac-release.result == 'success'
|
||
environment: adhoc-mac-build
|
||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||
timeout-minutes: 35
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
outputs:
|
||
tag: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
version: ${{ needs.orcad-template-support.outputs.version }}
|
||
head_sha: ${{ needs.resolve-ref.outputs.sha }}
|
||
published: ${{ steps.publish_live.outcome == 'success' && 'true' || 'false' }}
|
||
ships_orcad_template: ${{ needs.orcad-template-support.outputs.ships }}
|
||
steps:
|
||
- name: Mint adhoc repo token
|
||
id: app_token
|
||
uses: actions/create-github-app-token@v2
|
||
with:
|
||
app-id: ${{ secrets.HOURLY_RELEASE_APP_ID }}
|
||
private-key: ${{ secrets.HOURLY_RELEASE_APP_PRIVATE_KEY }}
|
||
owner: stablyai
|
||
repositories: orca-adhoc
|
||
|
||
- name: Require both architecture jobs
|
||
env:
|
||
ARCH_RESULT: ${{ needs.build-adhoc-mac-arch.result }}
|
||
run: test "$ARCH_RESULT" = success
|
||
|
||
- name: Checkout manifest merger
|
||
uses: actions/checkout@v6
|
||
with:
|
||
ref: ${{ github.workflow_sha }}
|
||
sparse-checkout: config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs
|
||
sparse-checkout-cone-mode: false
|
||
persist-credentials: false
|
||
|
||
- name: Download architecture manifests
|
||
uses: actions/download-artifact@v8
|
||
with:
|
||
pattern: adhoc-mac-manifest-*
|
||
merge-multiple: true
|
||
path: .build/mac-manifests
|
||
|
||
- name: Merge and verify the updater manifest
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
TAG: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
MAC_VERSION: ${{ needs.orcad-template-support.outputs.version }}
|
||
run: |
|
||
set -euo pipefail
|
||
gh release view "$TAG" --repo "$ADHOC_REPO" --json assets > .build/mac-manifests/assets.json
|
||
node "$(if [ -f config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs ]; then printf %s config/scripts/desktop-distribution/macos/adhoc-mac-manifests.mjs; else printf %s config/scripts/adhoc-mac-manifests.mjs; fi)" merge "$MAC_VERSION" .build/mac-manifests
|
||
gh release upload "$TAG" .build/mac-manifests/latest-mac.yml --repo "$ADHOC_REPO" --clobber
|
||
|
||
- name: Confirm final template qualification before publication
|
||
if: needs.orcad-template-support.outputs.ships == 'true'
|
||
uses: actions/github-script@v8
|
||
env:
|
||
WAIT_FOR_TEMPLATE_COMPLETION: '1'
|
||
with:
|
||
script: |
|
||
const deadline = Date.now() + 30 * 60_000
|
||
while (Date.now() < deadline) {
|
||
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
|
||
...context.repo, run_id: context.runId, filter: 'latest', per_page: 100
|
||
})
|
||
const lanes = jobs.filter(job => job.name.startsWith('orcad-template / '))
|
||
const failed = lanes.find(job => job.status === 'completed' &&
|
||
['failure', 'cancelled', 'timed_out', 'action_required', 'startup_failure'].includes(job.conclusion))
|
||
if (failed) throw new Error(`Template qualification failed: ${failed.name}`)
|
||
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
|
||
...context.repo, run_id: context.runId, per_page: 100
|
||
})
|
||
const ready = artifacts.some(artifact => artifact.name === 'orcad-template' && !artifact.expired)
|
||
const completed = lanes.length > 0 && lanes.every(job => job.status === 'completed')
|
||
if (ready && (process.env.WAIT_FOR_TEMPLATE_COMPLETION !== '1' || completed)) return
|
||
await new Promise(resolve => setTimeout(resolve, 15_000))
|
||
}
|
||
throw new Error('Timed out waiting for the qualified orcad template')
|
||
|
||
# Publish only after manifest validation so the picker never offers incomplete assets.
|
||
- name: Publish the verified release
|
||
id: publish_live
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
TAG: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
NAME: ${{ needs.orcad-template-support.outputs.name }}
|
||
run: |
|
||
set -euo pipefail
|
||
# Reassert the picker title after electron-builder may rewrite the tag’s draft on upload.
|
||
gh release edit "$TAG" --repo "$ADHOC_REPO" --draft=false --prerelease --title "$NAME"
|
||
echo "Published $TAG as \"$NAME\""
|
||
|
||
# Delete stranded drafts on failure/cancel, but never after publish_live: later prune failure must not remove an installable release.
|
||
- name: Discard the draft release on failure
|
||
if: >-
|
||
(failure() || cancelled()) && needs.prepare-adhoc-mac-release.outputs.tag != '' &&
|
||
steps.publish_live.outcome != 'success'
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
TAG: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
run: |
|
||
set -uo pipefail
|
||
# No --cleanup-tag: an unpublished draft never created a git tag.
|
||
echo "Run failed before publish; discarding draft $TAG"
|
||
gh release delete "$TAG" --repo "$ADHOC_REPO" --yes ||
|
||
echo "::warning::Could not discard draft $TAG; remove it manually."
|
||
|
||
- name: Prune expired adhoc releases
|
||
# Retire only after live publication; preserve this non-draft tag and leave failed runs to draft cleanup.
|
||
if: steps.publish_live.outcome == 'success'
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
||
# Exclude the just-published tag from pruning even if ordering/filter logic regresses.
|
||
TAG: ${{ needs.prepare-adhoc-mac-release.outputs.tag }}
|
||
run: |
|
||
set -euo pipefail
|
||
# Fix/log the cutoff epoch once per dispatch so retention decisions remain auditable.
|
||
cutoff=$(( $(date -u +%s) - ADHOC_RETAIN_DAYS * 86400 ))
|
||
echo "Pruning adhoc releases published before $(date -u -d "@$cutoff" '+%Y-%m-%dT%H:%M:%SZ')"
|
||
# --cleanup-tag removes release-less tags; failure cleanup owns drafts, which retention excludes.
|
||
# Use publishedAt: GitHub’s createdAt is the seed commit’s date for every tag. Aging by it once
|
||
# expired the entire channel in one run; retain releases with unknown publishedAt.
|
||
jq_filter='map(select(.isDraft | not))'
|
||
if [[ -n "${TAG:-}" ]]; then
|
||
jq_filter+=" | map(select(.tagName != \"${TAG//\"/\\\"}\"))"
|
||
fi
|
||
jq_filter+=" | map(select((.publishedAt // \"\") != \"\"))"
|
||
jq_filter+=" | map(select((.publishedAt | fromdateiso8601) < $cutoff)) | .[].tagName"
|
||
stale="$(gh release list --repo "$ADHOC_REPO" --limit 200 --json tagName,publishedAt,isDraft \
|
||
--jq "$jq_filter")"
|
||
if [[ -z "$stale" ]]; then
|
||
echo "Nothing to prune."
|
||
exit 0
|
||
fi
|
||
while read -r tag; do
|
||
[[ -n "$tag" ]] || continue
|
||
# Belt-and-suspenders: the filter above should already exclude $TAG.
|
||
if [[ -n "${TAG:-}" && "$tag" == "$TAG" ]]; then
|
||
echo "::warning::Prune list still included just-published $tag after protect; skipping delete."
|
||
continue
|
||
fi
|
||
echo "Pruning $tag"
|
||
gh release delete "$tag" --repo "$ADHOC_REPO" --yes --cleanup-tag || \
|
||
echo "::warning::Could not prune $tag"
|
||
done <<<"$stale"
|
||
|
||
# Build unsigned Windows artifacts alongside Mac; publication waits below.
|
||
build-adhoc-win:
|
||
needs: [resolve-ref, orcad-template-support, release-javascript]
|
||
if: >-
|
||
!cancelled() && github.repository == 'stablyai/orca' &&
|
||
needs.orcad-template-support.result == 'success' &&
|
||
needs.release-javascript.result == 'success'
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
uses: ./.github/workflows/dev-channel-win-build.yml
|
||
secrets: inherit
|
||
with:
|
||
channel: adhoc
|
||
tag: v${{ needs.orcad-template-support.outputs.version }}
|
||
ref: ${{ needs.resolve-ref.outputs.sha }}
|
||
version: ${{ needs.orcad-template-support.outputs.version }}
|
||
orcad_template: ${{ needs.orcad-template-support.outputs.ships == 'true' }}
|
||
stage_only: true
|
||
fast_release: true
|
||
javascript_source_sha: ${{ needs.release-javascript.outputs.supported == 'true' && needs.release-javascript.outputs.source_sha || '' }}
|
||
|
||
# Prefetch the installer during notarization; the publisher still waits for Mac success.
|
||
publish-adhoc-win:
|
||
needs: [orcad-template-support, build-adhoc-win]
|
||
if: >-
|
||
!cancelled() && needs.orcad-template-support.result == 'success' &&
|
||
needs.build-adhoc-win.result == 'success'
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
uses: ./.github/workflows/dev-channel-win-publish.yml
|
||
secrets: inherit
|
||
with:
|
||
channel: adhoc
|
||
tag: v${{ needs.orcad-template-support.outputs.version }}
|
||
wait_for_mac_job: build-adhoc-mac
|