@smbcloud / cli / commits / ce32bb4

Document trusted publishing and align CI for OIDC npm publish

- Add trusted publishing guidance to SKILL.md, including npm and GitHub Actions requirements, migration steps, and Rust toolchain handling - Update release-npm.yml: - Set permissions for OIDC (id-token: write, contents: read) - Remove NPM_TOKEN usage in publish steps - Read Rust toolchain from rust-toolchain.toml and use consistently - Pin dtolnay/rust-toolchain action and pass explicit toolchain input - Use latest OS images for build matrix - Skip npm publish if version already exists - Use actions/checkout@v6

Seto Elkahfi committed Mar 28, 2026 at 22:37 UTC ce32bb4fa6acc7976b6a5de275d324d7b18d305e
2 files changed +139 -19
.agents/skills/smbcloud-cli-release/SKILL.md
+97
@@ -97,6 +97,103 @@ When that happens, retry the exact `npm publish` command with:
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`.
.github/workflows/release-npm.yml
+42 -19
@@ -6,6 +6,10 @@ on:
6 - "v*.*.*"
7 workflow_dispatch:
8
9 +permissions:
10 + id-token: write
11 + contents: read
12 +
13 env:
14 SMB_CLIENT_ID: ${{ secrets.SMB_CLIENT_ID }}
15 SMB_CLIENT_SECRET: ${{ secrets.SMB_CLIENT_SECRET }}
@@ -26,44 +30,39 @@ jobs:
30 build:
31 - {
32 NAME: linux-x64-glibc,
29 - OS: ubuntu-20.04,
30 - TOOLCHAIN: 1.92,
33 + OS: ubuntu-latest,
34 TARGET: x86_64-unknown-linux-gnu,
35 }
36 # Cannot cross-compile for now
37 #- {
38 # NAME: linux-arm64-glibc,
36 - # OS: ubuntu-20.04,
39 + # OS: ubuntu-latest,
40 # TOOLCHAIN: stable,
41 # TARGET: aarch64-unknown-linux-gnu,
42 # }
43 - {
44 NAME: win32-x64-msvc,
45 OS: windows-2022,
43 - TOOLCHAIN: 1.92,
46 TARGET: x86_64-pc-windows-msvc,
47 }
48 - {
49 NAME: win32-arm64-msvc,
50 OS: windows-2022,
49 - TOOLCHAIN: 1.92,
51 TARGET: aarch64-pc-windows-msvc,
52 }
53 - {
54 NAME: darwin-x64,
54 - OS: macos-11,
55 - TOOLCHAIN: 1.92,
55 + OS: macos-15-intel,
56 TARGET: x86_64-apple-darwin,
57 }
58 - {
59 NAME: darwin-arm64,
60 - OS: macos-11,
61 - TOOLCHAIN: 1.92,
60 + OS: macos-latest,
61 TARGET: aarch64-apple-darwin,
62 }
63 steps:
64 - name: Checkout
66 - uses: actions/checkout@v3
65 + uses: actions/checkout@v6
66
67 - name: Set the release version
68 shell: bash
@@ -76,11 +75,27 @@ jobs:
75
76 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
77
78 + - name: Read Rust toolchain
79 + shell: bash
80 + run: |
81 + rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
82 + if [ -z "$rust_toolchain" ]; then
83 + echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
84 + exit 1
85 + fi
86 +
87 + echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
88 +
89 - name: Install toolkit
80 - uses: dtolnay/rust-toolchain@master
90 + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
91 with:
82 - targets: ${{ matrix.build.TARGET }}
83 - toolchain: ${{ matrix.build.TOOLCHAIN}}
92 + toolchain: ${{ env.RUST_TOOLCHAIN }}
93 +
94 + - name: Install Rust target
95 + shell: bash
96 + run: |
97 + rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }}
98 + rustup target list --installed
99
100 - name: Create .env file
101 run: |
@@ -142,17 +157,20 @@ jobs:
157 cp "../target/${{ matrix.build.TARGET }}/release/${cargo_bin}" "${node_pkg}/bin"
158 # publish the package
159 cd "${node_pkg}"
160 + npm_package_name="@smbcloud/${node_pkg}"
161 + if npm view "${npm_package_name}@${node_version}" version >/dev/null 2>&1; then
162 + echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
163 + exit 0
164 + fi
165 npm publish --access public
146 - env:
147 - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
166
167 publish-npm-base:
168 name: Publish the base NPM package
169 needs: publish-npm-binaries
152 - runs-on: ubuntu-20.04
170 + runs-on: ubuntu-latest
171 steps:
172 - name: Checkout
155 - uses: actions/checkout@v3
173 + uses: actions/checkout@v6
174
175 - name: Set the release version
176 shell: bash
@@ -178,8 +196,13 @@ jobs:
196
197 node ../scripts/render-main-package.cjs "./package.json" "${{ env.RELEASE_VERSION }}"
198
199 + npm_package_name="@smbcloud/cli"
200 + npm_package_version="${{ env.RELEASE_VERSION }}"
201 + if npm view "${npm_package_name}@${npm_package_version}" version >/dev/null 2>&1; then
202 + echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
203 + exit 0
204 + fi
205 +
206 npm install
207 npm run build
208 npm publish --access public
184 - env:
185 - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}