main
name: sdk-releases description: End-to-end SDK release process for all Onde distribution channels. Covers version bump checklist, CI/CD pipelines for Rust (crates.io), Swift (XCFramework → onde-swift), Dart (pub.dev), and npm, tag validation, GitHub Release creation, manual publish fallback, and common pitfalls. Apply whenever bumping versions or troubleshooting release workflows. allowed-tools: Read, Write, Edit, Glob, Grep, Bash
user-invocable: true
Skill: SDK Releases
What This Skill Covers
The complete release pipeline for shipping Onde across all registries:
- Rust crate → crates.io (
onde) - Swift package → Swift Package Index (
onde-swift) - Dart/Flutter package → pub.dev (
onde_inference) - React Native → npm (
@ondeinference/react-native) - Kotlin Multiplatform → Maven Central (
com.ondeinference:onde-inference)
All five SDK workflows share the same tag trigger. A single tag push fires all five in parallel. When only a subset needs (re-)publishing, use the manual publish fallback documented below to avoid collateral re-runs.
Version Bump Checklist
Every release starts with a version bump. All version sources must be updated in a single commit before tagging.
| # | File | Field | Example |
|---|---|---|---|
| 1 | Cargo.toml (root) |
version |
version = "0.1.3" |
| 2 | sdk/dart/pubspec.yaml |
version |
version: 0.1.3 |
| 3 | sdk/dart/CHANGELOG.md |
New ## 0.1.3 section |
Prepend at top of file |
| 4 | sdk/react-native/package.json |
version |
"version": "0.1.3" |
| 5 | sdk/react-native/rust/Cargo.toml |
version |
version = "0.1.3" |
| 6 | sdk/react-native/CHANGELOG.md |
New ## 0.1.3 section |
Prepend at top of file |
| 7 | sdk/kotlin/gradle.properties |
VERSION_NAME |
VERSION_NAME=0.1.3 |
| 8 | sdk/dart/rust/Cargo.lock |
onde package version |
Run cd sdk/dart/rust && cargo update -p onde |
| 9 | sdk/react-native/rust/Cargo.lock |
onde package version |
Run cd sdk/react-native/rust && cargo update -p onde |
| 10 | Cargo.lock (root) |
onde package version |
Run cargo check at repo root |
| 11 | CHANGELOG.md (root) |
New ## 0.1.3 section |
Prepend at top of file. sync-release-version.py --check gates on it since 1.2.5; before that nothing did, which is why the file jumps from 1.1.1 to 1.2.5 |
Files you do NOT manually edit
| File | Why |
|---|---|
onde-swift/Package.swift |
CI rewrites the url: + checksum: automatically |
onde-swift/Sources/Onde/onde.swift |
CI copies the freshly generated UniFFI glue |
Quick bump commands
# 1. Edit Cargo.toml version (manual)
# 2. Edit sdk/dart/pubspec.yaml version (manual)
# 3. Prepend new section to sdk/dart/CHANGELOG.md (manual)
# 4. Edit sdk/react-native/package.json version (manual)
# 5. Edit sdk/react-native/rust/Cargo.toml version (manual)
# 6. Prepend new section to sdk/react-native/CHANGELOG.md (manual)
# 7. Edit sdk/kotlin/gradle.properties VERSION_NAME (manual)
# 8. Prepend new section to CHANGELOG.md at the repo root (manual)
# 9. Sync lockfiles
cargo check # updates root Cargo.lock
cd sdk/dart/rust && cargo update -p onde # updates Dart SDK's Cargo.lock
cd sdk/react-native/rust && cargo update -p onde # updates React Native SDK's Cargo.lock
# 10. Verify
grep '^version' Cargo.toml # "0.1.3"
grep '^version:' sdk/dart/pubspec.yaml # 0.1.3
grep '"version"' sdk/react-native/package.json # "0.1.3"
grep '^version' sdk/react-native/rust/Cargo.toml # "0.1.3"
grep 'VERSION_NAME' sdk/kotlin/gradle.properties # 1.0.0
grep 'name = "onde"' -A1 Cargo.lock # version = "0.1.3"
grep 'name = "onde"' -A1 sdk/dart/rust/Cargo.lock # version = "0.1.3"
grep 'name = "onde"' -A1 sdk/react-native/rust/Cargo.lock # version = "0.1.3"
# 11. Commit and tag
git add -A
git commit -m "0.1.3"
git tag 0.1.3
git push origin main 0.1.3
Tag Format
All tags are bare semver — no v prefix.
0.1.3 ✅ correct
v0.1.3 ❌ will NOT trigger CI
0.1.3-beta ❌ will NOT match the tag pattern
All five CI workflows use the same trigger pattern:
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
CI Pipeline: Rust SDK (crates.io)
Workflow: .github/workflows/release-sdk-rust.yml
Trigger: Tag push matching [0-9]+.[0-9]+.[0-9]+ or workflow_dispatch
Runner: macos-26
Flow
Tag push (e.g. 1.0.0)
│
├─ 1. Checkout + install stable Rust
│
├─ 2. Validate tag == Cargo.toml version
│ └─ Fails fast if mismatched
│
├─ 3. cargo publish --dry-run
│ └─ Compiles and packages; catches errors before real publish
│
└─ 4. cargo publish (tag push only)
└─ Authenticates via CARGO_REGISTRY_TOKEN secret
Key details
workflow_dispatchruns the dry-run only (the publish step hasif: startsWith(github.ref, 'refs/tags/')) — useful for validation.- Runner is
macos-26because the crate depends onmistralrswith Metal features, which requires macOS + Xcode to compile. - crates.io is immutable — once a version is published, it cannot be
re-published. You must bump to the next version.
cargo yankhides a version from dependency resolution but does not delete it.
Required secret
CARGO_REGISTRY_TOKEN — a crates.io API token scoped to publish-update for
the onde crate. Create at https://crates.io/settings/tokens. Best practice:
scope it to a single crate rather than using a global token.
CI Pipeline: Swift SDK
Workflow: .github/workflows/release-sdk-swift.yml
Trigger: Tag push matching [0-9]+.[0-9]+.[0-9]+
Runner: macos-15
Flow
Tag push (e.g. 0.1.3)
│
├─ 1. Build XCFramework
│ └─ .github/scripts/build-swift-xcframework.sh
│ ├─ Extracts version from Cargo.toml via tomllib
│ ├─ Compiles staticlibs for 5 Apple targets
│ ├─ Generates UniFFI Swift bindings
│ ├─ Assembles XCFramework, zips, computes SHA-256
│ └─ Writes dist/swift/{version.txt, OndeFramework.checksum.txt}
│
├─ 2. Read version + checksum into step outputs
│
├─ 3. Validate tag == Cargo.toml version
│ └─ Fails fast if mismatched (e.g. tagged 0.1.3 but Cargo.toml says 0.1.2)
│
├─ 4. Upload CI artifacts (every run, including workflow_dispatch)
│
├─ 5. Create GitHub Release on `onde` (tag push only)
│ └─ Attaches OndeFramework.xcframework.zip + checksum
│
├─ 6. Checkout ondeinference/onde-swift (using ONDE_SWIFT_PAT)
│
├─ 7. Rewrite Package.swift
│ └─ Python regex replaces .binaryTarget url: + checksum:
│
├─ 8. Copy generated onde.swift → onde-swift/Sources/Onde/
│
└─ 9. Commit + tag onde-swift
├─ git commit -m "Release 0.1.3" (skipped if no changes)
├─ git push origin HEAD:main
└─ git tag -a "0.1.3" -m "Release 0.1.3" (skipped if tag exists)
└─ Annotated tag, never force-pushed
What happens on onde-swift after the push
The onde CI pushes a commit (to main) and a tag (e.g. 0.1.3) to onde-swift.
This triggers two workflows on onde-swift:
ci.yml(on push tomain) — validates manifest + builds all 5 platformsrelease.yml(on tag push) — validates package, then creates a GitHub Release with installation instructions and a link to the upstreamondechangelog
Both run concurrently without conflict — they use different concurrency groups
(ci-refs/heads/main vs the tag ref).
Tag immutability
Tags on onde-swift are never force-pushed. SPM caches the resolved commit
SHA per tag in Package.resolved. Moving a tag silently breaks every consumer's
lockfile. If a release needs to be redone:
- Delete the remote tag:
git push origin :refs/tags/0.1.3 - Delete the GitHub Release on both
ondeandonde-swift - Fix the issue, re-tag, push again
CI Pipeline: npm SDK (React Native)
Workflow: .github/workflows/release-sdk-npm.yml
Trigger: Tag push matching [0-9]+.[0-9]+.[0-9]+ or workflow_dispatch
Runner: ubuntu-latest
Flow
Tag push (e.g. 1.0.0)
│
├─ validate job:
│ ├─ Set up Node.js 22
│ ├─ Read version from sdk/react-native/package.json
│ ├─ Validate tag == package.json version
│ ├─ npm install + npm run build
│ └─ npm publish --dry-run --access public
│
└─ publish job (tag push only, needs: validate):
├─ npm install + npm run build
├─ Check if version already exists on npm
│ └─ If yes → skip publish (idempotent, exits 0)
└─ npm publish --access public
npm idempotency guard
The publish step checks npm view <package>@<version> before publishing. If the
version already exists, it skips with a success exit code. This makes the npm
workflow safe to re-run — unlike Dart/Rust, it won't fail on a duplicate version.
No secret: trusted publishing
Since 1.2.5 the npm workflow publishes over OIDC rather than a long-lived token,
so there is no NPM_TOKEN to rotate. Two things have to line up instead:
- The
publishjob declarespermissions: id-token: write. - The trusted publisher on
npmjs.com/package/@ondeinference/react-native/access names repo
ondeinference/onde, workflowrelease-sdk-npm.yml, and no environment.
If those drift apart, npm answers the publish with a 404 on PUT rather than an
auth error, which reads as a missing package and sends you looking in the wrong
place. Adding an environment: to the job without setting the matching
Environment name field on that npm page is the usual way to cause it.
The job also upgrades npm before publishing: Node 22 ships npm 10, and trusted publishing needs 11.5.1 or newer.
CI Pipeline: Dart SDK
Workflow: .github/workflows/release-sdk-dart.yml
Trigger: Tag push matching [0-9]+.[0-9]+.[0-9]+
Runner: ubuntu-latest
Flow
Tag push (e.g. 0.1.3)
│
├─ validate job:
│ ├─ Set up Flutter (version from sdk/dart/.flutter-version)
│ ├─ Read version from pubspec.yaml
│ ├─ Validate tag == pubspec.yaml version
│ │ └─ Fails fast if mismatched
│ ├─ flutter pub get
│ ├─ flutter analyze --no-fatal-infos
│ └─ flutter pub publish --dry-run
│
└─ publish job (tag push only, needs: validate):
├─ Set up Flutter
├─ flutter pub get
├─ Write PUB_CREDENTIALS to ~/.config/dart/pub-credentials.json
└─ flutter pub publish --force
Tag validation (Dart)
The Dart workflow independently validates the tag against pubspec.yaml:
if [ "$TAG" != "$PUBSPEC_VERSION" ]; then
echo "::error::Tag '$TAG' does not match pubspec.yaml version '$PUBSPEC_VERSION'."
exit 1
fi
This means both Cargo.toml and pubspec.yaml must have the same version
as the git tag, or both workflows fail.
Required secret
PUB_CREDENTIALS — the full JSON content of ~/.config/dart/pub-credentials.json
from a machine that has run dart pub login. Add as a repository secret on onde.
GitHub Releases (two repos)
| Repo | Created by | Contains |
|---|---|---|
onde |
release-sdk-swift.yml (step 5) |
XCFramework zip + checksum, auto-generated release notes |
onde-swift |
onde-swift release.yml |
Installation snippet, platform table, link to upstream changelog |
Both repos need GitHub Releases because they serve different audiences:
onde— Rust consumers, asset hosting for the XCFramework binaryonde-swift— Swift developers, Swift Package Index surfaces these as version notes
Version Sources and Validation Matrix
| Source | Read by | Validated against |
|---|---|---|
Cargo.toml version |
build-swift-xcframework.sh (tomllib) |
Git tag (Swift workflow) |
sdk/dart/pubspec.yaml version |
release-sdk-dart.yml (grep + awk) |
Git tag (Dart workflow) |
Git tag (github.ref_name) |
Both workflows | Both version files above |
onde-swift tag |
Created by release-sdk-swift.yml |
Same value as the onde tag |
All four must match. If any pair diverges, the relevant CI job fails.
Secrets Required
| Secret | Used by | Purpose |
|---|---|---|
CARGO_REGISTRY_TOKEN |
release-sdk-rust.yml |
Authenticate with crates.io for cargo publish. Scoped to publish-update for the onde crate. Create at https://crates.io/settings/tokens. |
ONDE_SWIFT_PAT |
release-sdk-swift.yml |
Push commits + tags to ondeinference/onde-swift. Must be a PAT (not GITHUB_TOKEN) so it triggers workflows on onde-swift. Needs contents: write scope. |
PUB_CREDENTIALS |
release-sdk-dart.yml |
Authenticate with pub.dev for flutter pub publish. Full JSON from ~/.config/dart/pub-credentials.json. |
ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME |
release-sdk-kotlin.yml |
Sonatype Central Portal token username. Generate at central.sonatype.com → Profile → Generate User Token. |
ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD |
release-sdk-kotlin.yml |
Sonatype Central Portal token password. Same dialog as above. |
ORG_GRADLE_PROJECT_SIGNINGKEYID |
release-sdk-kotlin.yml |
Last 8 chars of GPG key fingerprint used to sign Maven Central artifacts. |
ORG_GRADLE_PROJECT_SIGNINGKEY |
release-sdk-kotlin.yml |
ASCII-armored GPG private key. Export with gpg --armor --export-secret-keys <KEY_ID>. |
ORG_GRADLE_PROJECT_SIGNINGPASSWORD |
release-sdk-kotlin.yml |
Passphrase for the GPG signing key. |
GRESIQ_API_KEY_DEV |
All release workflows | Build-time env for dev environment API key. |
GRESIQ_API_SECRET_DEV |
All release workflows | Build-time env for dev environment API secret. |
GRESIQ_API_KEY_PRODUCTION |
All release workflows | Build-time env for production API key. |
GRESIQ_API_SECRET_PRODUCTION |
All release workflows | Build-time env for production API secret. |
HF_TOKEN |
All release workflows | HuggingFace token baked in at build time (option_env!). |
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
Forgot to bump pubspec.yaml |
Dart CI fails: "Tag '0.1.3' does not match pubspec.yaml version '0.1.2'" | Bump sdk/dart/pubspec.yaml, amend commit, re-tag |
Forgot to bump sdk/react-native/package.json |
npm CI fails: "Tag '0.1.3' does not match package.json version '0.1.2'" | Bump sdk/react-native/package.json, amend commit, re-tag |
Forgot to bump sdk/react-native/rust/Cargo.toml |
npm SDK's Rust bridge crate version drifts from the main crate | Bump sdk/react-native/rust/Cargo.toml, run cd sdk/react-native/rust && cargo update -p onde |
Forgot to update sdk/dart/rust/Cargo.lock |
Dart SDK's Rust bridge builds against stale onde version |
Run cd sdk/dart/rust && cargo update -p onde |
Forgot to update sdk/react-native/rust/Cargo.lock |
npm SDK's Rust bridge builds against stale onde version |
Run cd sdk/react-native/rust && cargo update -p onde |
Forgot to update sdk/dart/CHANGELOG.md |
pub.dev shows stale changelog | Prepend new ## 0.1.3 section before tagging |
Forgot to bump sdk/react-native/CHANGELOG.md |
npm shows stale changelog | Prepend new ## 0.1.3 section before tagging |
Forgot to update the root CHANGELOG.md |
Preflight fails: "CHANGELOG.md: no '## 0.1.3' section" | Prepend the new section before tagging |
| crates.io publish reports success but nothing is published | cargo publish step logs "already on crates.io — skipping" for a version that is not there |
Fixed in 1.2.5. The guard used cargo info, which resolves against the local workspace. If it recurs, check that scripts/crates-io-published.py is still what the workflow calls |
Forgot to bump sdk/kotlin/gradle.properties VERSION_NAME |
Kotlin CI fails: tag vs gradle.properties mismatch | Bump VERSION_NAME, amend commit, re-tag |
Tag has v prefix (v0.1.3) |
CI does not trigger — tag pattern requires bare semver | Delete the tag, re-tag without v |
CARGO_REGISTRY_TOKEN missing |
cargo publish fails with auth error |
Create a scoped token at crates.io/settings/tokens, add as repo secret |
ONDE_SWIFT_PAT expired |
onde-swift push fails with 403 |
Regenerate PAT at github.com/settings/tokens, update repo secret |
PUB_CREDENTIALS expired |
flutter pub publish fails with 401 |
Run dart pub login locally, copy new credentials JSON to repo secret |
| npm trusted publisher does not match the workflow | npm publish fails with a 404 on PUT, not a 401 |
Check repo, workflow filename, and environment on npmjs.com/package/@ondeinference/react-native/access against what the publish job declares |
| crates.io version already published | cargo publish fails with "already uploaded" |
crates.io is immutable — bump to next version. cargo yank hides but doesn't delete. |
| npm version already published | npm publish step skips (idempotency guard) | Safe — the workflow exits 0. No action needed. |
| pub.dev version already published | flutter pub publish fails |
pub.dev is immutable — bump to next version |
| Maven Central version already published | publishAndReleaseToMavenCentral fails |
Maven Central is immutable — bump to next version |
| Maven Central namespace not verified | Deployment fails: "Namespace 'com.ondeinference' is not allowed" | Verify namespace on central.sonatype.com (add DNS TXT record, wait for Sonatype to poll) |
Tag pushed before workflow exists on main |
Workflow never fires for that tag | Use workflow_dispatch for dry-run, then publish manually. See Manual Publish Fallback. |
| All 5 workflows fire but only 1–2 need re-publishing | Collateral re-runs of Swift/npm/Dart/Rust/Kotlin | Don't delete+re-push the tag. Use Manual Publish Fallback for the specific SDKs. |
onde-swift tag already exists |
Tag push skipped (warning emitted) | Delete remote tag first: git push origin :refs/tags/0.1.3 |
Force-pushed a tag on onde-swift |
SPM users get stale Package.resolved |
Never force-push. Delete + re-create instead. Advise consumers to run swift package resolve |
| XCFramework URL 404 | swift package resolve fails on consumer side |
Ensure the onde GitHub Release was created BEFORE the onde-swift tag was pushed (the workflow handles this order automatically) |
Package.swift regex didn't match |
Python RuntimeError in CI |
Check that onde-swift/Package.swift still has a .binaryTarget(name: "OndeFramework", ...) block |
Dart flutter analyze fails |
validate job fails, publish is skipped |
Fix analysis issues, re-tag |
Manual Publish Fallback
When a tag was pushed before a workflow existed on main, or when you need to
publish only a subset of SDKs without re-triggering all four workflows:
Rust (crates.io)
# Dry-run first
cargo publish --dry-run
# Publish
cargo publish
Requires ~/.cargo/credentials.toml or CARGO_REGISTRY_TOKEN env var.
Dart (pub.dev)
cd sdk/dart
# Dry-run
flutter pub publish --dry-run
# Publish
flutter pub publish --force
Requires ~/.config/dart/pub-credentials.json from dart pub login.
Kotlin (Maven Central)
cd sdk/kotlin
# Dry-run
./gradlew :lib:publishToMavenCentral --dry-run --no-configuration-cache
# Publish and release
./gradlew :lib:publishAndReleaseToMavenCentral --no-configuration-cache
Requires ~/.gradle/gradle.properties with mavenCentralUsername,
mavenCentralPassword, signingKeyId, signingKey, signingPassword.
Also requires the com.ondeinference namespace to be verified on
central.sonatype.com.
npm (React Native)
cd sdk/react-native
npm run build
npm publish --access public
Requires npm login. Trusted publishing is CI-only, so a manual publish falls
back to your own npm credentials.
When to use manual publish
| Scenario | Approach |
|---|---|
Tag exists but workflow wasn't on main yet |
Merge workflow, then publish manually |
| One SDK's CI failed, others succeeded | Fix the issue, publish that SDK manually |
| Need to avoid re-triggering Swift XCFramework build | Publish Rust/Dart/npm/Kotlin manually |
| Testing a new workflow before first real tag | Use workflow_dispatch for dry-run only |
Re-releasing a Version (Emergency Fix)
If a release was botched and you need to re-publish the same version:
Swift (onde-swift)
# 1. Delete the remote tag on onde-swift
git -C /path/to/onde-swift push origin :refs/tags/0.1.3
# 2. Delete the GitHub Release on onde-swift (via GitHub UI or gh CLI)
gh release delete 0.1.3 --repo ondeinference/onde-swift --yes
# 3. Delete the GitHub Release on onde
gh release delete 0.1.3 --repo ondeinference/onde --yes
# 4. Delete the local and remote tag on onde
git tag -d 0.1.3
git push origin :refs/tags/0.1.3
# 5. Fix the issue, commit, re-tag, push
git tag 0.1.3
git push origin main 0.1.3
Dart (pub.dev) / Rust (crates.io) / Kotlin (Maven Central)
pub.dev, crates.io, and Maven Central are all immutable. Once a version is
published, it cannot be re-published. You must bump to 0.1.4 instead.
- crates.io:
cargo yank --version 0.1.3hides the version from dependency resolution but does not delete it. - pub.dev: No yank equivalent. The version remains visible and installable.
If the publish job failed (i.e. the package was NOT published), you can
safely re-tag after fixing the issue.
npm
The npm workflow's idempotency guard means re-running it is safe — it skips if the version already exists. However, npm also does not allow re-publishing the same version with different content. Bump to the next version if the published content was wrong.
End-to-End Release Timeline
Developer merges feature → development → main (--no-ff) then tags
│
├─── release-sdk-rust.yml fires ──────────────────────────────────────┐
│ ~2 min (dry-run + publish to crates.io) │
│ │
├─── release-sdk-swift.yml fires ──────────────────────────────────────┤
│ ~15 min (XCFramework build) │
│ Creates GitHub Release on onde │
│ Pushes commit + tag to onde-swift │
│ │ │
│ ├─ onde-swift ci.yml fires (push to main) │
│ │ ~5 min (validate + build 5 platforms) │
│ │ │
│ └─ onde-swift release.yml fires (tag 1.0.0) │
│ ~5 min (validate + create GitHub Release) │
│ │
├─── release-sdk-dart.yml fires ───────────────────────────────────────┤
│ ~3 min (validate + publish to pub.dev) │
│ │
├─── release-sdk-npm.yml fires ───────────────────────────────────────┤
│ ~3 min (validate + publish to npm) │
│ │
├─── release-sdk-kotlin.yml fires ────────────────────────────────────┤
│ ~10 min (Android ABIs + JVM dylib + publish to Maven Central) │
│ │
└─── Swift Package Index picks up onde-swift tag ──────────────────────┘
~30 min (SPI polling interval)
klibs.io picks up Maven Central artifact within ~30 days
All five CI workflows fire in parallel from the single tag push. The only
ordering dependency is that release-sdk-swift must create the onde GitHub
Release (with XCFramework assets) before pushing the tag to onde-swift —
this is guaranteed by the step order within the workflow.
Merge train
Always merge through the branch train before tagging. Use --no-ff to
create explicit merge commits — they serve as bookmarks in the history
showing where each feature landed.
git checkout development && git merge feature/my-branch --no-ff --no-edit
git checkout main && git merge development --no-ff --no-edit
git push origin development main
git tag X.Y.Z && git push origin X.Y.Z
Never tag from a feature branch. The tag must point to a commit on main that
contains all workflow files and fixes. If a workflow is added on a feature branch
and the tag is pushed before merging, the workflow won't exist at the tagged
commit and CI won't fire.
Why --no-ff: Multi-commit feature branches (like the Kotlin SDK with 35
files across 6 commits) look like they were committed directly to main with
fast-forward merges. --no-ff preserves the branch context. It also allows
reverting an entire feature with a single git revert -m 1 <merge-commit>.
Distribution Registry Reference
| Registry | Package Name | Import | Workflow | Immutable? |
|---|---|---|---|---|
| crates.io | onde |
onde = "1.0" |
release-sdk-rust.yml |
Yes (yank hides, doesn't delete) |
| Swift Package Index | onde-swift (org: ondeinference) |
import Onde |
release-sdk-swift.yml → onde-swift/release.yml |
Tags are immutable by convention |
| pub.dev | onde_inference |
import 'package:onde_inference/onde_inference.dart' |
release-sdk-dart.yml |
Yes (no yank) |
| npm | @ondeinference/react-native |
import { OndeChatEngine } from "@ondeinference/react-native" |
release-sdk-npm.yml |
Yes (unpublish within 72h only) |
| Maven Central | com.ondeinference:onde-inference |
implementation("com.ondeinference:onde-inference:1.0.0") |
release-sdk-kotlin.yml |
Yes (cannot re-publish same version) |
| klibs.io | onde-inference |
Same Maven Central coordinate | Auto-indexed from Maven Central | N/A (discovery only) |
React Native npm SDK
Package: @ondeinference/react-native (scoped under @ondeinference org on npm)
Location: sdk/react-native/
Architecture: Expo native module wrapping Rust via C FFI
How it works
TypeScript (React Native)
│ import { OndeChatEngine } from "@ondeinference/react-native"
▼
Expo Module (Swift / Kotlin)
│ OndeInferenceModule — calls C FFI functions via @_silgen_name (iOS) / JNI (Android)
▼
Rust C FFI bridge (sdk/react-native/rust/)
│ extern "C" functions with JSON serialization, global tokio::Runtime
▼
onde crate (src/)
│ ChatEngine — tokio::sync::Mutex, mistral.rs inference
▼
mistral.rs → Metal (iOS) / CPU (Android)
Key files
| File | Purpose |
|---|---|
sdk/react-native/package.json |
npm package — @ondeinference/react-native |
sdk/react-native/expo-module.config.json |
Expo autolinking config |
sdk/react-native/src/index.ts |
Public TypeScript API — OndeChatEngine, free functions, JSON ↔ camelCase conversion |
sdk/react-native/src/types.ts |
TypeScript type definitions mirroring Rust types |
sdk/react-native/src/OndeInferenceModule.ts |
requireNativeModule("OndeInference") bridge |
sdk/react-native/rust/src/lib.rs |
C FFI exports (extern "C") + Android JNI wrappers |
sdk/react-native/ios/OndeInferenceModule.swift |
Swift Expo module — calls Rust via @_silgen_name |
sdk/react-native/android/.../OndeInferenceModule.kt |
Kotlin Expo module — calls Rust via JNI external fun |
sdk/react-native/scripts/build-rust.sh |
Cross-compile Rust for iOS (staticlib) + Android (cdylib) |
Building native libraries
# iOS (requires rustup targets: aarch64-apple-ios, aarch64-apple-ios-sim)
cd sdk/react-native && ./scripts/build-rust.sh ios
# Android (requires ANDROID_NDK_HOME)
cd sdk/react-native && ./scripts/build-rust.sh android
# Both
cd sdk/react-native && ./scripts/build-rust.sh all
Publishing to npm
cd sdk/react-native
npm run build # TypeScript → build/
npm publish --access public
The --access public flag is required for scoped packages on first publish.
Registry Immutability Summary
All four registries are effectively immutable once a version is published:
- crates.io —
cargo yankremoves from resolver but the tarball stays forever - pub.dev — No retraction mechanism at all
- npm —
npm unpublishworks within 72 hours, but only if no dependents - Maven Central — No retraction mechanism. Once published, it's permanent.
- Swift (onde-swift tags) — Convention: never force-push tags. Delete + re-create if needed.
Bottom line: get the release right before publishing. Use --dry-run locally
and via workflow_dispatch before pushing a real tag.
Update this skill when adding new SDK targets (Python, Ruby, Kotlin), changing CI runners, or modifying the tag/version validation logic.