Files
orca/.github/workflows/dev-channel-win-build.yml
T
OrcaWinandOrcaWin 2cd3e6d6a3 ci: narrow churny cache keys and move PR-local cache saves to main (#34600)
* ci: narrow native, launcher and packaging cache keys and stop PR-local saves

* ci: hash Rust crate sources, not build outputs, in launcher and hook keys

* ci: fail closed on unlocked native packages and cover transitive key inputs

* ci: resolve the Windows Rust cache scope with a script, not a nested local action

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 18:14:29 -07:00

385 lines
16 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Dev Channel Windows Build
# Share one independently dispatchable Windows dev build across hourly/daily/adhoc. Adhoc may stage beside Mac and publish after its success. Dev installers are unsigned because SignPath approval waits exceed this cadence. Installed stable/RC publisherName rejects them, requiring one manual entry install; dev builds omit publisherName so later in-app channel changes work. Manual rebuild: dispatch main with channel/tag/ref/version for an existing release.
on:
# Duplicate inputs because workflow_call lacks choice UI; validate its free-string channels with the allowlist.
workflow_call:
inputs:
channel:
description: Dev channel whose release this build uploads into
required: true
type: string
tag:
description: Release tag for staged or published artifacts
required: true
type: string
ref:
description: Commit SHA to build — must be the exact commit the mac leg built
required: true
type: string
version:
description: Version to package, without the leading v
required: true
type: string
stage_only:
description: Package unsigned artifacts for a later gated publish job
required: false
type: boolean
default: false
fast_release:
description: Use the faster unsigned-build runner outside signing provenance
type: boolean
default: true
javascript_source_sha:
description: Restore this run's shared dev JavaScript instead of rebuilding it
type: string
default: ''
orcad_template:
description: Ship the orcad-template artifact the calling run built
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
channel:
description: Dev channel whose release this build uploads into
required: true
type: choice
options:
- hourly
- daily
- adhoc
tag:
description: Existing release tag in the channel repo (e.g. v1.4.178-adhoc.20260819010203)
required: true
type: string
ref:
description: Commit SHA to build — must be the exact commit the mac leg built
required: true
type: string
version:
description: Version to package, without the leading v
required: true
type: string
fast_release:
description: Use Blacksmith for unsigned Windows builds
type: boolean
default: true
permissions:
contents: read
concurrency:
# Serialize per tag to prevent competing uploads while independent channels/branches proceed.
group: dev-channel-win-build-${{ inputs.tag }}
cancel-in-progress: false
jobs:
build-win:
if: github.repository == 'stablyai/orca'
# Use mac workflows’ same environment for cross-repo App secrets.
environment: adhoc-mac-build
# The fast image has VS Build Tools 2022; signed release provenance stays separate.
runs-on: ${{ inputs.fast_release && 'blacksmith-8vcpu-windows-2025' || 'windows-2022' }}
permissions:
contents: read
actions: read
timeout-minutes: 90
env:
ORCA_BACKGROUND_LAUNCH: '1'
JOBS: ${{ inputs.fast_release && 'max' || '' }}
NODE_OPTIONS: --max-old-space-size=4096
CHANNEL: ${{ inputs.channel }}
TAG: ${{ inputs.tag }}
VERSION: ${{ inputs.version }}
steps:
# Re-vet source before checkout because standalone dispatch also exposes a release-write token.
- name: Vet the requested inputs
id: vetted
shell: bash
env:
REQUESTED_SHA: ${{ inputs.ref }}
REPO_URL: https://github.com/${{ github.repository }}
run: |
set -euo pipefail
# workflow_call inputs are free text; validate the channel set here.
case "$CHANNEL" in
hourly|daily|adhoc) ;;
*)
echo "::error::Unknown dev channel '$CHANNEL'; expected hourly, daily, or adhoc."
exit 1
;;
esac
if [[ ! "$REQUESTED_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::ref must be a full 40-character commit SHA, got '$REQUESTED_SHA'. The dispatching workflow passes the commit it resolved."
exit 1
fi
# Bind the tag to the packaged version so assets cannot describe a different build.
if [[ "$TAG" != "v$VERSION" ]]; then
echo "::error::tag '$TAG' does not match version '$VERSION'."
exit 1
fi
# Require the channel’s prerelease identifier so hourly assets cannot enter adhoc releases.
if [[ "$VERSION" != *"-$CHANNEL."* ]]; then
echo "::error::version '$VERSION' is not a $CHANNEL version."
exit 1
fi
# Trust requires branch/tag reachability; GitHub serves PR-only SHAs too. Bare/tree:0 limits fetch to
# the commit graph; reftable avoids case-only refs failing the entire fetch 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/*'
if ! git -C "$scratch" rev-parse --verify --quiet "$REQUESTED_SHA^{commit}" >/dev/null; then
echo "::error::Commit $REQUESTED_SHA is not in stablyai/orca."
exit 1
fi
if [[ -z "$(git -C "$scratch" for-each-ref --contains "$REQUESTED_SHA" refs/heads refs/tags | head -1)" ]]; then
echo "::error::Commit $REQUESTED_SHA is not reachable from any branch or tag of stablyai/orca; refusing to build it."
exit 1
fi
echo "Vetted $CHANNEL $TAG at $REQUESTED_SHA"
- name: Enable parallel checkout
if: inputs.fast_release
shell: bash
run: |
git config --global checkout.workers 0
# These fresh native builds do not need MSBuild's incremental file tracking.
echo 'TrackFileAccess=false' >> "$GITHUB_ENV"
- name: Checkout the built commit
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
# Read this repo without persisted credentials; writes use the dev-repo env App token (zizmor: artipacked).
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
# Reject old packaging configs that ignore ORCA_WIN_* and would publish to the main repo.
- name: Resolve the dev-channel packaging identity
shell: bash
run: |
set -euo pipefail
upper="$(printf '%s' "$CHANNEL" | tr '[:lower:]' '[:upper:]')"
echo "ORCA_WIN_${upper}=1" >>"$GITHUB_ENV"
echo "ORCA_${upper}_BUILD_VERSION=${VERSION}" >>"$GITHUB_ENV"
if [[ ! -f config/scripts/verify-dev-channel-packaging.mjs ]]; then
echo "::error::$TAG was built from a commit with no config/scripts/verify-dev-channel-packaging.mjs; that commit predates Windows dev builds, so it cannot produce one."
exit 1
fi
# pnpm must be on PATH before setup-node so setup-node can locate the store.
- 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
# Electron and builder releases fix what this downloads; the lockfile churns far more often.
- name: Resolve electron-builder download versions
id: eb-versions
shell: bash
run: node -p "const d = require('./package.json').devDependencies; 'versions=electron' + d.electron + '-builder' + d['electron-builder']" >> "$GITHUB_OUTPUT"
# Cache Electron/NSIS/winCodeSign tools with release-cut’s key shape.
- name: Cache electron-builder downloads
uses: actions/cache@v5
with:
path: |
~\AppData\Local\electron\Cache
~\AppData\Local\electron-builder\Cache
key: electron-builder-win-${{ steps.eb-versions.outputs.versions }}
restore-keys: |
electron-builder-win-
# Retry transient Electron CDN 504s during install; keep host-scoped natives for this runner’s only target.
- name: Install dependencies
uses: nick-fields/retry@v4
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: pnpm install --frozen-lockfile
# Build required out/mobile-web from mobile’s React Native/Expo install.
- uses: ./.github/actions/install-mobile-dependencies
if: inputs.javascript_source_sha == ''
# Validate packaging config after install, before the expensive build.
- name: Verify dev-channel packaging identity
shell: bash
run: node config/scripts/verify-dev-channel-packaging.mjs --channel="$CHANNEL" --platform=win32
- name: Download shared JavaScript
if: inputs.javascript_source_sha != ''
uses: actions/download-artifact@v8
with:
name: release-javascript
path: .build/release-javascript
- name: Build app
shell: bash
run: |
if [[ "$ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA" != '' ]]; then
node config/scripts/release-javascript-artifact.mjs restore
pnpm run build:release:host
else
pnpm build:release
fi
env:
ORCA_RELEASE_JAVASCRIPT_SOURCE_SHA: ${{ inputs.javascript_source_sha }}
ORCA_RELEASE_JAVASCRIPT_BUILD_IDENTITY: dev
# Unset dev build identity so official stable/rc telemetry stays silent.
ORCA_DIAGNOSTICS_TOKEN_URL: https://www.onorca.dev/diagnostics/token
# Inline so refs predating the wait can still use the trusted dispatch workflow.
- name: Wait for the qualified orcad template
if: inputs.fast_release && inputs.stage_only && inputs.orcad_template
uses: actions/github-script@v8
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')
# After the app build so nothing that cleans out/ can drop it; electron-builder ships it.
- name: Download the orcad deployment template
if: inputs.orcad_template
uses: actions/download-artifact@v8
with:
name: orcad-template
path: out/orcad-template
# Mint the one-hour write token after read-only setup/build.
- name: Mint dev channel repo token
if: ${{ !inputs.stage_only }}
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-${{ inputs.channel }}
# Require existing release: publish:always would recreate a discarded mac draft as an untitled Windows-only release.
- name: Confirm the target release still exists
if: ${{ !inputs.stage_only }}
shell: bash
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
run: |
set -euo pipefail
if ! gh release view "$TAG" --repo "stablyai/orca-$CHANNEL" --json tagName >/dev/null 2>&1; then
echo "::error::Release $TAG no longer exists in stablyai/orca-$CHANNEL; the mac leg most likely failed and discarded it. Not creating a Windows-only release."
exit 1
fi
- name: Publish Windows artifacts
if: ${{ !inputs.stage_only }}
uses: nick-fields/retry@v4
with:
# 30 is the pack + upload budget; there is no notary queue on this leg.
timeout_minutes: 30
max_attempts: 2
retry_wait_seconds: 30
command: node config/scripts/ensure-native-runtime.mjs --runtime=electron; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish always
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
ORCA_BUILD_COMMIT: ${{ inputs.ref }}
# beforePack and afterPack fail the package when the template is absent.
ORCA_REQUIRE_ORCAD_TEMPLATE: ${{ inputs.orcad_template && '1' || '' }}
# Allow upload beyond publisher’s 2h release-age guard; slow mac notarization/Windows build can otherwise silently drop every asset.
EP_GH_IGNORE_TIME: 'true'
# Require installer and latest.yml, preventing picker-visible releases with missing updater manifests.
- name: Verify Windows update manifest published
if: ${{ !inputs.stage_only }}
shell: bash
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
run: |
set -euo pipefail
assets="$(gh release view "$TAG" --repo "stablyai/orca-$CHANNEL" --json assets --jq '.assets[].name')"
echo "Assets on $TAG:"
echo "$assets"
for required in latest.yml orca-windows-setup.exe; do
if ! grep -qx "$required" <<<"$assets"; then
echo "::error::$TAG is missing $required; Windows could not install this build."
exit 1
fi
done
echo "Windows artifacts verified on $TAG."
- name: Package Windows artifacts without publishing
if: inputs.stage_only
uses: nick-fields/retry@v4
with:
timeout_minutes: 30
max_attempts: 2
retry_wait_seconds: 30
command: node config/scripts/ensure-native-runtime.mjs --runtime=electron; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; pnpm exec electron-builder --config config/electron-builder.config.cjs --win --publish never
env:
ORCA_BUILD_COMMIT: ${{ inputs.ref }}
ORCA_REQUIRE_ORCAD_TEMPLATE: ${{ inputs.orcad_template && '1' || '' }}
- name: Validate staged Windows artifacts
if: inputs.stage_only
shell: bash
run: |
set -euo pipefail
for required in orca-windows-setup.exe orca-windows-setup.exe.blockmap latest.yml; do
if [[ ! -s "dist/$required" ]]; then
echo "::error::Missing or empty staged Windows artifact: $required"
exit 1
fi
done
- name: Stage Windows artifacts for gated publishing
if: inputs.stage_only
uses: actions/upload-artifact@v7
with:
name: dev-channel-windows-${{ inputs.tag }}
path: |
dist/orca-windows-setup.exe
dist/orca-windows-setup.exe.blockmap
dist/latest.yml
if-no-files-found: error
retention-days: 3
compression-level: 0
overwrite: true