Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/regen-codegen/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ git diff js/packages/truapi/src/generated/
```

The diff shape should match the Rust diff — new methods produce new
client stubs and wire-table entries. Commit the regenerated files
alongside the Rust changes.
client stubs and wire-table entries. The generated files are gitignored;
nothing is committed, CI regenerates them from the Rust changes.

## Failure modes

Expand Down
12 changes: 7 additions & 5 deletions .claude/skills/truapi-definition-of-done/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ every PR. A green CI run is sufficient evidence for the static layers
the e2e job runs the Playwright suite from the `e2e-dotli` skill against
a freshly built dotli host.

The `ios-bindings` job only compares the committed bindings against
freshly generated ones. The `ios-swift` job compiles the package and its
The `ios-bindings` job regenerates the Swift bindings, which proves
every UniFFI-exposed type still has a binding representation; nothing is
committed to diff against. The `ios-swift` job generates the package's
Swift sources and container resource, then compiles the package and its
test target on macOS, which is what catches a hand-written conformer
that misses a new protocol requirement. It is path-filtered to pull
requests touching `ios/`, `Package.swift`, the `Makefile` or
`rust/crates/truapi-server/src/native*`, so it shows as skipped
elsewhere.
requests touching `ios/`, `Package.swift`, the `Makefile`,
`js/container/` or any crate the bindings are generated from, so it
shows as skipped elsewhere.

Still uncovered: nothing compiles Kotlin, so `TrUAPIHost.kt` fails at
release time, and the embedding apps are not built here at all.
14 changes: 14 additions & 0 deletions .github/workflows/ci-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# codegen.sh formats its generated Rust with `cargo +nightly fmt`.
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly
components: rustfmt
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2.8.1
- uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
Expand All @@ -31,6 +36,15 @@ jobs:
- uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
gradle-version: "8.9"
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
# truapi-server declares its generated modules unconditionally and they are
# gitignored, so the crate does not compile until codegen has run.
- name: Generate the Rust codegen output
run: |
npm ci --ignore-scripts
TRUAPI_SKIP_PACKAGE_BUILD=1 ./scripts/codegen.sh
- name: Generate UniFFI Kotlin bindings
run: make uniffi-kotlin
- name: Compile the shell against the bindings
Expand Down
117 changes: 91 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ permissions:

jobs:
rust:
needs: codegen
name: Rust workspace
runs-on: ubuntu-latest
env:
Expand All @@ -38,6 +39,11 @@ jobs:

- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2

- name: Download codegen output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: codegen-output

- name: cargo build
run: cargo build --workspace --all-targets --all-features

Expand Down Expand Up @@ -121,15 +127,18 @@ jobs:
- name: Install workspace deps
run: npm ci --ignore-scripts

- name: Check no generated outputs are committed
run: |
committed="$(git ls-files -ci --exclude-standard)"
if [ -n "$committed" ]; then
echo "$committed"
echo "Generated outputs above are committed. Untrack them with 'git rm --cached'." >&2
exit 1
fi

- name: Run codegen
run: ./scripts/codegen.sh

- name: Check generated Rust output is committed
run: |
git diff --exit-code -- \
rust/crates/truapi-server/src/generated \
rust/crates/truapi-server/src/wasm/generated_bridge.rs

- name: Check Rust/TS wire table parity
run: TRUAPI_REQUIRE_GENERATED_TS=1 cargo test -p truapi-server --test wire_table_ts_parity

Expand All @@ -146,9 +155,12 @@ jobs:
js/packages/truapi/src/explorer/versions.ts
js/packages/truapi-host/src/generated
playground/test/generated
rust/crates/truapi-server/src/generated
rust/crates/truapi-server/src/wasm

ios-bindings:
name: iOS bindings (uniffi)
needs: codegen
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -161,19 +173,24 @@ jobs:

- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2

# truapi-server declares its generated modules unconditionally, so the
# cdylib bindgen builds from does not compile without the codegen output.
- name: Download codegen output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: codegen-output

# Swift bindgen is pure Rust codegen, so this needs neither Xcode nor the
# iOS targets, unlike rebuild.sh which also builds the xcframework.
# iOS targets, unlike rebuild.sh which also builds the xcframework. It
# gates that every UniFFI-exposed type still has a binding representation.
- name: Generate Swift bindings
run: make uniffi

- name: Check committed iOS bindings are current
run: ./ios/truapi-host/scripts/sync-bindings.sh --check
- name: Sync iOS bindings into package
run: ./ios/truapi-host/scripts/sync-bindings.sh

# The provider ships its own UniFFI surface and its own committed
# bindings; `rebuild.sh` overwrites them in place, so only this check
# can fail on drift.
- name: Check committed TrUAPIProvider bindings are current
run: make provider-swift-check
- name: Generate TrUAPIProvider bindings
run: make provider-swift

ios-changes:
name: iOS change filter
Expand All @@ -190,10 +207,10 @@ jobs:
# ios-swift on the output. On pull_request the checkout is the merge
# commit, so HEAD^1 is the base. Other events always run the gate.
#
# The list needs no broader rust/ entry: the generated bindings are
# committed under ios/, and ios-bindings runs unconditionally, so a Rust
# change that moves the UniFFI surface either carries an ios/ change or
# fails there first.
# The bindings are generated, not committed, so a UniFFI surface change
# leaves no ios/ diff to key on. The list therefore names every crate the
# bindings are generated from, plus js/container, which is compiled into
# the TrUAPIHost target as a resource.
- name: Detect iOS-relevant changes
id: filter
run: |
Expand All @@ -203,15 +220,15 @@ jobs:
exit 0
fi
if git diff --name-only HEAD^1 HEAD \
| grep -qE '^(ios/|Package\.swift$|Makefile$|rust/crates/truapi-server/src/native|rust/crates/truapi-provider/)'; then
| grep -qE '^(ios/|Package\.swift$|Makefile$|js/container/|rust/crates/truapi/|rust/crates/truapi-platform/|rust/crates/truapi-server/|rust/crates/truapi-provider/)'; then
echo "ios=true" >> "$GITHUB_OUTPUT"
else
echo "ios=false" >> "$GITHUB_OUTPUT"
fi

ios-swift:
name: iOS package (swift compile)
needs: ios-changes
needs: [ios-changes, codegen]
if: needs.ios-changes.outputs.ios == 'true'
runs-on: macos-15
timeout-minutes: 30
Expand All @@ -224,6 +241,10 @@ jobs:
with:
persist-credentials: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22

- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable
with:
toolchain: stable
Expand All @@ -233,31 +254,75 @@ jobs:
with:
shared-key: ios-swift-gate

- name: Download codegen output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: codegen-output

# Simulator slice only, debug profile. This gate compiles Swift and ships
# no binary, so the device slice and release codegen are waste. The
# published binary is older than the current Rust surface, so
# TRUAPI_USE_LOCAL_BINARY points the manifest at this build instead.
- name: Build the simulator XCFramework
run: make xcframework XCFRAMEWORK_TARGETS=aarch64-apple-ios-sim XCFRAMEWORK_PROFILE=debug

# make xcframework leaves the bindings in target/uniffi-swift-out; the
# package's Swift targets are gitignored, so they only exist once this
# copies them in.
- name: Sync the generated Swift bindings into the package
run: ./ios/truapi-host/scripts/sync-bindings.sh

- name: Stage it into the package
run: ./ios/truapi-host/scripts/stage-xcframework.sh

# TrUAPIHost declares Resources/truapi-container.js as a .copy resource,
# so SwiftPM refuses to resolve the package until this exists.
- name: Build the lockdown container bundle
run: |
npm --prefix js/container install --no-fund --no-audit
npm --prefix js/container run build

- name: Build the simulator XCFramework for TrUAPIProvider
run: PROFILE=debug ./ios/truapi-provider/scripts/rebuild.sh --sim-only

# Compiling the test target is the point: the hand-written conformers a
# generated-file diff cannot see live there and in TrUAPIHost.swift.
- name: Compile the package and its tests
# Build the tag a release would cut, then compile from a clean clone of
# it. Compiling in place would pass on generated files that the tag does
# not carry, which is exactly the failure this guards: a consumer only
# ever sees the tag.
- name: Build the SwiftPM consumer tag
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
./ios/truapi-host/scripts/tag-release.sh 0.0.0-ci

- name: Check the tag carries every generated file
run: |
set -euo pipefail
probe="$RUNNER_TEMP/tag-probe"
rm -rf "$probe"
git clone --quiet --branch 0.0.0-ci "file://$PWD" "$probe"
while read -r path; do
diff -u <(cd "$probe" && find "$path" -type f | sort) \
<(find "$path" -type f | sort)
done <<< "$(sed -n -E 's/^[[:space:]]*path: "([^"]+)".*/\1/p' Package.swift | grep -v '/Binaries/')"

# The xcframework ships as a release asset rather than in the tag, so the
# locally built one stands in for it here.
- name: Compile the package and its tests from the tag
run: |
set -euo pipefail
probe="$RUNNER_TEMP/tag-probe"
mkdir -p "$probe/ios/truapi-host/Binaries" "$probe/ios/truapi-provider/Binaries"
cp -R ios/truapi-host/Binaries/truapi_server.xcframework "$probe/ios/truapi-host/Binaries/"
cp -R ios/truapi-provider/Binaries/truapi_provider.xcframework "$probe/ios/truapi-provider/Binaries/"
cd "$probe"
xcodebuild build-for-testing \
-scheme TrUAPIHost \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$RUNNER_TEMP/TrUAPIHostDerivedData" \
CODE_SIGNING_ALLOWED=NO

- name: Compile TrUAPIProvider
run: swift build --target TrUAPIProvider
swift build --target TrUAPIProvider

ts-client:
name: "@parity/truapi"
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/release-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ jobs:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false

# codegen.sh formats its generated Rust with `cargo +nightly fmt`.
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: nightly
components: rustfmt
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,x86_64-linux-android
Expand All @@ -58,6 +63,15 @@ jobs:
with:
gradle-version: "8.9"

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
# truapi-server declares its generated modules unconditionally and they are
# gitignored, so the crate does not compile until codegen has run.
- name: Generate the Rust codegen output
run: |
npm ci --ignore-scripts
TRUAPI_SKIP_PACKAGE_BUILD=1 ./scripts/codegen.sh
- name: Generate UniFFI Kotlin bindings
run: make uniffi-kotlin

Expand Down
Loading