|
| 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. |
0 commit comments