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.
389 lines
17 KiB
YAML
389 lines
17 KiB
YAML
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/desktop-distribution/channel-policy/verify-dev-channel-packaging.mjs ]]; then
|
||
echo "::error::$TAG was built from a commit with no config/scripts/desktop-distribution/channel-policy/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/desktop-distribution/channel-policy/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
|
||
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: ${{ 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://orca.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 $(if (Test-Path config/scripts/native-build/node-addons/ensure-native-runtime.mjs) { "config/scripts/native-build/node-addons/ensure-native-runtime.mjs" } else { "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 $(if (Test-Path config/scripts/native-build/node-addons/ensure-native-runtime.mjs) { "config/scripts/native-build/node-addons/ensure-native-runtime.mjs" } else { "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
|