Skip to content

Commit 4ac13af

Browse files
authored
ci: trusted publishers (#34)
1 parent 3a3bb13 commit 4ac13af

18 files changed

Lines changed: 1265 additions & 4 deletions

File tree

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
name: NPM Trusted Release (iOS engines)
2+
3+
# Publishes one or more engine-specific NativeScript iOS runtime packages
4+
# (@nativescript/ios-v8, @nativescript/ios-hermes, @nativescript/ios-jsc,
5+
# @nativescript/ios-quickjs) via npm trusted publishing (OIDC).
6+
#
7+
# Each package must be configured on npmjs.com with a trusted publisher that
8+
# points at this repository + workflow + environment. With `engine: all`, the
9+
# workflow fans out across all four engines via a matrix.
10+
11+
on:
12+
workflow_dispatch:
13+
inputs:
14+
engine:
15+
description: "Engine to release (or 'all' to publish every engine)"
16+
required: true
17+
type: choice
18+
default: v8
19+
options:
20+
- v8
21+
- hermes
22+
- jsc
23+
- quickjs
24+
- all
25+
release-type:
26+
description: "Version bump (patch/minor/major publish to 'latest'; prerelease uses 'preid' as the dist-tag)"
27+
required: false
28+
type: choice
29+
default: prerelease
30+
options:
31+
- prerelease
32+
- patch
33+
- minor
34+
- major
35+
preid:
36+
description: "Prerelease identifier (used only when release-type=prerelease; also becomes the npm dist-tag, e.g. next | canary)"
37+
required: false
38+
type: string
39+
default: next
40+
dry-run:
41+
description: "Run release steps without making changes (no git push, no publish)"
42+
required: false
43+
type: boolean
44+
default: true
45+
46+
concurrency:
47+
# Avoid overlapping publishes on the same ref/engine selection.
48+
group: npm-trusted-release-${{ github.ref }}-${{ inputs.engine }}
49+
cancel-in-progress: false
50+
51+
env:
52+
XCODE_VERSION: "26.2.0"
53+
54+
jobs:
55+
matrix:
56+
name: Resolve engine matrix
57+
runs-on: ubuntu-latest
58+
outputs:
59+
engines: ${{ steps.compute.outputs.engines }}
60+
steps:
61+
- name: Compute matrix
62+
id: compute
63+
run: |
64+
if [ "${{ inputs.engine }}" = "all" ]; then
65+
echo 'engines=["v8","hermes","jsc","quickjs"]' >> "$GITHUB_OUTPUT"
66+
else
67+
echo "engines=[\"${{ inputs.engine }}\"]" >> "$GITHUB_OUTPUT"
68+
fi
69+
70+
build:
71+
name: Build ${{ matrix.engine }}
72+
needs: matrix
73+
runs-on: macos-26
74+
strategy:
75+
fail-fast: false
76+
matrix:
77+
engine: ${{ fromJson(needs.matrix.outputs.engines) }}
78+
outputs:
79+
# Per-engine outputs aren't natively supported with matrices, so each job
80+
# uploads its computed metadata alongside the tarball artifact.
81+
placeholder: noop
82+
env:
83+
IOS_VARIANT: ios-${{ matrix.engine }}
84+
steps:
85+
- name: Harden the runner (Audit all outbound calls)
86+
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
87+
with:
88+
egress-policy: audit
89+
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
90+
with:
91+
xcode-version: ${{ env.XCODE_VERSION }}
92+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
93+
with:
94+
fetch-depth: 0
95+
submodules: recursive
96+
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
97+
with:
98+
node-version: 24
99+
registry-url: "https://registry.npmjs.org"
100+
- name: Install Python
101+
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
102+
with:
103+
python-version: "3"
104+
- name: Install Dependencies
105+
run: |
106+
npm install
107+
python3 -m pip install --upgrade pip six
108+
if ! command -v ld64.lld >/dev/null; then
109+
brew list lld || brew install lld
110+
fi
111+
if ! command -v cmake >/dev/null; then
112+
brew list cmake || brew install cmake
113+
fi
114+
if [ ! -x /usr/local/bin/cmake ]; then
115+
sudo mkdir -p /usr/local/bin
116+
sudo ln -sf "$(command -v cmake)" /usr/local/bin/cmake
117+
fi
118+
- name: Bump version
119+
id: bump
120+
shell: bash
121+
run: |
122+
set -euo pipefail
123+
release_type='${{ inputs.release-type }}'
124+
preid='${{ inputs.preid }}'
125+
pkg_dir="packages/${IOS_VARIANT}"
126+
127+
pushd "$pkg_dir" >/dev/null
128+
if [ "$release_type" = "prerelease" ]; then
129+
npm version prerelease --preid "$preid" --no-git-tag-version >/dev/null
130+
else
131+
npm version "$release_type" --no-git-tag-version >/dev/null
132+
fi
133+
NPM_VERSION=$(node -e "console.log(require('./package.json').version)")
134+
popd >/dev/null
135+
136+
NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node ./scripts/get-npm-tag.js "$IOS_VARIANT")
137+
echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT"
138+
echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT"
139+
echo "Resolved $IOS_VARIANT@$NPM_VERSION (tag: $NPM_TAG)"
140+
- name: Build (--${{ matrix.engine }})
141+
run: ./scripts/build_all_ios.sh --${{ matrix.engine }}
142+
- name: Record metadata
143+
shell: bash
144+
run: |
145+
mkdir -p packages/${IOS_VARIANT}/dist
146+
cat > packages/${IOS_VARIANT}/dist/release-meta.json <<EOF
147+
{
148+
"engine": "${{ matrix.engine }}",
149+
"variant": "${IOS_VARIANT}",
150+
"package_name": "@nativescript/ios-${{ matrix.engine }}",
151+
"version": "${{ steps.bump.outputs.NPM_VERSION }}",
152+
"tag": "${{ steps.bump.outputs.NPM_TAG }}"
153+
}
154+
EOF
155+
- name: Upload npm package artifact
156+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
157+
with:
158+
name: npm-package-${{ matrix.engine }}
159+
path: |
160+
packages/ios-${{ matrix.engine }}/dist/nativescript-ios-${{ matrix.engine }}-${{ steps.bump.outputs.NPM_VERSION }}.tgz
161+
packages/ios-${{ matrix.engine }}/dist/release-meta.json
162+
- name: Upload dSYMs artifact
163+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
164+
with:
165+
name: NativeScript-dSYMs-${{ matrix.engine }}
166+
path: dist/dSYMs
167+
168+
publish:
169+
name: Publish ${{ matrix.engine }}
170+
needs:
171+
- matrix
172+
- build
173+
runs-on: ubuntu-latest
174+
environment:
175+
name: ${{ inputs.dry-run && 'npm-publish-dry-run' || 'npm-publish' }}
176+
strategy:
177+
fail-fast: false
178+
matrix:
179+
engine: ${{ fromJson(needs.matrix.outputs.engines) }}
180+
permissions:
181+
contents: read
182+
id-token: write
183+
steps:
184+
- name: Harden the runner (Audit all outbound calls)
185+
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
186+
with:
187+
egress-policy: audit
188+
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
189+
with:
190+
node-version: 24
191+
registry-url: "https://registry.npmjs.org"
192+
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
193+
with:
194+
name: npm-package-${{ matrix.engine }}
195+
path: packages/ios-${{ matrix.engine }}/dist
196+
- name: Update npm (required for OIDC trusted publishing)
197+
run: |
198+
corepack enable npm
199+
corepack install -g npm@11.6.2
200+
test "$(npm --version)" = "11.6.2"
201+
test "$(npx --version)" = "11.6.2"
202+
- name: Read release metadata
203+
id: meta
204+
shell: bash
205+
run: |
206+
set -euo pipefail
207+
meta="packages/ios-${{ matrix.engine }}/dist/release-meta.json"
208+
if [ ! -f "$meta" ]; then
209+
echo "Missing release metadata at $meta" >&2
210+
exit 1
211+
fi
212+
NPM_VERSION=$(node -e "console.log(require('./$meta').version)")
213+
NPM_TAG=$(node -e "console.log(require('./$meta').tag)")
214+
PACKAGE_NAME=$(node -e "console.log(require('./$meta').package_name)")
215+
echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT"
216+
echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT"
217+
echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
218+
- name: Publish package (OIDC trusted publishing)
219+
if: ${{ vars.USE_NPM_TOKEN != 'true' }}
220+
shell: bash
221+
env:
222+
NPM_VERSION: ${{ steps.meta.outputs.NPM_VERSION }}
223+
NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }}
224+
PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }}
225+
DRY_RUN: ${{ inputs.dry-run }}
226+
NODE_AUTH_TOKEN: ""
227+
run: |
228+
set -euo pipefail
229+
TARBALL="packages/ios-${{ matrix.engine }}/dist/nativescript-ios-${{ matrix.engine }}-${NPM_VERSION}.tgz"
230+
PUBLISH_ARGS=("$TARBALL" --tag "$NPM_TAG" --access public --provenance)
231+
if [ "$DRY_RUN" = "true" ]; then
232+
PUBLISH_ARGS+=(--dry-run)
233+
fi
234+
echo "Publishing ${PACKAGE_NAME}@${NPM_VERSION} (tag: $NPM_TAG, dry-run: $DRY_RUN) via OIDC trusted publishing..."
235+
unset NODE_AUTH_TOKEN
236+
rm -f ~/.npmrc || true
237+
if [ -n "${NPM_CONFIG_USERCONFIG:-}" ]; then
238+
rm -f "$NPM_CONFIG_USERCONFIG" || true
239+
fi
240+
npm publish "${PUBLISH_ARGS[@]}"
241+
- name: Publish package (granular token fallback)
242+
if: ${{ vars.USE_NPM_TOKEN == 'true' }}
243+
shell: bash
244+
env:
245+
NPM_VERSION: ${{ steps.meta.outputs.NPM_VERSION }}
246+
NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }}
247+
PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }}
248+
DRY_RUN: ${{ inputs.dry-run }}
249+
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
250+
run: |
251+
set -euo pipefail
252+
TARBALL="packages/ios-${{ matrix.engine }}/dist/nativescript-ios-${{ matrix.engine }}-${NPM_VERSION}.tgz"
253+
PUBLISH_ARGS=("$TARBALL" --tag "$NPM_TAG" --access public --provenance)
254+
if [ "$DRY_RUN" = "true" ]; then
255+
PUBLISH_ARGS+=(--dry-run)
256+
fi
257+
echo "Publishing ${PACKAGE_NAME}@${NPM_VERSION} (tag: $NPM_TAG, dry-run: $DRY_RUN) via granular token..."
258+
npm publish "${PUBLISH_ARGS[@]}"
259+
260+
summary:
261+
name: Release summary
262+
if: always()
263+
needs:
264+
- matrix
265+
- build
266+
- publish
267+
runs-on: ubuntu-latest
268+
steps:
269+
- name: Print summary
270+
run: |
271+
echo "Engine selection: ${{ inputs.engine }}"
272+
echo "Release type: ${{ inputs.release-type }}"
273+
echo "Preid: ${{ inputs.preid }}"
274+
echo "Dry run: ${{ inputs.dry-run }}"
275+
echo "Engines: ${{ needs.matrix.outputs.engines }}"
276+
echo "Build result: ${{ needs.build.result }}"
277+
echo "Publish result: ${{ needs.publish.result }}"

0 commit comments

Comments
 (0)