1 name: NPM Release
2
3 on:
4 push:
5 tags:
6 - "v*.*.*"
7 workflow_dispatch:
8 inputs:
9 tag:
10 description: "Release tag (e.g. v0.1.1)"
11 required: true
12
13 concurrency:
14 # A re-pushed or re-dispatched tag fires this a second time. Queue the
15 # duplicate behind the original instead of cancelling it: cancelling a run
16 # mid-publish can leave a channel half-uploaded, whereas a queued duplicate
17 # just hits the "already published" check and exits clean.
18 group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
19 cancel-in-progress: false
20
21 permissions:
22 id-token: write
23 contents: read
24
25 env:
26 CARGO_TERM_COLOR: always
27
28 jobs:
29 publish-npm-binaries:
30 name: Publish NPM platform packages
31 runs-on: ${{ matrix.build.OS }}
32 strategy:
33 fail-fast: false
34 matrix:
35 build:
36 - {
37 NAME: linux-x64-glibc,
38 OS: ubuntu-latest,
39 TARGET: x86_64-unknown-linux-gnu,
40 }
41 - {
42 NAME: linux-arm64-glibc,
43 OS: ubuntu-24.04-arm,
44 TARGET: aarch64-unknown-linux-gnu,
45 }
46 - {
47 NAME: win32-x64-msvc,
48 OS: windows-2022,
49 TARGET: x86_64-pc-windows-msvc,
50 }
51 - {
52 NAME: win32-arm64-msvc,
53 OS: windows-2022,
54 TARGET: aarch64-pc-windows-msvc,
55 }
56 - { NAME: darwin-x64, OS: macos-26, TARGET: x86_64-apple-darwin }
57 - { NAME: darwin-arm64, OS: macos-26, TARGET: aarch64-apple-darwin }
58
59 steps:
60 - name: Checkout
61 uses: actions/checkout@v6
62 with:
63 ref: ${{ github.event.inputs.tag || github.ref }}
64
65 - name: Set the release version
66 shell: bash
67 run: |
68 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
69 release_version="${GITHUB_REF_NAME#v}"
70 else
71 release_version="${{ github.event.inputs.tag }}"
72 release_version="${release_version#v}"
73 fi
74 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
75
76 - name: Read Rust toolchain
77 shell: bash
78 run: |
79 rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
80 if [ -z "$rust_toolchain" ]; then
81 echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
82 exit 1
83 fi
84 echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
85
86 - name: Install Rust toolchain
87 uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
88 with:
89 toolchain: ${{ env.RUST_TOOLCHAIN }}
90
91 - name: Install Rust target
92 shell: bash
93 run: |
94 rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }}
95 rustup target list --installed
96
97 - name: Setup Rust cache
98 uses: Swatinem/rust-cache@v2
99 with:
100 key: npm-${{ matrix.build.TARGET }}
101
102 - name: Build binary
103 shell: bash
104 run: cargo build --locked --release --target ${{ matrix.build.TARGET }}
105
106 - name: Install Node
107 uses: actions/setup-node@v6
108 with:
109 node-version-file: .nvmrc
110 registry-url: "https://registry.npmjs.org"
111
112 # Trusted Publishing (OIDC) needs npm >= 11.5.1. .nvmrc pins the Node
113 # version (>= 22.14.0, also an OIDC requirement); npm still needs
114 # upgrading past whatever ships with it.
115 - name: Upgrade npm for Trusted Publishing
116 shell: bash
117 run: npm install -g npm@latest
118
119 # Authentication is OIDC: each @getsigit/* and @smbcloud/* package has a
120 # Trusted Publisher pointing at this workflow, so there is no token to
121 # leak. @smbcloud is the pre-1.5.5 scope, published alongside @getsigit
122 # so that an install made before the rename keeps updating.
123 - name: Publish platform package to NPM
124 shell: bash
125 run: |
126 cd npm
127
128 # Binary name from Cargo.toml
129 cargo_bin="sigit"
130
131 # Prefix used for optional dependency package names
132 node_pkg_prefix="sigit"
133
134 # Derive OS and architecture from the build matrix name
135 # Matrix name format: <os>-<arch>[-<extra>]
136 node_os=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f1)
137 export node_os
138 node_arch=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f2)
139 export node_arch
140
141 # Set the version
142 export node_version="${{ env.RELEASE_VERSION }}"
143
144 # Build the platform package name; normalise win32 → windows
145 if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
146 export node_pkg="${node_pkg_prefix}-windows-${node_arch}"
147 else
148 export node_pkg="${node_pkg_prefix}-${node_os}-${node_arch}"
149 fi
150
151 # Create the package directory
152 mkdir -p "${node_pkg}/bin"
153
154 # Windows binaries carry a .exe extension
155 if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then
156 cargo_bin="${cargo_bin}.exe"
157 fi
158
159 cp "../target/${{ matrix.build.TARGET }}/release/${cargo_bin}" "${node_pkg}/bin"
160
161 # One binary, published under both scopes: re-render package.json and
162 # README for each, from the same directory.
163 for scope in "@getsigit" "@smbcloud"; do
164 node ./scripts/render-platform-package.cjs \
165 "${node_pkg}" \
166 "${node_version}" \
167 "${node_os}" \
168 "${node_arch}" \
169 "${scope}"
170
171 npm_package_name="${scope}/${node_pkg}"
172
173 # `npm view` exits non-zero both when the version is genuinely
174 # unpublished and when the registry could not be reached, so treat
175 # only an E404 as "not published yet". Anything else stops the job
176 # instead of publishing over a version that is already there.
177 if view_output=$(npm view "${npm_package_name}@${node_version}" version 2>&1); then
178 echo "${npm_package_name}@${node_version} already exists on npm, skipping publish"
179 continue
180 elif ! grep -q "E404" <<<"${view_output}"; then
181 echo "npm view failed while checking ${npm_package_name}@${node_version}:" >&2
182 echo "${view_output}" >&2
183 exit 1
184 fi
185
186 (cd "${node_pkg}" && npm publish --access public)
187 done
188
189 publish-npm-base:
190 name: Publish base NPM packages (@getsigit/sigit, @smbcloud/sigit)
191 needs: publish-npm-binaries
192 runs-on: ubuntu-latest
193
194 steps:
195 - name: Checkout
196 uses: actions/checkout@v6
197 with:
198 ref: ${{ github.event.inputs.tag || github.ref }}
199
200 - name: Set the release version
201 shell: bash
202 run: |
203 if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
204 release_version="${GITHUB_REF_NAME#v}"
205 else
206 release_version="${{ github.event.inputs.tag }}"
207 release_version="${release_version#v}"
208 fi
209 echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
210
211 - name: Install Node
212 uses: actions/setup-node@v6
213 with:
214 node-version-file: .nvmrc
215 registry-url: "https://registry.npmjs.org"
216
217 # Trusted Publishing (OIDC) needs npm >= 11.5.1. .nvmrc pins the Node
218 # version (>= 22.14.0, also an OIDC requirement); npm still needs
219 # upgrading past whatever ships with it.
220 - name: Upgrade npm for Trusted Publishing
221 shell: bash
222 run: npm install -g npm@latest
223
224 - name: Publish base packages to NPM
225 shell: bash
226 run: |
227 cd npm/sigit
228
229 npm_package_version="${{ env.RELEASE_VERSION }}"
230
231 # Rewrite package.json with the correct release version and matching
232 # optionalDependency versions, then build. The compiled launcher is
233 # scope-agnostic — it reads its own package.json at runtime to decide
234 # which scope to resolve the platform binary from — so both packages
235 # ship the same lib/ and it only needs building once.
236 node ../scripts/render-main-package.cjs \
237 "./package.json" \
238 "${npm_package_version}"
239
240 npm install
241 npm run build
242
243 # @getsigit/sigit is the canonical package; @smbcloud/sigit is the
244 # pre-1.5.5 name, kept publishing so installs predating the rename
245 # keep updating. Its template points optionalDependencies at the
246 # @smbcloud platform packages.
247 for template in "package-main.json.tmpl" "package-compat.json.tmpl"; do
248 node ../scripts/render-main-package.cjs \
249 "./package.json" \
250 "${npm_package_version}" \
251 "${template}"
252
253 npm_package_name=$(node -p "require('./package.json').name")
254
255 # Same E404-only reading as the platform packages above: a registry
256 # failure must not look like an unpublished version.
257 if view_output=$(npm view "${npm_package_name}@${npm_package_version}" version 2>&1); then
258 echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish"
259 continue
260 elif ! grep -q "E404" <<<"${view_output}"; then
261 echo "npm view failed while checking ${npm_package_name}@${npm_package_version}:" >&2
262 echo "${view_output}" >&2
263 exit 1
264 fi
265
266 npm publish --access public
267 done