Skip to content

Commit 7fd9672

Browse files
committed
Merge branch 'development'
2 parents fcd9877 + 35d3c4e commit 7fd9672

33 files changed

Lines changed: 1505 additions & 65 deletions
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
---
2+
name: distribution
3+
description: Use when adding, fixing, or documenting package distribution for Onde CLI. Covers wrapper-package design across npm, PyPI, NuGet, pub.dev, GitHub Releases, and Homebrew, plus version alignment, artifact staging, README surfaces, and smoke-test strategy.
4+
---
5+
6+
# Onde CLI distribution
7+
8+
This skill captures how `onde-cli` is distributed today and what to watch when adding new channels.
9+
10+
## Distribution model
11+
12+
`onde-cli` is a native Rust CLI first.
13+
14+
Everything else is packaging.
15+
16+
That means:
17+
18+
- build the Rust binary once per target
19+
- publish thin wrappers that install or launch that binary
20+
- keep channel-specific logic small and predictable
21+
22+
This design keeps behavior consistent across ecosystems and avoids re-implementing CLI logic in each language.
23+
24+
## Channel map
25+
26+
### Native source of truth
27+
28+
- Rust crate: `onde-cli`
29+
- binary name: `onde`
30+
- root manifest: `Cargo.toml`
31+
32+
### Wrapper channels
33+
34+
- npm: JavaScript launcher + optional platform packages
35+
- PyPI: `maturin` binary wheels
36+
- NuGet: .NET global tool launcher
37+
- pub.dev: Dart launcher package
38+
39+
### Native artifact channels
40+
41+
- crates.io
42+
- GitHub Releases
43+
- Homebrew
44+
45+
## Wrapper design patterns
46+
47+
### npm
48+
49+
Use a very small Node launcher that:
50+
51+
- detects OS and architecture
52+
- resolves the installed platform package
53+
- spawns the binary with inherited stdio
54+
55+
Good fit for users who already live in npm tooling.
56+
57+
Important release detail:
58+
59+
- the committed `npm/onde-cli/package.json` is a working file, not the publish-time source of truth
60+
- the release workflow rewrites the base package manifest with `npm/scripts/render-main-package.cjs`
61+
- platform package manifests are generated on the fly with `npm/scripts/render-platform-package.cjs`
62+
- the actual npm version comes from the release tag via `RELEASE_VERSION`, not from manually editing the committed npm manifest before publish
63+
64+
### PyPI
65+
66+
Use `maturin` with `bindings = "bin"` so the native executable is what gets installed.
67+
68+
Good fit for Python users who want the CLI but not a Python reimplementation.
69+
70+
### NuGet
71+
72+
Use a `.NET global tool` wrapper.
73+
74+
Important details:
75+
76+
- keep the package as a launcher, not a managed rewrite
77+
- place native binaries under the tool payload
78+
- resolve the runtime identifier at startup
79+
- set Unix execute bits before launch
80+
81+
Good fit for .NET developers and CI environments that already use `dotnet tool install`.
82+
83+
### pub.dev
84+
85+
Use a Dart package with a command entrypoint and a library that launches the bundled native binary.
86+
87+
Important details:
88+
89+
- executable command should stay `onde`
90+
- use `Isolate.resolvePackageUri` to find the installed package path
91+
- keep the runtime-id mapping explicit
92+
- do not overcomplicate the wrapper with platform-specific Dart APIs unless necessary
93+
94+
Good fit for Dart and Flutter developers who want the same CLI through familiar tooling.
95+
96+
## Runtime identifier mapping
97+
98+
Keep these names aligned anywhere wrappers or workflows refer to native assets:
99+
100+
- `darwin-x64`
101+
- `darwin-arm64`
102+
- `linux-x64`
103+
- `linux-arm64`
104+
- `windows-x64`
105+
- `windows-arm64`
106+
107+
If a wrapper uses one naming scheme and a workflow uploads another, the installed package will pass packaging but fail at runtime.
108+
109+
## Release workflow principles
110+
111+
### Build first, package second
112+
113+
Always build native binaries in a target matrix first.
114+
115+
Then let the wrapper channel jobs package those artifacts.
116+
117+
This avoids rebuilding the product multiple times and makes failures easier to reason about.
118+
119+
### Idempotency
120+
121+
Every publish workflow should check whether the target version already exists before publishing.
122+
123+
Examples:
124+
125+
- npm: `npm view`
126+
- PyPI: package JSON endpoint
127+
- NuGet: flat container index
128+
- pub.dev: package API endpoint
129+
130+
### Smoke tests should match the product shape
131+
132+
Because `onde` is TUI-first, avoid smoke tests that assume a traditional CLI help flow unless you know that code path is stable and non-interactive.
133+
134+
Safer checks:
135+
136+
- install succeeds
137+
- bundled native binary exists where expected
138+
- package manager validation passes
139+
140+
## pub.dev-specific notes
141+
142+
pub.dev behaves differently from npm and NuGet in one important way:
143+
144+
- files ignored by git are generally excluded from publish input
145+
146+
That means staged native binaries for `pub/onde_cli/native/` must be available to the package at publish time.
147+
148+
Two practical consequences:
149+
150+
- do not ignore `pub/onde_cli/native/` in the root `.gitignore` if CI stages binaries there
151+
- a `.pubignore` can hide local tool noise, but do not hide the native payload you intend to publish
152+
153+
Run these during verification:
154+
155+
- `dart pub get --directory pub/onde_cli`
156+
- `dart analyze pub/onde_cli`
157+
- `dart pub publish --dry-run --directory pub/onde_cli`
158+
159+
## NuGet-specific notes
160+
161+
NuGet packaging is sensitive to path layout.
162+
163+
The safest pattern is:
164+
165+
- copy native files into the publish output
166+
- let the .NET tool pack process carry them into the final package
167+
168+
Avoid custom `PackagePath` patterns that can duplicate path segments or create nested native folders by accident.
169+
170+
If the tool installs but cannot find the native binary, inspect the `.nupkg` contents before debugging the launcher.
171+
172+
## README surfaces
173+
174+
Every user-facing channel should document the same product with channel-specific installation instructions.
175+
176+
Main files:
177+
178+
- `README.md`
179+
- `npm/onde-cli/README.md`
180+
- `pypi/README.md`
181+
- `nuget/onde-cli/README.md`
182+
- `pub/onde_cli/README.md`
183+
184+
Keep these aligned on:
185+
186+
- product description
187+
- supported platforms
188+
- install method
189+
- license and copyright
190+
- relevant supporting links like the forward-pass explainer
191+
192+
Do not let one channel sound like a different product.
193+
194+
## Version alignment
195+
196+
When cutting a release, update every user-facing distribution version that should move with the CLI.
197+
198+
At minimum, check:
199+
200+
- Rust crate version
201+
- npm release templates and workflow assumptions
202+
- npm lockfile version if committed
203+
- PyPI release version source
204+
- NuGet package version at pack time
205+
- pub.dev package version in `pubspec.yaml`
206+
- workflow example tags and docs that mention concrete versions
207+
208+
## Common failure modes
209+
210+
- wrapper version does not match the Rust release version
211+
- manually bumping the committed npm manifest and assuming that is what npm publishes
212+
- staged native binaries land in the wrong directory before packaging
213+
- package builds successfully but installs without the native binary
214+
- pub.dev package omits staged assets because gitignore excludes them
215+
- smoke test invokes a TUI-first binary in a way that does not make sense for CI
216+
- README badges and install commands drift out of sync with real package names
217+
218+
## Rule of thumb
219+
220+
If the native binary works and the wrappers are boring, distribution stays manageable.
221+
222+
If a wrapper starts acting like its own product, complexity grows fast.
223+
224+
Keep it thin.
225+
Keep it aligned.
226+
Keep the Rust binary as the source of truth.

.github/workflows/ci.yml

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,39 @@ jobs:
108108
GRESIQ_API_SECRET: ${{ secrets.GRESIQ_API_SECRET }}
109109
HF_TOKEN: ${{ secrets.HF_TOKEN }}
110110

111+
dotnet-tool:
112+
name: .NET tool launcher build
113+
runs-on: ubuntu-latest
114+
steps:
115+
- name: Checkout
116+
uses: actions/checkout@v6
117+
118+
- name: Setup .NET SDK
119+
uses: actions/setup-dotnet@v4
120+
with:
121+
dotnet-version: 8.0.x
122+
123+
- name: Build .NET tool launcher
124+
run: dotnet build nuget/onde-cli/Onde.Cli.csproj --configuration Release
125+
126+
dart-package:
127+
name: Dart pub package check
128+
runs-on: ubuntu-latest
129+
steps:
130+
- name: Checkout
131+
uses: actions/checkout@v6
132+
133+
- name: Setup Dart SDK
134+
uses: dart-lang/setup-dart@v1
135+
136+
- name: Get package dependencies
137+
working-directory: pub/onde_cli
138+
run: dart pub get
139+
140+
- name: Analyze Dart package
141+
working-directory: pub/onde_cli
142+
run: dart analyze
143+
111144
build:
112145
name: Build (aarch64-apple-darwin)
113146
runs-on: macos-26
@@ -140,13 +173,17 @@ jobs:
140173
- clippy
141174
- test
142175
- check
176+
- dotnet-tool
177+
- dart-package
143178
- build
144179
if: always()
145180
steps:
146181
- name: Verify all required jobs succeeded
147182
run: |
148-
test "${{ needs.fmt.result }}" = "success"
149-
test "${{ needs.clippy.result }}" = "success"
150-
test "${{ needs.test.result }}" = "success"
151-
test "${{ needs.check.result }}" = "success"
152-
test "${{ needs.build.result }}" = "success"
183+
test "${{ needs.fmt.result }}" = "success"
184+
test "${{ needs.clippy.result }}" = "success"
185+
test "${{ needs.test.result }}" = "success"
186+
test "${{ needs.check.result }}" = "success"
187+
test "${{ needs.dotnet-tool.result }}" = "success"
188+
test "${{ needs.dart-package.result }}" = "success"
189+
test "${{ needs.build.result }}" = "success"

.github/workflows/release-github.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88
inputs:
99
tag:
10-
description: "Release tag (e.g. v0.1.1)"
10+
description: "Release tag (e.g. v0.3.1)"
1111
required: true
1212

1313
permissions:

.github/workflows/release-homebrew.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch:
55
inputs:
66
tag:
7-
description: "Release tag (e.g. v0.1.1)"
7+
description: "Release tag (e.g. v0.3.1)"
88
required: true
99

1010
permissions:

.github/workflows/release-npm.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88
inputs:
99
tag:
10-
description: "Release tag (e.g. v0.2.0)"
10+
description: "Release tag (e.g. v0.3.1)"
1111
required: true
1212

1313
permissions:
@@ -55,11 +55,7 @@ jobs:
5555
OS: macos-26-intel,
5656
TARGET: x86_64-apple-darwin,
5757
}
58-
- {
59-
NAME: darwin-arm64,
60-
OS: macos-26,
61-
TARGET: aarch64-apple-darwin,
62-
}
58+
- { NAME: darwin-arm64, OS: macos-26, TARGET: aarch64-apple-darwin }
6359

6460
steps:
6561
- name: Checkout

0 commit comments

Comments
 (0)