Skip to content

refactor(web-shared): import web-shared via @kilocode/web-shared #2951

refactor(web-shared): import web-shared via @kilocode/web-shared

refactor(web-shared): import web-shared via @kilocode/web-shared #2951

# Build the mobile debug dev-client natively and publish it as a run
# artifact named mobile-native-<platform>-<nativeHash>. The nativeHash is
# input-deterministic (dev/local/mobile-{ios,android}-build.ts), so any
# machine with the same native inputs computes the same name and can install
# the artifact instead of compiling (dev/local/mobile-remote-native.ts).
#
# Runs on pushes to main that can change native inputs, on pull requests that
# change a native input, and on demand for any pushed branch via
# workflow_dispatch. The gate job skips platforms whose artifact already
# exists, so a PR whose nativeHash is already published costs one ubuntu gate
# job and no macOS runner.
name: mobile-native-build
on:
workflow_dispatch:
inputs:
platform:
description: Platform to build
type: choice
options: [all, ios, android]
default: all
push:
branches: [main]
paths:
- 'apps/mobile/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'patches/**'
- '.github/workflows/mobile-native-build.yml'
# Native inputs: any of these can break the native build, so a pull request
# that changes one builds it before merge (PR 6115 merged an iOS app that
# did not compile with only the workflow-file path here). A PR whose hash is
# already published skips both platforms in the gate job. Editing this
# workflow file itself also runs it, under a pr<N>-<workflow hash>- prefix
# hosts never install.
pull_request:
paths:
- 'apps/mobile/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'patches/**'
- '.github/workflows/mobile-native-build.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Never cancel a push build: it is producing an artifact hosts will
# install. A superseded pull_request build produces nothing anyone
# keeps, so it must not hold a macOS runner for 90 minutes.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
actions: read
# The gate lists the pull request's changed files to decide whether the run
# validates this workflow file (out-of-namespace prefix) or a native input
# (plain, installable name).
pull-requests: read
jobs:
gate:
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
ios_hash: ${{ steps.decide.outputs.ios_hash }}
android_hash: ${{ steps.decide.outputs.android_hash }}
need_ios: ${{ steps.decide.outputs.need_ios }}
need_android: ${{ steps.decide.outputs.need_android }}
prefix: ${{ steps.decide.outputs.prefix }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
lfs: true
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Compute hashes and gate the build
id: decide
env:
GH_TOKEN: ${{ github.token }}
PLATFORM: ${{ inputs.platform || 'all' }}
PR_NUMBER: ${{ github.event.number }}
run: |
set -euo pipefail
ios_hash=$(pnpm exec tsx dev/local/mobile-native-hash.ts ios)
android_hash=$(pnpm exec tsx dev/local/mobile-native-hash.ts android)
echo "ios_hash=$ios_hash" >> "$GITHUB_OUTPUT"
echo "android_hash=$android_hash" >> "$GITHUB_OUTPUT"
# dev/local/mobile-native-gate.ts decides the prefix and which
# platforms still need building. A pull_request run that edits this
# workflow is validating that edit, so it publishes under
# pr<N>-<workflow hash>- and hosts never install it; a pull_request
# run whose native input changed publishes the plain
# mobile-native-<platform>-<nativeHash> name a host installs, and
# skips a platform whose artifact already exists.
pnpm exec tsx dev/local/mobile-native-gate.ts "$ios_hash" "$android_hash"
ios:
needs: gate
if: needs.gate.outputs.need_ios == 'true'
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
lfs: true
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build
# A consumer computes the hash on macOS; the gate computed it on
# Linux. A mismatch would publish an artifact nobody can ever find,
# so fail loudly instead. Android is checked here too: it builds on
# Linux but is consumed on macOS, and nothing else compares the two.
- name: Verify the hashes are platform-independent
run: |
set -euo pipefail
check() {
mac_hash=$(pnpm exec tsx dev/local/mobile-native-hash.ts "$1")
if [ "$mac_hash" != "$2" ]; then
echo "::error::$1 nativeHash differs between Linux ($2) and macOS ($mac_hash)"
exit 1
fi
}
check ios "${{ needs.gate.outputs.ios_hash }}"
check android "${{ needs.gate.outputs.android_hash }}"
- name: Expo prebuild
run: CI=1 pnpm --filter kilo-app exec expo prebuild --platform ios
- name: Build
run: |
set -euo pipefail
xcodebuild \
-workspace apps/mobile/ios/Kilo.xcworkspace \
-scheme Kilo \
-configuration Debug \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$RUNNER_TEMP/DerivedData" \
build
# A tarball keeps the executable bit that upload-artifact's zip drops.
# The artifact name keys only on nativeHash, but the local cache key
# also keys on the Xcode and simulator SDK. Record them so a consumer
# on a different toolchain refuses the binary (see
# dev/local/mobile-remote-native.ts) instead of caching an app its
# simulator runtime cannot launch.
- name: Package
run: |
set -euo pipefail
products="$RUNNER_TEMP/DerivedData/Build/Products/Debug-iphonesimulator"
xcode=$(xcodebuild -version | sed -n 's/^Build version \(.*\)$/\1/p')
sdk=$(xcrun --sdk iphonesimulator --show-sdk-version)
printf '{"xcodeBuildVersion":"%s","simulatorSdkVersion":"%s"}\n' "$xcode" "$sdk" \
> "$products/toolchain.json"
tar -C "$products" \
-czf "ios-${{ needs.gate.outputs.ios_hash }}.tar.gz" Kilo.app toolchain.json
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.gate.outputs.prefix }}mobile-native-ios-${{ needs.gate.outputs.ios_hash }}
path: ios-${{ needs.gate.outputs.ios_hash }}.tar.gz
compression-level: 0
retention-days: ${{ github.event_name == 'pull_request' && 1 || 90 }}
if-no-files-found: error
android:
needs: gate
if: needs.gate.outputs.need_android == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
lfs: true
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- name: Setup Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: '17'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build trpc types
run: pnpm --filter @kilocode/trpc run build
- name: Expo prebuild
run: CI=1 pnpm --filter kilo-app exec expo prebuild --platform android
- name: Build
run: |
set -euo pipefail
cd apps/mobile/android
./gradlew --no-daemon app:assembleDebug
cp app/build/outputs/apk/debug/app-debug.apk "$RUNNER_TEMP/Kilo.apk"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ needs.gate.outputs.prefix }}mobile-native-android-${{ needs.gate.outputs.android_hash }}
path: ${{ runner.temp }}/Kilo.apk
compression-level: 0
retention-days: ${{ github.event_name == 'pull_request' && 1 || 90 }}
if-no-files-found: error