main
md 262 lines 8.48 KB
Rendered Raw
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 native binary.
86
87 Important details:
88
89 - executable command should stay `onde`
90 - keep the runtime-id mapping explicit
91 - download the correct binary from GitHub Releases on first run
92 - cache downloads under `~/.onde/cli`
93 - keep the wrapper thin, the Rust binary is still the product
94 - publish the pub.dev package only after the matching GitHub Release exists
95
96 Good fit for Dart and Flutter developers who want the same CLI through familiar tooling.
97
98 ## Runtime identifier mapping
99
100 Keep these names aligned anywhere wrappers or workflows refer to native assets:
101
102 - `darwin-x64`
103 - `darwin-arm64`
104 - `linux-x64`
105 - `linux-arm64`
106 - `windows-x64`
107 - `windows-arm64`
108
109 If a wrapper uses one naming scheme and a workflow uploads another, the installed package will pass packaging but fail at runtime.
110
111 ## Release workflow principles
112
113 ### One tag trigger, then fan out
114
115 `release-crates.yml` is the only workflow that reacts to a tag push. It verifies the
116 tag against `Cargo.toml` and `pubspec.yaml`, publishes the crate, then dispatches
117 `release-github`, `release-npm`, `release-nuget`, and `release-pypi` with the same tag.
118 `release-github` in turn dispatches `release-homebrew` and `release-pub` after the
119 release assets exist.
120
121 ```
122 tag push
123 └─ release-crates (verify -> publish -> dispatch)
124 ├─ release-github ─┬─ release-homebrew
125 │ └─ release-pub
126 ├─ release-npm
127 ├─ release-nuget
128 └─ release-pypi
129 ```
130
131 This mirrors `smbcloud-cli`. Two properties matter:
132
133 - crates.io is the gate. If verify or publish fails, no wrapper publishes a version
134 that has no crate behind it.
135 - every dispatch passes `ref: <tag>`, not `ref: main`, so downstream workflows run the
136 workflow definition from the release state instead of whatever `main` happens to be.
137
138 Do not add `push: tags:` to a wrapper workflow. Several workflows racing on the same tag
139 is what lets the pub.dev launcher publish before its GitHub Release assets exist.
140
141 ### Build first, package second
142
143 Always build native binaries in a target matrix first.
144
145 Then let the wrapper channel jobs package those artifacts.
146
147 This avoids rebuilding the product multiple times and makes failures easier to reason about.
148
149 ### Idempotency
150
151 Every publish workflow should check whether the target version already exists before publishing.
152
153 Examples:
154
155 - npm: `npm view`
156 - PyPI: package JSON endpoint
157 - NuGet: flat container index
158 - pub.dev: package API endpoint
159
160 ### Smoke tests should match the product shape
161
162 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.
163
164 Safer checks:
165
166 - install succeeds
167 - bundled native binary exists where expected
168 - package manager validation passes
169
170 ## pub.dev-specific notes
171
172 pub.dev behaves differently from npm and NuGet in one important way:
173
174 - large bundled binaries quickly hit package size limits
175
176 That makes the safest design a thin Dart launcher that downloads the right native binary from GitHub Releases at runtime instead of shipping every platform binary inside the package.
177
178 Practical consequences:
179
180 - `pub/onde_cli/native/` should stay out of the published package
181 - do not set `publish_to: https://pub.dev` in `pubspec.yaml`; omit `publish_to` for normal pub.dev publishing
182 - use package-level ignore rules so local native staging does not leak into publish input
183 - keep release asset names aligned with the GitHub Release workflow, because the launcher depends on them
184 - treat `~/.onde/cli` as the local cache root for downloaded binaries
185 - do not let the pub.dev workflow race ahead of GitHub Releases; dispatch it after `release-github.yml` has created the assets
186
187 Run these during verification:
188
189 - `dart pub get --directory pub/onde_cli`
190 - `dart analyze pub/onde_cli`
191 - `dart pub publish --dry-run --directory pub/onde_cli`
192
193 ## NuGet-specific notes
194
195 NuGet packaging is sensitive to path layout.
196
197 The safest pattern is:
198
199 - copy native files into the publish output
200 - let the .NET tool pack process carry them into the final package
201
202 Avoid custom `PackagePath` patterns that can duplicate path segments or create nested native folders by accident.
203
204 If the tool installs but cannot find the native binary, inspect the `.nupkg` contents before debugging the launcher.
205
206 ## README surfaces
207
208 Every user-facing channel should document the same product with channel-specific installation instructions.
209
210 Main files:
211
212 - `README.md`
213 - `npm/onde-cli/README.md`
214 - `pypi/README.md`
215 - `nuget/onde-cli/README.md`
216 - `pub/onde_cli/README.md`
217
218 Keep these aligned on:
219
220 - product description
221 - supported platforms
222 - install method
223 - license and copyright
224 - relevant supporting links like the forward-pass explainer
225
226 Do not let one channel sound like a different product.
227
228 ## Version alignment
229
230 When cutting a release, update every user-facing distribution version that should move with the CLI.
231
232 At minimum, check:
233
234 - Rust crate version
235 - npm release templates and workflow assumptions
236 - npm lockfile version if committed
237 - PyPI release version source
238 - NuGet package version at pack time
239 - pub.dev package version in `pubspec.yaml`
240 - workflow example tags and docs that mention concrete versions
241
242 ## Common failure modes
243
244 - wrapper version does not match the Rust release version
245 - manually bumping the committed npm manifest and assuming that is what npm publishes
246 - staged native binaries land in the wrong directory before packaging
247 - package builds successfully but installs without the native binary
248 - pub.dev launcher points at the wrong GitHub Release asset name or runtime id
249 - pub.dev workflow publishes before the matching GitHub Release assets exist
250 - pub.dev package still includes bundled binaries and blows the upload size limit
251 - smoke test invokes a TUI-first binary in a way that does not make sense for CI
252 - README badges and install commands drift out of sync with real package names
253
254 ## Rule of thumb
255
256 If the native binary works and the wrappers are boring, distribution stays manageable.
257
258 If a wrapper starts acting like its own product, complexity grows fast.
259
260 Keep it thin.
261 Keep it aligned.
262 Keep the Rust binary as the source of truth.