|
1
|
--- |
|
2
|
name: sdk-releases |
|
3
|
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. |
|
4
|
allowed-tools: Read, Write, Edit, Glob, Grep, Bash |
|
5
|
user-invocable: true |
|
6
|
--- |
|
7
|
|
|
8
|
# Skill: SDK Releases |
|
9
|
|
|
10
|
## What This Skill Covers |
|
11
|
|
|
12
|
The complete release pipeline for shipping Onde across all registries: |
|
13
|
|
|
14
|
- **Rust crate** → crates.io (`onde`) |
|
15
|
- **Swift package** → Swift Package Index (`onde-swift`) |
|
16
|
- **Dart/Flutter package** → pub.dev (`onde_inference`) |
|
17
|
- **React Native** → npm (`@ondeinference/react-native`) |
|
18
|
- **Kotlin Multiplatform** → Maven Central (`com.ondeinference:onde-inference`) |
|
19
|
|
|
20
|
All five SDK workflows share the same tag trigger. A single tag push fires all |
|
21
|
five in parallel. When only a subset needs (re-)publishing, use the **manual |
|
22
|
publish fallback** documented below to avoid collateral re-runs. |
|
23
|
|
|
24
|
--- |
|
25
|
|
|
26
|
## Version Bump Checklist |
|
27
|
|
|
28
|
Every release starts with a version bump. **All version sources must be updated |
|
29
|
in a single commit before tagging.** |
|
30
|
|
|
31
|
| # | File | Field | Example | |
|
32
|
|---|------|-------|---------| |
|
33
|
| 1 | `Cargo.toml` (root) | `version` | `version = "0.1.3"` | |
|
34
|
| 2 | `sdk/dart/pubspec.yaml` | `version` | `version: 0.1.3` | |
|
35
|
| 3 | `sdk/dart/CHANGELOG.md` | New `## 0.1.3` section | Prepend at top of file | |
|
36
|
| 4 | `sdk/react-native/package.json` | `version` | `"version": "0.1.3"` | |
|
37
|
| 5 | `sdk/react-native/rust/Cargo.toml` | `version` | `version = "0.1.3"` | |
|
38
|
| 6 | `sdk/react-native/CHANGELOG.md` | New `## 0.1.3` section | Prepend at top of file | |
|
39
|
| 7 | `sdk/kotlin/gradle.properties` | `VERSION_NAME` | `VERSION_NAME=0.1.3` | |
|
40
|
| 8 | `sdk/dart/rust/Cargo.lock` | `onde` package version | Run `cd sdk/dart/rust && cargo update -p onde` | |
|
41
|
| 9 | `sdk/react-native/rust/Cargo.lock` | `onde` package version | Run `cd sdk/react-native/rust && cargo update -p onde` | |
|
42
|
| 10 | `Cargo.lock` (root) | `onde` package version | Run `cargo check` at repo root | |
|
43
|
| 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 | |
|
44
|
|
|
45
|
### Files you do NOT manually edit |
|
46
|
|
|
47
|
| File | Why | |
|
48
|
|------|-----| |
|
49
|
| `onde-swift/Package.swift` | CI rewrites the `url:` + `checksum:` automatically | |
|
50
|
| `onde-swift/Sources/Onde/onde.swift` | CI copies the freshly generated UniFFI glue | |
|
51
|
|
|
52
|
### Quick bump commands |
|
53
|
|
|
54
|
```bash |
|
55
|
# 1. Edit Cargo.toml version (manual) |
|
56
|
# 2. Edit sdk/dart/pubspec.yaml version (manual) |
|
57
|
# 3. Prepend new section to sdk/dart/CHANGELOG.md (manual) |
|
58
|
# 4. Edit sdk/react-native/package.json version (manual) |
|
59
|
# 5. Edit sdk/react-native/rust/Cargo.toml version (manual) |
|
60
|
# 6. Prepend new section to sdk/react-native/CHANGELOG.md (manual) |
|
61
|
# 7. Edit sdk/kotlin/gradle.properties VERSION_NAME (manual) |
|
62
|
# 8. Prepend new section to CHANGELOG.md at the repo root (manual) |
|
63
|
|
|
64
|
# 9. Sync lockfiles |
|
65
|
cargo check # updates root Cargo.lock |
|
66
|
cd sdk/dart/rust && cargo update -p onde # updates Dart SDK's Cargo.lock |
|
67
|
cd sdk/react-native/rust && cargo update -p onde # updates React Native SDK's Cargo.lock |
|
68
|
|
|
69
|
# 10. Verify |
|
70
|
grep '^version' Cargo.toml # "0.1.3" |
|
71
|
grep '^version:' sdk/dart/pubspec.yaml # 0.1.3 |
|
72
|
grep '"version"' sdk/react-native/package.json # "0.1.3" |
|
73
|
grep '^version' sdk/react-native/rust/Cargo.toml # "0.1.3" |
|
74
|
grep 'VERSION_NAME' sdk/kotlin/gradle.properties # 1.0.0 |
|
75
|
grep 'name = "onde"' -A1 Cargo.lock # version = "0.1.3" |
|
76
|
grep 'name = "onde"' -A1 sdk/dart/rust/Cargo.lock # version = "0.1.3" |
|
77
|
grep 'name = "onde"' -A1 sdk/react-native/rust/Cargo.lock # version = "0.1.3" |
|
78
|
|
|
79
|
# 11. Commit and tag |
|
80
|
git add -A |
|
81
|
git commit -m "0.1.3" |
|
82
|
git tag 0.1.3 |
|
83
|
git push origin main 0.1.3 |
|
84
|
``` |
|
85
|
|
|
86
|
--- |
|
87
|
|
|
88
|
## Tag Format |
|
89
|
|
|
90
|
All tags are **bare semver** — no `v` prefix. |
|
91
|
|
|
92
|
``` |
|
93
|
0.1.3 ✅ correct |
|
94
|
v0.1.3 ❌ will NOT trigger CI |
|
95
|
0.1.3-beta ❌ will NOT match the tag pattern |
|
96
|
``` |
|
97
|
|
|
98
|
All five CI workflows use the same trigger pattern: |
|
99
|
|
|
100
|
```yaml |
|
101
|
on: |
|
102
|
push: |
|
103
|
tags: |
|
104
|
- "[0-9]+.[0-9]+.[0-9]+" |
|
105
|
``` |
|
106
|
|
|
107
|
--- |
|
108
|
|
|
109
|
## CI Pipeline: Rust SDK (crates.io) |
|
110
|
|
|
111
|
**Workflow:** `.github/workflows/release-sdk-rust.yml` |
|
112
|
**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` or `workflow_dispatch` |
|
113
|
**Runner:** `macos-26` |
|
114
|
|
|
115
|
### Flow |
|
116
|
|
|
117
|
``` |
|
118
|
Tag push (e.g. 1.0.0) |
|
119
|
│ |
|
120
|
├─ 1. Checkout + install stable Rust |
|
121
|
│ |
|
122
|
├─ 2. Validate tag == Cargo.toml version |
|
123
|
│ └─ Fails fast if mismatched |
|
124
|
│ |
|
125
|
├─ 3. cargo publish --dry-run |
|
126
|
│ └─ Compiles and packages; catches errors before real publish |
|
127
|
│ |
|
128
|
└─ 4. cargo publish (tag push only) |
|
129
|
└─ Authenticates via CARGO_REGISTRY_TOKEN secret |
|
130
|
``` |
|
131
|
|
|
132
|
### Key details |
|
133
|
|
|
134
|
- **`workflow_dispatch`** runs the dry-run only (the publish step has |
|
135
|
`if: startsWith(github.ref, 'refs/tags/')`) — useful for validation. |
|
136
|
- **Runner is `macos-26`** because the crate depends on `mistralrs` with Metal |
|
137
|
features, which requires macOS + Xcode to compile. |
|
138
|
- **crates.io is immutable** — once a version is published, it cannot be |
|
139
|
re-published. You must bump to the next version. `cargo yank` hides a version |
|
140
|
from dependency resolution but does not delete it. |
|
141
|
|
|
142
|
### Required secret |
|
143
|
|
|
144
|
`CARGO_REGISTRY_TOKEN` — a crates.io API token scoped to `publish-update` for |
|
145
|
the `onde` crate. Create at https://crates.io/settings/tokens. Best practice: |
|
146
|
scope it to a single crate rather than using a global token. |
|
147
|
|
|
148
|
--- |
|
149
|
|
|
150
|
## CI Pipeline: Swift SDK |
|
151
|
|
|
152
|
**Workflow:** `.github/workflows/release-sdk-swift.yml` |
|
153
|
**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` |
|
154
|
**Runner:** `macos-15` |
|
155
|
|
|
156
|
### Flow |
|
157
|
|
|
158
|
``` |
|
159
|
Tag push (e.g. 0.1.3) |
|
160
|
│ |
|
161
|
├─ 1. Build XCFramework |
|
162
|
│ └─ .github/scripts/build-swift-xcframework.sh |
|
163
|
│ ├─ Extracts version from Cargo.toml via tomllib |
|
164
|
│ ├─ Compiles staticlibs for 5 Apple targets |
|
165
|
│ ├─ Generates UniFFI Swift bindings |
|
166
|
│ ├─ Assembles XCFramework, zips, computes SHA-256 |
|
167
|
│ └─ Writes dist/swift/{version.txt, OndeFramework.checksum.txt} |
|
168
|
│ |
|
169
|
├─ 2. Read version + checksum into step outputs |
|
170
|
│ |
|
171
|
├─ 3. Validate tag == Cargo.toml version |
|
172
|
│ └─ Fails fast if mismatched (e.g. tagged 0.1.3 but Cargo.toml says 0.1.2) |
|
173
|
│ |
|
174
|
├─ 4. Upload CI artifacts (every run, including workflow_dispatch) |
|
175
|
│ |
|
176
|
├─ 5. Create GitHub Release on `onde` (tag push only) |
|
177
|
│ └─ Attaches OndeFramework.xcframework.zip + checksum |
|
178
|
│ |
|
179
|
├─ 6. Checkout ondeinference/onde-swift (using ONDE_SWIFT_PAT) |
|
180
|
│ |
|
181
|
├─ 7. Rewrite Package.swift |
|
182
|
│ └─ Python regex replaces .binaryTarget url: + checksum: |
|
183
|
│ |
|
184
|
├─ 8. Copy generated onde.swift → onde-swift/Sources/Onde/ |
|
185
|
│ |
|
186
|
└─ 9. Commit + tag onde-swift |
|
187
|
├─ git commit -m "Release 0.1.3" (skipped if no changes) |
|
188
|
├─ git push origin HEAD:main |
|
189
|
└─ git tag -a "0.1.3" -m "Release 0.1.3" (skipped if tag exists) |
|
190
|
└─ Annotated tag, never force-pushed |
|
191
|
``` |
|
192
|
|
|
193
|
### What happens on `onde-swift` after the push |
|
194
|
|
|
195
|
The `onde` CI pushes a commit (to `main`) and a tag (e.g. `0.1.3`) to `onde-swift`. |
|
196
|
This triggers **two** workflows on `onde-swift`: |
|
197
|
|
|
198
|
1. **`ci.yml`** (on push to `main`) — validates manifest + builds all 5 platforms |
|
199
|
2. **`release.yml`** (on tag push) — validates package, then creates a GitHub |
|
200
|
Release with installation instructions and a link to the upstream `onde` changelog |
|
201
|
|
|
202
|
Both run concurrently without conflict — they use different concurrency groups |
|
203
|
(`ci-refs/heads/main` vs the tag ref). |
|
204
|
|
|
205
|
### Tag immutability |
|
206
|
|
|
207
|
Tags on `onde-swift` are **never force-pushed**. SPM caches the resolved commit |
|
208
|
SHA per tag in `Package.resolved`. Moving a tag silently breaks every consumer's |
|
209
|
lockfile. If a release needs to be redone: |
|
210
|
|
|
211
|
1. Delete the remote tag: `git push origin :refs/tags/0.1.3` |
|
212
|
2. Delete the GitHub Release on both `onde` and `onde-swift` |
|
213
|
3. Fix the issue, re-tag, push again |
|
214
|
|
|
215
|
--- |
|
216
|
|
|
217
|
## CI Pipeline: npm SDK (React Native) |
|
218
|
|
|
219
|
**Workflow:** `.github/workflows/release-sdk-npm.yml` |
|
220
|
**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` or `workflow_dispatch` |
|
221
|
**Runner:** `ubuntu-latest` |
|
222
|
|
|
223
|
### Flow |
|
224
|
|
|
225
|
``` |
|
226
|
Tag push (e.g. 1.0.0) |
|
227
|
│ |
|
228
|
├─ validate job: |
|
229
|
│ ├─ Set up Node.js 22 |
|
230
|
│ ├─ Read version from sdk/react-native/package.json |
|
231
|
│ ├─ Validate tag == package.json version |
|
232
|
│ ├─ npm install + npm run build |
|
233
|
│ └─ npm publish --dry-run --access public |
|
234
|
│ |
|
235
|
└─ publish job (tag push only, needs: validate): |
|
236
|
├─ npm install + npm run build |
|
237
|
├─ Check if version already exists on npm |
|
238
|
│ └─ If yes → skip publish (idempotent, exits 0) |
|
239
|
└─ npm publish --access public |
|
240
|
``` |
|
241
|
|
|
242
|
### npm idempotency guard |
|
243
|
|
|
244
|
The publish step checks `npm view <package>@<version>` before publishing. If the |
|
245
|
version already exists, it **skips** with a success exit code. This makes the npm |
|
246
|
workflow safe to re-run — unlike Dart/Rust, it won't fail on a duplicate version. |
|
247
|
|
|
248
|
### No secret: trusted publishing |
|
249
|
|
|
250
|
Since 1.2.5 the npm workflow publishes over OIDC rather than a long-lived token, |
|
251
|
so there is no `NPM_TOKEN` to rotate. Two things have to line up instead: |
|
252
|
|
|
253
|
- The `publish` job declares `permissions: id-token: write`. |
|
254
|
- The trusted publisher on |
|
255
|
npmjs.com/package/@ondeinference/react-native/access names repo |
|
256
|
`ondeinference/onde`, workflow `release-sdk-npm.yml`, and no environment. |
|
257
|
|
|
258
|
If those drift apart, npm answers the publish with a 404 on PUT rather than an |
|
259
|
auth error, which reads as a missing package and sends you looking in the wrong |
|
260
|
place. Adding an `environment:` to the job without setting the matching |
|
261
|
Environment name field on that npm page is the usual way to cause it. |
|
262
|
|
|
263
|
The job also upgrades npm before publishing: Node 22 ships npm 10, and trusted |
|
264
|
publishing needs 11.5.1 or newer. |
|
265
|
|
|
266
|
--- |
|
267
|
|
|
268
|
## CI Pipeline: Dart SDK |
|
269
|
|
|
270
|
**Workflow:** `.github/workflows/release-sdk-dart.yml` |
|
271
|
**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` |
|
272
|
**Runner:** `ubuntu-latest` |
|
273
|
|
|
274
|
### Flow |
|
275
|
|
|
276
|
``` |
|
277
|
Tag push (e.g. 0.1.3) |
|
278
|
│ |
|
279
|
├─ validate job: |
|
280
|
│ ├─ Set up Flutter (version from sdk/dart/.flutter-version) |
|
281
|
│ ├─ Read version from pubspec.yaml |
|
282
|
│ ├─ Validate tag == pubspec.yaml version |
|
283
|
│ │ └─ Fails fast if mismatched |
|
284
|
│ ├─ flutter pub get |
|
285
|
│ ├─ flutter analyze --no-fatal-infos |
|
286
|
│ └─ flutter pub publish --dry-run |
|
287
|
│ |
|
288
|
└─ publish job (tag push only, needs: validate): |
|
289
|
├─ Set up Flutter |
|
290
|
├─ flutter pub get |
|
291
|
├─ Write PUB_CREDENTIALS to ~/.config/dart/pub-credentials.json |
|
292
|
└─ flutter pub publish --force |
|
293
|
``` |
|
294
|
|
|
295
|
### Tag validation (Dart) |
|
296
|
|
|
297
|
The Dart workflow independently validates the tag against `pubspec.yaml`: |
|
298
|
|
|
299
|
```yaml |
|
300
|
if [ "$TAG" != "$PUBSPEC_VERSION" ]; then |
|
301
|
echo "::error::Tag '$TAG' does not match pubspec.yaml version '$PUBSPEC_VERSION'." |
|
302
|
exit 1 |
|
303
|
fi |
|
304
|
``` |
|
305
|
|
|
306
|
This means **both** `Cargo.toml` and `pubspec.yaml` must have the same version |
|
307
|
as the git tag, or both workflows fail. |
|
308
|
|
|
309
|
### Required secret |
|
310
|
|
|
311
|
`PUB_CREDENTIALS` — the full JSON content of `~/.config/dart/pub-credentials.json` |
|
312
|
from a machine that has run `dart pub login`. Add as a repository secret on `onde`. |
|
313
|
|
|
314
|
--- |
|
315
|
|
|
316
|
## GitHub Releases (two repos) |
|
317
|
|
|
318
|
| Repo | Created by | Contains | |
|
319
|
|------|-----------|----------| |
|
320
|
| `onde` | `release-sdk-swift.yml` (step 5) | XCFramework zip + checksum, auto-generated release notes | |
|
321
|
| `onde-swift` | `onde-swift` `release.yml` | Installation snippet, platform table, link to upstream changelog | |
|
322
|
|
|
323
|
Both repos need GitHub Releases because they serve different audiences: |
|
324
|
|
|
325
|
- **`onde`** — Rust consumers, asset hosting for the XCFramework binary |
|
326
|
- **`onde-swift`** — Swift developers, Swift Package Index surfaces these as version notes |
|
327
|
|
|
328
|
--- |
|
329
|
|
|
330
|
## Version Sources and Validation Matrix |
|
331
|
|
|
332
|
| Source | Read by | Validated against | |
|
333
|
|--------|---------|-------------------| |
|
334
|
| `Cargo.toml` `version` | `build-swift-xcframework.sh` (tomllib) | Git tag (Swift workflow) | |
|
335
|
| `sdk/dart/pubspec.yaml` `version` | `release-sdk-dart.yml` (grep + awk) | Git tag (Dart workflow) | |
|
336
|
| Git tag (`github.ref_name`) | Both workflows | Both version files above | |
|
337
|
| `onde-swift` tag | Created by `release-sdk-swift.yml` | Same value as the `onde` tag | |
|
338
|
|
|
339
|
All four must match. If any pair diverges, the relevant CI job fails. |
|
340
|
|
|
341
|
--- |
|
342
|
|
|
343
|
## Secrets Required |
|
344
|
|
|
345
|
| Secret | Used by | Purpose | |
|
346
|
|--------|---------|---------| |
|
347
|
| `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. | |
|
348
|
| `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. | |
|
349
|
| `PUB_CREDENTIALS` | `release-sdk-dart.yml` | Authenticate with pub.dev for `flutter pub publish`. Full JSON from `~/.config/dart/pub-credentials.json`. | |
|
350
|
| `ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME` | `release-sdk-kotlin.yml` | Sonatype Central Portal token username. Generate at central.sonatype.com → Profile → Generate User Token. | |
|
351
|
| `ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD` | `release-sdk-kotlin.yml` | Sonatype Central Portal token password. Same dialog as above. | |
|
352
|
| `ORG_GRADLE_PROJECT_SIGNINGKEYID` | `release-sdk-kotlin.yml` | Last 8 chars of GPG key fingerprint used to sign Maven Central artifacts. | |
|
353
|
| `ORG_GRADLE_PROJECT_SIGNINGKEY` | `release-sdk-kotlin.yml` | ASCII-armored GPG private key. Export with `gpg --armor --export-secret-keys <KEY_ID>`. | |
|
354
|
| `ORG_GRADLE_PROJECT_SIGNINGPASSWORD` | `release-sdk-kotlin.yml` | Passphrase for the GPG signing key. | |
|
355
|
| `GRESIQ_API_KEY_DEV` | All release workflows | Build-time env for dev environment API key. | |
|
356
|
| `GRESIQ_API_SECRET_DEV` | All release workflows | Build-time env for dev environment API secret. | |
|
357
|
| `GRESIQ_API_KEY_PRODUCTION` | All release workflows | Build-time env for production API key. | |
|
358
|
| `GRESIQ_API_SECRET_PRODUCTION` | All release workflows | Build-time env for production API secret. | |
|
359
|
| `HF_TOKEN` | All release workflows | HuggingFace token baked in at build time (`option_env!`). | |
|
360
|
|
|
361
|
--- |
|
362
|
|
|
363
|
## Common Pitfalls |
|
364
|
|
|
365
|
| Pitfall | Symptom | Fix | |
|
366
|
|---------|---------|-----| |
|
367
|
| 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 | |
|
368
|
| 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 | |
|
369
|
| 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` | |
|
370
|
| 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` | |
|
371
|
| 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` | |
|
372
|
| Forgot to update `sdk/dart/CHANGELOG.md` | pub.dev shows stale changelog | Prepend new `## 0.1.3` section before tagging | |
|
373
|
| Forgot to bump `sdk/react-native/CHANGELOG.md` | npm shows stale changelog | Prepend new `## 0.1.3` section before tagging | |
|
374
|
| Forgot to update the root `CHANGELOG.md` | Preflight fails: "CHANGELOG.md: no '## 0.1.3' section" | Prepend the new section before tagging | |
|
375
|
| 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 | |
|
376
|
| Forgot to bump `sdk/kotlin/gradle.properties` `VERSION_NAME` | Kotlin CI fails: tag vs gradle.properties mismatch | Bump `VERSION_NAME`, amend commit, re-tag | |
|
377
|
| Tag has `v` prefix (`v0.1.3`) | CI does not trigger — tag pattern requires bare semver | Delete the tag, re-tag without `v` | |
|
378
|
| `CARGO_REGISTRY_TOKEN` missing | `cargo publish` fails with auth error | Create a scoped token at crates.io/settings/tokens, add as repo secret | |
|
379
|
| `ONDE_SWIFT_PAT` expired | `onde-swift` push fails with 403 | Regenerate PAT at github.com/settings/tokens, update repo secret | |
|
380
|
| `PUB_CREDENTIALS` expired | `flutter pub publish` fails with 401 | Run `dart pub login` locally, copy new credentials JSON to repo secret | |
|
381
|
| 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 | |
|
382
|
| 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. | |
|
383
|
| npm version already published | npm publish step skips (idempotency guard) | Safe — the workflow exits 0. No action needed. | |
|
384
|
| pub.dev version already published | `flutter pub publish` fails | pub.dev is immutable — bump to next version | |
|
385
|
| Maven Central version already published | `publishAndReleaseToMavenCentral` fails | Maven Central is immutable — bump to next version | |
|
386
|
| 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) | |
|
387
|
| 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**. | |
|
388
|
| 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. | |
|
389
|
| `onde-swift` tag already exists | Tag push skipped (warning emitted) | Delete remote tag first: `git push origin :refs/tags/0.1.3` | |
|
390
|
| 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` | |
|
391
|
| 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) | |
|
392
|
| `Package.swift` regex didn't match | Python `RuntimeError` in CI | Check that `onde-swift/Package.swift` still has a `.binaryTarget(name: "OndeFramework", ...)` block | |
|
393
|
| Dart `flutter analyze` fails | `validate` job fails, `publish` is skipped | Fix analysis issues, re-tag | |
|
394
|
|
|
395
|
--- |
|
396
|
|
|
397
|
## Manual Publish Fallback |
|
398
|
|
|
399
|
When a tag was pushed before a workflow existed on `main`, or when you need to |
|
400
|
publish only a subset of SDKs without re-triggering all four workflows: |
|
401
|
|
|
402
|
### Rust (crates.io) |
|
403
|
|
|
404
|
```bash |
|
405
|
# Dry-run first |
|
406
|
cargo publish --dry-run |
|
407
|
|
|
408
|
# Publish |
|
409
|
cargo publish |
|
410
|
``` |
|
411
|
|
|
412
|
Requires `~/.cargo/credentials.toml` or `CARGO_REGISTRY_TOKEN` env var. |
|
413
|
|
|
414
|
### Dart (pub.dev) |
|
415
|
|
|
416
|
```bash |
|
417
|
cd sdk/dart |
|
418
|
|
|
419
|
# Dry-run |
|
420
|
flutter pub publish --dry-run |
|
421
|
|
|
422
|
# Publish |
|
423
|
flutter pub publish --force |
|
424
|
``` |
|
425
|
|
|
426
|
Requires `~/.config/dart/pub-credentials.json` from `dart pub login`. |
|
427
|
|
|
428
|
### Kotlin (Maven Central) |
|
429
|
|
|
430
|
```bash |
|
431
|
cd sdk/kotlin |
|
432
|
|
|
433
|
# Dry-run |
|
434
|
./gradlew :lib:publishToMavenCentral --dry-run --no-configuration-cache |
|
435
|
|
|
436
|
# Publish and release |
|
437
|
./gradlew :lib:publishAndReleaseToMavenCentral --no-configuration-cache |
|
438
|
``` |
|
439
|
|
|
440
|
Requires `~/.gradle/gradle.properties` with `mavenCentralUsername`, |
|
441
|
`mavenCentralPassword`, `signingKeyId`, `signingKey`, `signingPassword`. |
|
442
|
Also requires the `com.ondeinference` namespace to be verified on |
|
443
|
central.sonatype.com. |
|
444
|
|
|
445
|
### npm (React Native) |
|
446
|
|
|
447
|
```bash |
|
448
|
cd sdk/react-native |
|
449
|
npm run build |
|
450
|
npm publish --access public |
|
451
|
``` |
|
452
|
|
|
453
|
Requires `npm login`. Trusted publishing is CI-only, so a manual publish falls |
|
454
|
back to your own npm credentials. |
|
455
|
|
|
456
|
### When to use manual publish |
|
457
|
|
|
458
|
| Scenario | Approach | |
|
459
|
|----------|----------| |
|
460
|
| Tag exists but workflow wasn't on `main` yet | Merge workflow, then publish manually | |
|
461
|
| One SDK's CI failed, others succeeded | Fix the issue, publish that SDK manually | |
|
462
|
| Need to avoid re-triggering Swift XCFramework build | Publish Rust/Dart/npm/Kotlin manually | |
|
463
|
| Testing a new workflow before first real tag | Use `workflow_dispatch` for dry-run only | |
|
464
|
|
|
465
|
--- |
|
466
|
|
|
467
|
## Re-releasing a Version (Emergency Fix) |
|
468
|
|
|
469
|
If a release was botched and you need to re-publish the same version: |
|
470
|
|
|
471
|
### Swift (onde-swift) |
|
472
|
|
|
473
|
```bash |
|
474
|
# 1. Delete the remote tag on onde-swift |
|
475
|
git -C /path/to/onde-swift push origin :refs/tags/0.1.3 |
|
476
|
|
|
477
|
# 2. Delete the GitHub Release on onde-swift (via GitHub UI or gh CLI) |
|
478
|
gh release delete 0.1.3 --repo ondeinference/onde-swift --yes |
|
479
|
|
|
480
|
# 3. Delete the GitHub Release on onde |
|
481
|
gh release delete 0.1.3 --repo ondeinference/onde --yes |
|
482
|
|
|
483
|
# 4. Delete the local and remote tag on onde |
|
484
|
git tag -d 0.1.3 |
|
485
|
git push origin :refs/tags/0.1.3 |
|
486
|
|
|
487
|
# 5. Fix the issue, commit, re-tag, push |
|
488
|
git tag 0.1.3 |
|
489
|
git push origin main 0.1.3 |
|
490
|
``` |
|
491
|
|
|
492
|
### Dart (pub.dev) / Rust (crates.io) / Kotlin (Maven Central) |
|
493
|
|
|
494
|
**pub.dev, crates.io, and Maven Central are all immutable.** Once a version is |
|
495
|
published, it cannot be re-published. You must bump to `0.1.4` instead. |
|
496
|
|
|
497
|
- **crates.io:** `cargo yank --version 0.1.3` hides the version from dependency |
|
498
|
resolution but does not delete it. |
|
499
|
- **pub.dev:** No yank equivalent. The version remains visible and installable. |
|
500
|
|
|
501
|
If the `publish` job failed (i.e. the package was NOT published), you can |
|
502
|
safely re-tag after fixing the issue. |
|
503
|
|
|
504
|
### npm |
|
505
|
|
|
506
|
The npm workflow's idempotency guard means re-running it is safe — it skips |
|
507
|
if the version already exists. However, npm also does not allow re-publishing |
|
508
|
the same version with different content. Bump to the next version if the |
|
509
|
published content was wrong. |
|
510
|
|
|
511
|
--- |
|
512
|
|
|
513
|
## End-to-End Release Timeline |
|
514
|
|
|
515
|
``` |
|
516
|
Developer merges feature → development → main (--no-ff) then tags |
|
517
|
│ |
|
518
|
├─── release-sdk-rust.yml fires ──────────────────────────────────────┐ |
|
519
|
│ ~2 min (dry-run + publish to crates.io) │ |
|
520
|
│ │ |
|
521
|
├─── release-sdk-swift.yml fires ──────────────────────────────────────┤ |
|
522
|
│ ~15 min (XCFramework build) │ |
|
523
|
│ Creates GitHub Release on onde │ |
|
524
|
│ Pushes commit + tag to onde-swift │ |
|
525
|
│ │ │ |
|
526
|
│ ├─ onde-swift ci.yml fires (push to main) │ |
|
527
|
│ │ ~5 min (validate + build 5 platforms) │ |
|
528
|
│ │ │ |
|
529
|
│ └─ onde-swift release.yml fires (tag 1.0.0) │ |
|
530
|
│ ~5 min (validate + create GitHub Release) │ |
|
531
|
│ │ |
|
532
|
├─── release-sdk-dart.yml fires ───────────────────────────────────────┤ |
|
533
|
│ ~3 min (validate + publish to pub.dev) │ |
|
534
|
│ │ |
|
535
|
├─── release-sdk-npm.yml fires ───────────────────────────────────────┤ |
|
536
|
│ ~3 min (validate + publish to npm) │ |
|
537
|
│ │ |
|
538
|
├─── release-sdk-kotlin.yml fires ────────────────────────────────────┤ |
|
539
|
│ ~10 min (Android ABIs + JVM dylib + publish to Maven Central) │ |
|
540
|
│ │ |
|
541
|
└─── Swift Package Index picks up onde-swift tag ──────────────────────┘ |
|
542
|
~30 min (SPI polling interval) |
|
543
|
klibs.io picks up Maven Central artifact within ~30 days |
|
544
|
``` |
|
545
|
|
|
546
|
All five CI workflows fire **in parallel** from the single tag push. The only |
|
547
|
ordering dependency is that `release-sdk-swift` must create the `onde` GitHub |
|
548
|
Release (with XCFramework assets) **before** pushing the tag to `onde-swift` — |
|
549
|
this is guaranteed by the step order within the workflow. |
|
550
|
|
|
551
|
### Merge train |
|
552
|
|
|
553
|
Always merge through the branch train before tagging. Use `--no-ff` to |
|
554
|
create explicit merge commits — they serve as bookmarks in the history |
|
555
|
showing where each feature landed. |
|
556
|
|
|
557
|
```bash |
|
558
|
git checkout development && git merge feature/my-branch --no-ff --no-edit |
|
559
|
git checkout main && git merge development --no-ff --no-edit |
|
560
|
git push origin development main |
|
561
|
git tag X.Y.Z && git push origin X.Y.Z |
|
562
|
``` |
|
563
|
|
|
564
|
Never tag from a feature branch. The tag must point to a commit on `main` that |
|
565
|
contains all workflow files and fixes. If a workflow is added on a feature branch |
|
566
|
and the tag is pushed before merging, the workflow won't exist at the tagged |
|
567
|
commit and CI won't fire. |
|
568
|
|
|
569
|
**Why `--no-ff`:** Multi-commit feature branches (like the Kotlin SDK with 35 |
|
570
|
files across 6 commits) look like they were committed directly to `main` with |
|
571
|
fast-forward merges. `--no-ff` preserves the branch context. It also allows |
|
572
|
reverting an entire feature with a single `git revert -m 1 <merge-commit>`. |
|
573
|
|
|
574
|
--- |
|
575
|
|
|
576
|
## Distribution Registry Reference |
|
577
|
|
|
578
|
| Registry | Package Name | Import | Workflow | Immutable? | |
|
579
|
|----------|-------------|--------|----------|------------| |
|
580
|
| crates.io | `onde` | `onde = "1.0"` | `release-sdk-rust.yml` | Yes (yank hides, doesn't delete) | |
|
581
|
| Swift Package Index | `onde-swift` (org: `ondeinference`) | `import Onde` | `release-sdk-swift.yml` → `onde-swift/release.yml` | Tags are immutable by convention | |
|
582
|
| pub.dev | `onde_inference` | `import 'package:onde_inference/onde_inference.dart'` | `release-sdk-dart.yml` | Yes (no yank) | |
|
583
|
| npm | `@ondeinference/react-native` | `import { OndeChatEngine } from "@ondeinference/react-native"` | `release-sdk-npm.yml` | Yes (unpublish within 72h only) | |
|
584
|
| Maven Central | `com.ondeinference:onde-inference` | `implementation("com.ondeinference:onde-inference:1.0.0")` | `release-sdk-kotlin.yml` | Yes (cannot re-publish same version) | |
|
585
|
| klibs.io | `onde-inference` | Same Maven Central coordinate | Auto-indexed from Maven Central | N/A (discovery only) | |
|
586
|
|
|
587
|
--- |
|
588
|
|
|
589
|
--- |
|
590
|
|
|
591
|
## React Native npm SDK |
|
592
|
|
|
593
|
**Package:** `@ondeinference/react-native` (scoped under `@ondeinference` org on npm) |
|
594
|
**Location:** `sdk/react-native/` |
|
595
|
**Architecture:** Expo native module wrapping Rust via C FFI |
|
596
|
|
|
597
|
### How it works |
|
598
|
|
|
599
|
``` |
|
600
|
TypeScript (React Native) |
|
601
|
│ import { OndeChatEngine } from "@ondeinference/react-native" |
|
602
|
▼ |
|
603
|
Expo Module (Swift / Kotlin) |
|
604
|
│ OndeInferenceModule — calls C FFI functions via @_silgen_name (iOS) / JNI (Android) |
|
605
|
▼ |
|
606
|
Rust C FFI bridge (sdk/react-native/rust/) |
|
607
|
│ extern "C" functions with JSON serialization, global tokio::Runtime |
|
608
|
▼ |
|
609
|
onde crate (src/) |
|
610
|
│ ChatEngine — tokio::sync::Mutex, mistral.rs inference |
|
611
|
▼ |
|
612
|
mistral.rs → Metal (iOS) / CPU (Android) |
|
613
|
``` |
|
614
|
|
|
615
|
### Key files |
|
616
|
|
|
617
|
| File | Purpose | |
|
618
|
|------|---------| |
|
619
|
| `sdk/react-native/package.json` | npm package — `@ondeinference/react-native` | |
|
620
|
| `sdk/react-native/expo-module.config.json` | Expo autolinking config | |
|
621
|
| `sdk/react-native/src/index.ts` | Public TypeScript API — `OndeChatEngine`, free functions, JSON ↔ camelCase conversion | |
|
622
|
| `sdk/react-native/src/types.ts` | TypeScript type definitions mirroring Rust types | |
|
623
|
| `sdk/react-native/src/OndeInferenceModule.ts` | `requireNativeModule("OndeInference")` bridge | |
|
624
|
| `sdk/react-native/rust/src/lib.rs` | C FFI exports (`extern "C"`) + Android JNI wrappers | |
|
625
|
| `sdk/react-native/ios/OndeInferenceModule.swift` | Swift Expo module — calls Rust via `@_silgen_name` | |
|
626
|
| `sdk/react-native/android/.../OndeInferenceModule.kt` | Kotlin Expo module — calls Rust via JNI `external fun` | |
|
627
|
| `sdk/react-native/scripts/build-rust.sh` | Cross-compile Rust for iOS (staticlib) + Android (cdylib) | |
|
628
|
|
|
629
|
### Building native libraries |
|
630
|
|
|
631
|
```bash |
|
632
|
# iOS (requires rustup targets: aarch64-apple-ios, aarch64-apple-ios-sim) |
|
633
|
cd sdk/react-native && ./scripts/build-rust.sh ios |
|
634
|
|
|
635
|
# Android (requires ANDROID_NDK_HOME) |
|
636
|
cd sdk/react-native && ./scripts/build-rust.sh android |
|
637
|
|
|
638
|
# Both |
|
639
|
cd sdk/react-native && ./scripts/build-rust.sh all |
|
640
|
``` |
|
641
|
|
|
642
|
### Publishing to npm |
|
643
|
|
|
644
|
```bash |
|
645
|
cd sdk/react-native |
|
646
|
npm run build # TypeScript → build/ |
|
647
|
npm publish --access public |
|
648
|
``` |
|
649
|
|
|
650
|
The `--access public` flag is required for scoped packages on first publish. |
|
651
|
|
|
652
|
--- |
|
653
|
|
|
654
|
## Registry Immutability Summary |
|
655
|
|
|
656
|
All four registries are effectively immutable once a version is published: |
|
657
|
|
|
658
|
- **crates.io** — `cargo yank` removes from resolver but the tarball stays forever |
|
659
|
- **pub.dev** — No retraction mechanism at all |
|
660
|
- **npm** — `npm unpublish` works within 72 hours, but only if no dependents |
|
661
|
- **Maven Central** — No retraction mechanism. Once published, it's permanent. |
|
662
|
- **Swift (onde-swift tags)** — Convention: never force-push tags. Delete + re-create if needed. |
|
663
|
|
|
664
|
**Bottom line:** get the release right before publishing. Use `--dry-run` locally |
|
665
|
and via `workflow_dispatch` before pushing a real tag. |
|
666
|
|
|
667
|
--- |
|
668
|
|
|
669
|
*Update this skill when adding new SDK targets (Python, Ruby, Kotlin), changing |
|
670
|
CI runners, or modifying the tag/version validation logic.* |