Files
orca/.github/workflows/mobile.yml
T
Neil aa07d5891a fix(mobile): break the transport and terminal-document import cycles, and gate cycles after mobile's install (#30290)
* fix(mobile): break the transport and terminal-document import cycles and gate them after mobile's install

client-context re-exported the host-client hooks, which import client-context back: the re-export
barrel is gone and importers (and their test mocks) name host-client-hooks directly. In the terminal
document, the cell-metric getters move to a leaf cell-metrics module and select-mode entry/exit to
selection-mode, so fit, transform, overlay and range no longer import each other. pr.yml lints imports
before mobile's install, when mobile's tsconfig can't load, so Mobile verify now runs the same import
rules over mobile after its install with zero warnings allowed.

* fix(mobile): the browser-pane render harness imports useHostClient from host-client-hooks

Its route source is a template string, so no typecheck saw the removed client-context re-export; the mobile web bundle check did.

* fix(ci): the e2e typecheck ratchet counts the same with or without mobile's install

Specs reach mobile/src/transport, whose expo-crypto and async-storage imports resolve only after mobile's install and then load React Native's globals (numeric timers, a required NODE_ENV), adding errors in unrelated specs. An empty stub pins both, so the static job reads 232 either way.
2026-10-18 04:00:24 -07:00

308 lines
12 KiB
YAML

name: Mobile Checks
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- converted_to_draft
paths:
- 'mobile/**'
# Why all of src/shared: mobile imports hundreds of its modules, directly and through the RPC
# recordings and the host params check, so any shared edit can move a golden or break mobile's
# typecheck. Why the root lockfile: those modules resolve their packages from the root install.
- 'src/shared/**'
# Mobile's Node RPC recording tools import this package.
- 'src/packages/process-host/**'
- 'pnpm-lock.yaml'
# Mobile launch contracts exercise the real host dispatcher and durable receipt store.
- 'src/main/agent-launch/**'
- 'src/main/runtime/rpc/**'
- 'src/main/runtime/runtime-rpc/**'
- 'src/main/runtime/runtime-rpc.ts'
- 'src/main/runtime/device-registry.ts'
- 'src/main/runtime/orca-runtime.ts'
- 'src/main/runtime/agent-session-*.ts'
- 'src/main/native-chat/agent-session-wire/**'
- 'src/main/runtime/structured-attention-original-delivery.test-fixture.ts'
# Why: this job holds the only checks that load the Fastfile, so edits to
# it or to the release workflow it guards must re-run them.
- '.github/workflows/mobile.yml'
- 'config/mobile-ci.vitest.config.mjs'
- 'config/scripts/run-mobile-ci-tests.mjs'
- 'config/oxlint-code-quality-native-plugins.json'
- 'config/.bun-version'
- '.github/actions/install-node-dependencies/**'
- '.github/actions/restore-pnpm-verification/**'
- '.github/workflows/mobile-ios-release.yml'
- 'config/scripts/mobile-release-check-scope*'
- 'config/scripts/mobile-test-change-scope*'
- 'config/scripts/pr-code-change-scope.mjs'
- 'config/scripts/pr-package-test-paths.mjs'
- 'config/scripts/pr-layout-oracle-route.mjs'
# Desktop suites do not feed mobile; keep fixtures and test/toolchain config inputs.
- '!src/shared/**/*.test.[jt]s'
- '!src/shared/**/*.test.[jt]sx'
- '!src/shared/**/*.test.[cm][jt]s'
- '!src/packages/process-host/**/*.test.[jt]s'
- '!src/packages/process-host/**/*.test.[jt]sx'
- '!src/packages/process-host/**/*.test.[cm][jt]s'
- '!src/main/**/*.test.[jt]s'
- '!src/main/**/*.test.[jt]sx'
- '!src/main/**/*.test.[cm][jt]s'
# Why main too: two pull requests can each pass against their own base and disagree once both
# land, and `verify` never runs on main. The same source paths as above, less the CI inputs only
# `verify` reads.
push:
branches:
- main
paths:
- 'mobile/**'
- 'src/shared/**'
- 'src/packages/process-host/**'
- 'pnpm-lock.yaml'
- 'src/main/agent-launch/**'
- 'src/main/runtime/rpc/**'
- 'src/main/runtime/runtime-rpc/**'
- 'src/main/runtime/runtime-rpc.ts'
- 'src/main/runtime/device-registry.ts'
- 'src/main/runtime/orca-runtime.ts'
- 'src/main/runtime/agent-session-*.ts'
- 'src/main/native-chat/agent-session-wire/**'
- 'src/main/runtime/structured-attention-original-delivery.test-fixture.ts'
- '.github/workflows/mobile.yml'
- 'config/mobile-ci.vitest.config.mjs'
- 'config/scripts/run-mobile-ci-tests.mjs'
- 'config/.bun-version'
# Desktop suites do not feed mobile; keep fixtures and test/toolchain config inputs.
- '!src/shared/**/*.test.[jt]s'
- '!src/shared/**/*.test.[jt]sx'
- '!src/shared/**/*.test.[cm][jt]s'
- '!src/packages/process-host/**/*.test.[jt]s'
- '!src/packages/process-host/**/*.test.[jt]sx'
- '!src/packages/process-host/**/*.test.[cm][jt]s'
- '!src/main/**/*.test.[jt]s'
- '!src/main/**/*.test.[jt]sx'
- '!src/main/**/*.test.[cm][jt]s'
concurrency:
# Per commit on main, not per branch. GitHub cancels any PENDING run in a group when a new one
# queues, whatever `cancel-in-progress` says, so one shared main group drops the middle merge of
# three -- and a merge that breaks main there is exactly what `main-tests` checks for.
group: mobile-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
verify:
name: Mobile verify
if: github.event_name == 'pull_request' && github.event.pull_request.draft != true
# Why ARM: 209s of this job is Vitest and nothing here needs x86: no Android SDK, emulator, gradle,
# Hermes or Watchman, no docker, and no artifacts. The Gemfile.lock lists the generic `ruby`
# platform, so frozen bundler installs without an aarch64-linux entry.
runs-on: ubuntu-24.04-arm
env:
# Why: an unfrozen bundler silently re-resolves when Gemfile.lock drifts
# from the Gemfile, which is how the release jobs could land on different
# fastlane versions in the first place. Fail here instead.
BUNDLE_FROZEN: 'true'
ORCA_MOBILE_TYPECHECK_COMPILER: 'native'
defaults:
run:
working-directory: mobile
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 2
- uses: ./.github/actions/install-node-dependencies
with:
cache-pnpm-store: 'false'
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
- name: Detect Ruby release inputs
id: ruby-scope
shell: bash
working-directory: .
run: |
# Keep deletions when release files move into an application directory.
if ! git diff --name-only --no-renames -z HEAD^1 HEAD > "$RUNNER_TEMP/mobile-release-changes"; then
echo 'should_run=true' >> "$GITHUB_OUTPUT"
elif ! node config/scripts/mobile-release-check-scope.mjs "$RUNNER_TEMP/mobile-release-changes"; then
echo 'should_run=true' >> "$GITHUB_OUTPUT"
fi
# bundler-cache installs mobile/Gemfile.lock, so this job is also what
# proves the pinned fastlane the release workflow depends on still
# resolves — before a release run finds out.
- name: Setup Ruby and fastlane
id: ruby-tools
background: true
if: steps.ruby-scope.outputs.should_run != 'false'
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: mobile
- name: Install dependencies
run: pnpm install --frozen-lockfile
# Why here: pr.yml runs these import rules before mobile's install, when mobile's tsconfig
# (which extends expo's) can't load, so its import cycles were never seen. Zero tolerance.
- name: Lint mobile imports with mobile dependencies installed
working-directory: .
run: node node_modules/oxlint/bin/oxlint --config config/oxlint-code-quality-native-plugins.json mobile --deny-warnings
# Call installed tools so pnpm's dependency refresh cannot race between checks.
- name: Typecheck
id: production-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/typecheck-mobile.mjs
# Why a ratchet and not the raw typecheck: mobile/tsconfig.json excludes test files, so until
# tsconfig.test.json existed nothing checked them, and at introduction 127 of the 632 had
# drifted. This fails when a test file that checks today stops checking, when a test leaves
# the program, and on @ts-nocheck; the baseline may only shrink.
- name: Typecheck tests (ratchet)
id: test-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/check-tests-typecheck-ratchet.mjs
# This includes the bridged replay of the whole recording corpus, which used to be a second
# step of its own behind RPC_FOUNDATION_BRIDGE=1. A gate nobody can forget to set is the point:
# it fails when a divergence class grows, when a divergence lands in no class at all, or when
# one of the 103 goldens inside the C1 page closure changes the verdict it is pinned to. It is
# ~3 min of test time on its own, and Vitest runs it on a worker beside the rest of the suite,
# so folding it in costs a fraction of that in wall time and one step less to skip.
- name: Detect mobile test inputs
id: test-scope
shell: bash
working-directory: .
run: |
if ! git diff --name-only --no-renames -z HEAD^1 HEAD > "$RUNNER_TEMP/mobile-test-changes"; then
echo 'should_run=true' >> "$GITHUB_OUTPUT"
elif ! node config/scripts/mobile-test-change-scope.mjs "$RUNNER_TEMP/mobile-test-changes"; then
echo 'should_run=true' >> "$GITHUB_OUTPUT"
fi
- name: Setup Bun for tests
if: steps.test-scope.outputs.should_run != 'false'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Test
if: steps.test-scope.outputs.should_run != 'false'
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: node ../config/scripts/run-mobile-ci-tests.mjs
# All checks read the completed install; compilers need not hold up test workers.
- wait: [production-types, test-types, ruby-tools]
# Reports, never gates: the goldens are content-addressed JSON, so their raw diff is hashes.
# This decodes which recorded behaviour the pull request moves into the job summary, and runs
# after a red Test too, when a reviewer most wants it.
- name: Summarize RPC recording changes
if: ${{ !cancelled() }}
continue-on-error: true
run: pnpm run rpc:diff HEAD^1 --summary "$GITHUB_STEP_SUMMARY"
- name: Test iOS release version resolution
if: steps.ruby-scope.outputs.should_run != 'false'
run: ruby fastlane/ios_release_version_test.rb
- name: Test TestFlight lane arguments
if: steps.ruby-scope.outputs.should_run != 'false'
run: ruby fastlane/fastfile_testflight_arguments_test.rb
# Why: nothing else in CI loads the Fastfile, so a syntax error, a broken
# require, or an undefined constant only surfaces mid-release — the
# ios-distribute job failed every run for six days that way. `lanes` just
# loads and lists, so it needs no App Store Connect credentials and makes
# no network calls to Apple.
- name: Smoke-check the Fastfile
if: steps.ruby-scope.outputs.should_run != 'false'
env:
FASTLANE_SKIP_UPDATE_CHECK: '1'
FASTLANE_OPT_OUT_USAGE: '1'
run: bundle exec fastlane lanes
- name: Smoke-check CocoaPods
if: steps.ruby-scope.outputs.should_run != 'false'
run: bundle exec pod --version
- name: Lint
run: pnpm lint
- name: Check formatting
run: pnpm format:check
main-tests:
name: Mobile tests on main
env:
ORCA_MOBILE_TYPECHECK_COMPILER: 'native'
if: github.event_name == 'push'
# Why ARM: the same pure-Node Vitest run `verify` makes on a pull request.
runs-on: ubuntu-24.04-arm
defaults:
run:
working-directory: mobile
steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: ./.github/actions/install-node-dependencies
with:
cache-dependency-path: |
pnpm-lock.yaml
mobile/pnpm-lock.yaml
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
id: production-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/typecheck-mobile.mjs
- name: Typecheck tests (ratchet)
id: test-types
background: true
env:
GOMAXPROCS: '2'
run: node scripts/check-tests-typecheck-ratchet.mjs
# The whole suite, not only the recordings: the mutant and page-bridge suites read the goldens
# too. A red run here names each golden and the `rpc:record` command; the author of the merge
# that turned it red re-records in a follow-up.
- name: Setup Bun for tests
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: config/.bun-version
- name: Test
env:
ORCA_BACKGROUND_LAUNCH: '1'
run: node ../config/scripts/run-mobile-ci-tests.mjs
- wait: [production-types, test-types]