1 ---
2 name: smbcloud-cli-release
3 description: Use when building, packaging, or releasing smbCloud CLI binaries through npm or GitHub Actions CI/CD. Covers the Rust binary packages under `npm/cli-*`, the `@smbcloud/cli` wrapper package, macOS arm64 vs x64 constraints, npm 2FA publishing, and fixes to `.github/workflows/release-npm.yml`.
4 ---
5
6 # smbcloud CLI Release
7
8 Use this skill when work touches any part of the smbCloud CLI distribution flow:
9
10 - local npm release for `smbcloud-cli`
11 - GitHub Actions npm release automation
12 - Rust binary packaging into scoped npm platform packages
13 - release failures caused by macOS cross-compilation, missing package metadata, or npm 2FA
14
15 ## Release model
16
17 There are two npm package layers. Publish them in order.
18
19 ### 1. Platform binary packages
20
21 These packages contain the compiled Rust binary only:
22
23 - `@smbcloud/cli-darwin-arm64`
24 - `@smbcloud/cli-darwin-x64`
25 - `@smbcloud/cli-linux-x64`
26 - `@smbcloud/cli-windows-arm64`
27 - `@smbcloud/cli-windows-x64`
28
29 Their package directories are generated under `npm/cli-*/` and should be treated as release artifacts, not source.
30
31 ### 2. Wrapper package
32
33 The public package is:
34
35 - `@smbcloud/cli`
36
37 It contains the Node launcher in `npm/smbcloud-cli/src/index.ts` and resolves the correct platform package with `require.resolve(...)`.
38
39 Do not publish `@smbcloud/cli` before the required platform packages for that version exist.
40
41 ## Source of truth
42
43 Use these files as the release source of truth:
44
45 - Rust crate version: `crates/cli/Cargo.toml`
46 - npm release workflow: `.github/workflows/release-npm.yml`
47 - platform package generator: `npm/scripts/render-platform-package.cjs`
48 - wrapper package generator: `npm/scripts/render-main-package.cjs`
49 - wrapper launcher: `npm/smbcloud-cli/src/index.ts`
50
51 Do not hand-edit generated `npm/cli-*/package.json` files unless debugging a broken release artifact.
52
53 ## Local release workflow
54
55 ### Preflight
56
57 1. Confirm npm auth with `npm whoami`.
58 2. Confirm the crate version in `crates/cli/Cargo.toml`.
59 3. Use the same version string for every npm package in that release.
60
61 ### Build platform binaries
62
63 Build each target first:
64
65 - `cargo build --release --locked --target aarch64-apple-darwin`
66 - `cargo build --release --locked --target x86_64-apple-darwin`
67 - or the matching Linux/Windows target in CI
68
69 ### Generate platform package
70
71 From `npm/`:
72
73 - run `node ./scripts/render-platform-package.cjs <pkg> <version> <os> <arch>`
74 - create `<pkg>/bin`
75 - copy the built `smb` binary into `bin/`
76 - on Windows, use `smb.exe`
77
78 Example:
79
80 - `node ./scripts/render-platform-package.cjs cli-darwin-arm64 0.3.33 darwin arm64`
81
82 ### Publish order
83
84 1. Publish each platform package with `npm publish --access public`
85 2. Generate `npm/smbcloud-cli/package.json` with `render-main-package.cjs`
86 3. Run `npm install`
87 4. Run `npm run build`
88 5. Publish `@smbcloud/cli`
89
90 ## npm 2FA
91
92 Publishing may fail with `EOTP`.
93
94 When that happens, retry the exact `npm publish` command with:
95
96 - `--otp=<code>`
97
98 Do not assume the first publish failed before the package upload step. Check the exit result or npm registry state before retrying repeatedly.
99
100 ## Trusted publishing
101
102 Prefer npm trusted publishers for CI over `NPM_TOKEN`.
103
104 Official references:
105
106 - https://docs.npmjs.com/trusted-publishers/
107 - https://docs.npmjs.com/cli/v11/commands/npm-trust/
108
109 ### npm-side constraints
110
111 Trusted publishing can only be configured for packages that already exist on npm.
112
113 That means:
114
115 - publish a package once manually if it does not exist yet
116 - then configure trust for that package
117 - each package can have only one trusted publisher configuration at a time
118
119 For `smbcloud-cli`, configure trust per package, not only for `@smbcloud/cli`.
120
121 Example packages:
122
123 - `@smbcloud/cli`
124 - `@smbcloud/cli-darwin-arm64`
125 - later, each additional published `@smbcloud/cli-*` package
126
127 ### GitHub Actions setup
128
129 The workflow must include:
130
131 - `permissions:`
132 - `id-token: write`
133 - `contents: read`
134
135 Do not pass `NODE_AUTH_TOKEN` to `npm publish` when using trusted publishing.
136
137 The npm CLI detects the GitHub OIDC environment automatically and exchanges it for a short-lived publish credential.
138
139 ### Rust toolchain consistency
140
141 When cross-compiling in CI, the Rust toolchain used for `rustup target add` must match the toolchain used by `cargo build`.
142
143 For this repo, `rust-toolchain.toml` is the source of truth.
144
145 Do not duplicate the Rust version in the matrix unless the workflow intentionally tests multiple toolchains.
146
147 If the workflow installs a target for one toolchain but Cargo builds with another, CI can fail with:
148
149 - `error[E0463]: can't find crate for core`
150 - note that the target may not be installed
151
152 This can happen even when `rustup target add <target>` already ran successfully.
153
154 ### dtolnay action behavior
155
156 For the pinned `dtolnay/rust-toolchain` revision used in this repo, do not assume the action will infer the toolchain from `rust-toolchain.toml` without input.
157
158 A workflow can fail with:
159
160 - `'toolchain' is a required input`
161
162 Preferred pattern in this repo:
163
164 - read `channel = "..."` from `rust-toolchain.toml`
165 - write it to `RUST_TOOLCHAIN` in `GITHUB_ENV`
166 - pass `toolchain: ${{ env.RUST_TOOLCHAIN }}` to `dtolnay/rust-toolchain`
167 - run `rustup target add <target> --toolchain ${{ env.RUST_TOOLCHAIN }}`
168 - run `cargo build --target <target>` after the action sets that toolchain active
169
170 This keeps `rust-toolchain.toml` as the only Rust version source while avoiding implicit action behavior.
171
172 ### Trusted publisher command
173
174 For this repo, the trust relationship should point at:
175
176 - repository: `smbcloudXYZ/smbcloud-cli`
177 - workflow file: `release-npm.yml`
178
179 Example:
180
181 - `npm trust github @smbcloud/cli --repo smbcloudXYZ/smbcloud-cli --file release-npm.yml --yes`
182 - `npm trust github @smbcloud/cli-darwin-arm64 --repo smbcloudXYZ/smbcloud-cli --file release-npm.yml --yes`
183
184 These commands may require npm 2FA and support `--otp=<code>`.
185
186 ### Migration guidance
187
188 Recommended order:
189
190 1. enable trusted publishing for existing packages
191 2. verify a CI publish works through OIDC
192 3. then remove or stop using `NPM_TOKEN`
193 4. after the migration is proven, tighten npm package publishing access if desired
194
195 Do not remove the manual publish path for brand-new package names, because npm trust cannot be configured before the first publish.
196
197 ## CI/CD workflow rules
198
199 The release workflow lives in `.github/workflows/release-npm.yml`.
200
201 Important rules:
202
203 - `runs-on` must use `matrix.build.OS`, not `matrix.build.os`
204 - derive `RELEASE_VERSION` from the git tag when `GITHUB_REF_TYPE=tag`
205 - for `workflow_dispatch`, fall back to the version in `crates/cli/Cargo.toml`
206 - avoid `envsubst` for package generation across runners
207 - use the Node generator scripts instead
208 - use `npm install` and `npm run build` for the wrapper package job
209
210 ### Package generation
211
212 Prefer deterministic Node scripts over shell templating.
213
214 Reasons:
215
216 - `envsubst` is not guaranteed across macOS and Windows runners
217 - package metadata must stay identical between local release and CI
218 - the generated wrapper package must point optional dependencies at the exact release version
219
220 ## macOS cross-build constraint
221
222 On an Apple Silicon host, `x86_64-apple-darwin` may fail due to `openssl-sys` if only arm64 Homebrew OpenSSL exists under `/opt/homebrew`.
223
224 Typical symptom:
225
226 - `openssl-sys` cannot find an `x86_64-apple-darwin` OpenSSL
227
228 If that happens:
229
230 - do not claim the x64 package is releasable from that machine
231 - either build on an Intel Mac
232 - or use a Rosetta/x86 Homebrew toolchain with Intel OpenSSL under `/usr/local/opt/openssl@3`
233 - or publish only the arm64 platform package and explicitly avoid calling the release complete
234
235 Do not publish `@smbcloud/cli` for a version unless you accept that missing platform packages for that version can break installs on those platforms.
236
237 ## Generated artifacts
238
239 Generated platform package directories should be git-ignored:
240
241 - `/npm/cli-*/`
242
243 These are build artifacts and should not be committed.
244
245 ## Validation
246
247 Use the smallest checks that match the change.
248
249 ### Local
250
251 - `npm whoami`
252 - `cargo build --release --locked --target <target>`
253 - `node npm/scripts/render-platform-package.cjs ...`
254 - `node npm/scripts/render-main-package.cjs ...`
255 - `npm install`
256 - `npm run build`
257
258 ### CI/CD edits
259
260 After changing the workflow:
261
262 - inspect `.github/workflows/release-npm.yml`
263 - verify the package generators still output the expected names and versions
264 - confirm `@smbcloud/cli` optional dependencies match the platform package names exactly
265
266 ## Common mistakes
267
268 - publishing `@smbcloud/cli` before the matching platform packages exist
269 - assuming `workflow_dispatch` has a tag-derived version
270 - relying on `envsubst` in a cross-platform GitHub Actions workflow
271 - forgetting that Windows binaries need `.exe`
272 - treating generated `npm/cli-*` directories as source files
273 - trying to cross-build macOS x64 on Apple Silicon without an Intel OpenSSL toolchain