-
Notifications
You must be signed in to change notification settings - Fork 2
321 lines (315 loc) · 16.1 KB
/
Copy pathnpm_release.yml
File metadata and controls
321 lines (315 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
name: npm release
# Publishes the classic runtime — @nativescript/windows — and the four engine variants —
# @nativescript/windows-{hermes,jsc,quickjs,v8} — to npm via OIDC trusted publishing. npm matches
# a trusted publisher on repository + workflow FILENAME + environment, so `npm_release.yml` and the
# `npm-publish` environment below are part of the credential: renaming either breaks publishing for
# all five packages, and every package needs its own trusted-publisher entry on npmjs.com.
#
# NOTE: `paths` filters deliberately do NOT accompany the tag trigger — a tag push can have an
# empty changed-file set, and paths+tags together silently skip the run.
on:
push:
tags:
- "v*" # -> all five packages at the tag's version
workflow_dispatch:
inputs:
engine:
description: "Which package to release"
type: choice
required: true
default: all
options: [all, classic, hermes, jsc, quickjs, v8]
version:
description: "Release version to cut, e.g. 0.1.0-alpha.2 (dist-tag follows the prerelease id; a plain version publishes 'latest'). Leave empty for a rolling 'next' build."
required: false
default: ""
rebuild_framework:
description: "Engine variants: rebuild the shared framework (tools + dotnet-bridge) from source instead of shipping the copies committed under template/framework. (The classic build always rebuilds it.)"
type: boolean
required: false
default: false
skip_arm64:
description: "Classic only: build x64-only, skipping the arm64 cross-compiles (the engine variants are x64-only regardless)."
type: boolean
required: false
default: false
# Minimal default token permissions for every job; the publish job declares id-token:write locally.
permissions:
contents: read
# Every package is the same framework — the WinUI 3 app template, dotnet-bridge and tools — with a
# runtime DLL staged as framework/libs/<arch>/nativescript.dll:
#
# classic `template/build.ps1` builds the workspace `nativescript` cdylib (rusty_v8) for x64 and
# arm64, release and devtools, rebuilds the tools (dotnet-tool, nsbundle_pack, sbg,
# typings-generator, ManifestMerger, dotnet-typings-gen) and stages it all under
# template/framework. Published from template/ as @nativescript/windows.
# variant `template/build.ps1 -Engine <engine>` copies the shared scaffolding out of
# template/framework and builds the engine cdylib into packages/windows-<engine>/framework
# as framework/libs/x64/nativescript.dll (x64 only, no devtools).
#
# A release is: setup (resolve version + package matrix) -> build (one Windows job per package,
# npm pack) -> publish (npm, OIDC). Everything the variants need is committed under
# template/framework, so a variant build only compiles the engine; `rebuild_framework` regenerates
# that scaffolding from source instead.
jobs:
setup:
name: Resolve version
runs-on: ubuntu-latest
outputs:
npm_version: ${{ steps.out.outputs.NPM_VERSION }}
npm_tag: ${{ steps.out.outputs.NPM_TAG }}
build_matrix: ${{ steps.out.outputs.BUILD_MATRIX }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
# setup-node v6 enables dependency caching by default when it detects a package manager —
# a cache-poisoning vector, worst in the jobs that publish. Disabled on every setup-node
# step in this workflow (also keeps pre-v6 behavior).
package-manager-cache: false
- name: Compute version, dist-tag and package matrix
id: out
env:
# env indirection keeps the dispatch inputs out of shell interpolation
INPUT_VERSION: ${{ inputs.version }}
INPUT_ENGINE: ${{ inputs.engine || 'all' }}
INPUT_SKIP_ARM64: ${{ inputs.skip_arm64 && 'true' || 'false' }}
run: |
args=(--version "$INPUT_VERSION" --engine "$INPUT_ENGINE")
if [ "$INPUT_SKIP_ARM64" = "true" ]; then args+=(--skip-arm64); fi
node ./scripts/resolve-release.mjs "${args[@]}"
build:
name: Build ${{ matrix.engine }}
runs-on: windows-latest
needs: setup
strategy:
fail-fast: false
# { include: [{ engine, dir, npm_name, tarball, rust_targets, rust_workspaces }, ...] } — the
# packages selected in `setup` (see scripts/resolve-release.mjs).
matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }}
env:
NPM_VERSION: ${{ needs.setup.outputs.npm_version }}
ENGINE: ${{ matrix.engine }}
PACKAGE_DIR: ${{ matrix.dir }}
SKIP_ARM64: ${{ inputs.skip_arm64 && 'true' || 'false' }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false # disable to prevent leaking credentials to build scripts
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
package-manager-cache: false
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
with:
toolchain: stable
# classic cross-compiles the arm64 runtime and tools; the variants build for the host.
targets: ${{ matrix.rust_targets }}
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
# The engine packages are excluded from the root workspace (their C builds / prebuilt
# engine links must not run on a plain `cargo build`), so each has its own target dir.
workspaces: ${{ matrix.rust_workspaces }}
key: ${{ matrix.engine }}
# rusty_v8's build script downloads its prebuilt static lib to <profile dir>/gn_out, which
# rust-cache prunes (it keeps only build/, deps/ and .fingerprint/) while keeping the build
# script's recorded output. When a lockfile change then recompiles the v8 crate, the script
# counts as fresh, nothing downloads the lib again, and the link fails with "could not find
# native static library `rusty_v8`". Drop the v8 build state so the script runs again. The
# profile dir is target/release for a host build and target/<triple>/<profile> for the
# classic cross builds, so every v8 build dir under the target roots is checked.
- name: Re-fetch rusty_v8's static lib when the cache pruned it
shell: pwsh
run: |
foreach ($root in @('target', (Join-Path $env:PACKAGE_DIR 'target'))) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem -Path $root -Recurse -Directory -Filter 'v8-*' -ErrorAction SilentlyContinue |
Where-Object { $_.Parent.Name -eq 'build' } |
ForEach-Object {
$profileDir = $_.Parent.Parent.FullName
if (-not (Test-Path (Join-Path $profileDir 'gn_out/obj/rusty_v8.lib'))) {
Write-Host "rusty_v8.lib missing under $profileDir; resetting the v8 build script."
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue `
(Join-Path $profileDir 'build/v8-*'), (Join-Path $profileDir '.fingerprint/v8-*')
}
}
}
# quickjs' napi shim uses clang/GCC C extensions that MSVC `cl` rejects; build.rs compiles it
# with clang-cl from this exact path and silently falls back to `cl` (which then fails deep in
# the C build) when it is absent. Fail early with the real reason instead.
- name: Ensure clang-cl (quickjs shim)
if: ${{ matrix.engine == 'quickjs' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$clangCl = 'C:\Program Files\LLVM\bin\clang-cl.exe'
if (-not (Test-Path $clangCl)) {
Write-Host "clang-cl not found at $clangCl — installing LLVM."
choco install llvm --no-progress -y
}
if (-not (Test-Path $clangCl)) {
throw "clang-cl is required to compile the quickjs napi shim and is not at $clangCl."
}
& $clangCl --version
# The classic build publishes ManifestMerger and dotnet-typings-gen with `dotnet publish`
# every time; a variant only needs the SDK when it regenerates the shared framework.
- name: Setup .NET
if: ${{ matrix.engine == 'classic' || inputs.rebuild_framework }}
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
# dotnet-typings-gen targets net10.0; ManifestMerger is netstandard2.0 and builds on it.
dotnet-version: "10.x"
- name: Rebuild shared framework
if: ${{ matrix.engine != 'classic' && inputs.rebuild_framework }}
shell: pwsh
# -SkipRelease -SkipDevtools: the classic V8 nativescript.dll is not staged into a variant
# (the engine cdylib takes its place), so building it here would be thrown away.
# -SkipArm64: the variants are x64-only (`cpu: ["x64"]` in their package.json).
run: |
$ErrorActionPreference = 'Stop'
./template/build.ps1 -SkipArm64 -SkipRelease -SkipDevtools
# The runtime DLLs and tool binaries are committed under template/framework so the variants
# can stage them without a classic build. A classic release must ship what THIS run built,
# so clear them first: with the committed copies gone, the checks after the build can only
# pass on fresh output, never on a stale binary a failed step left in place.
- name: Clear committed runtime DLLs and tools (classic)
if: ${{ matrix.engine == 'classic' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
foreach ($dir in @('template/framework/libs/x64', 'template/framework/libs/arm64',
'template/framework/libs/devtools', 'template/framework/tools')) {
if (Test-Path $dir) { Remove-Item -Recurse -Force $dir }
}
- name: Set package version
shell: pwsh
working-directory: ${{ matrix.dir }}
run: npm version $env:NPM_VERSION --no-git-tag-version --allow-same-version
- name: Build ${{ matrix.engine }} framework
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# A hashtable splat binds by parameter name. (An array splat would pass its elements
# positionally, so '-Engine' itself would land in build.ps1's -Engine parameter.)
$buildArgs = @{}
if ($env:ENGINE -ne 'classic') {
$buildArgs.Engine = $env:ENGINE
} elseif ($env:SKIP_ARM64 -eq 'true') {
$buildArgs.SkipArm64 = $true
}
# build.ps1 reports through Write-Host, so the log has to be teed off the information
# stream (`*>&1`) rather than stdout for the log checks below to see anything.
$logPath = Join-Path $env:RUNNER_TEMP "build-$env:ENGINE.log"
./template/build.ps1 @buildArgs *>&1 | Tee-Object -FilePath $logPath
$libs = Join-Path $env:PACKAGE_DIR 'framework/libs'
$tools = Join-Path $env:PACKAGE_DIR 'framework/tools'
if ($env:ENGINE -eq 'classic') {
# The tool builds only warn on failure and skip the copy; treat that as fatal here.
$failure = 'failed for target|Expected build output not found|Skipping copy|dotnet publish for .* failed'
if (Select-String -Path $logPath -Pattern $failure -Quiet) {
throw "A tool build failed — see the log above."
}
$archs = @('x64')
if ($env:SKIP_ARM64 -ne 'true') { $archs += 'arm64' }
$expected = @('ManifestMerger/ManifestMerger.dll', 'dotnet-typings-gen/dotnet-typings-gen.dll') |
ForEach-Object { Join-Path $tools $_ }
foreach ($arch in $archs) {
$expected += (Join-Path $libs "$arch/nativescript.dll")
$expected += (Join-Path $libs "devtools/$arch/nativescript.dll")
foreach ($tool in @('dotnet-tool', 'nsbundle_pack', 'sbg', 'typings-generator')) {
$expected += (Join-Path $tools "$tool-$arch.exe")
}
}
} else {
# A missing engine runtime DLL (hermes.dll, JavaScriptCore.dll, …) beside the cdylib is
# only a warning there, but it ships a framework that fails to load at app start.
if (Select-String -Path $logPath -Pattern 'expected engine DLL not found' -Quiet) {
throw "An engine runtime DLL was not staged — see the WARN above."
}
$expected = @(Join-Path $libs 'x64/nativescript.dll')
}
$missing = $expected | Where-Object { -not (Test-Path $_) }
if ($missing) { throw "Build output missing: $($missing -join ', ')" }
- name: Pack
shell: pwsh
working-directory: ${{ matrix.dir }}
run: |
$ErrorActionPreference = 'Stop'
# --pack-destination does not create the directory.
$dist = Join-Path $env:GITHUB_WORKSPACE 'dist'
New-Item -ItemType Directory -Force -Path $dist | Out-Null
npm pack --pack-destination $dist
- name: Upload npm package artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: npm-package-${{ matrix.engine }}
path: dist/${{ matrix.tarball }}
if-no-files-found: error
publish:
name: Publish ${{ matrix.engine }}
runs-on: ubuntu-latest
# Part of the npm trusted-publisher match — see the header note before renaming.
environment: npm-publish
needs:
- setup
- build
permissions:
contents: read
id-token: write # OIDC trusted publishing + provenance
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }}
env:
NPM_VERSION: ${{ needs.setup.outputs.npm_version }}
NPM_TAG: ${{ needs.setup.outputs.npm_tag }}
NPM_NAME: ${{ matrix.npm_name }}
TARBALL: ${{ matrix.tarball }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
package-manager-cache: false
registry-url: "https://registry.npmjs.org"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: npm-package-${{ matrix.engine }}
path: dist
- name: Update npm (required for OIDC trusted publishing)
run: |
corepack enable npm
corepack install -g npm@11.5.1
test "$(npm --version)" = "11.5.1"
test "$(npx --version)" = "11.5.1"
- name: Publish package (OIDC trusted publishing)
if: ${{ vars.USE_NPM_TOKEN != 'true' }}
run: |
echo "Publishing $NPM_NAME@$NPM_VERSION to NPM with tag $NPM_TAG via OIDC trusted publishing..."
unset NODE_AUTH_TOKEN
if [ -n "${NPM_CONFIG_USERCONFIG:-}" ]; then
rm -f "$NPM_CONFIG_USERCONFIG"
fi
npm publish "./dist/$TARBALL" --tag "$NPM_TAG" --access public --provenance
env:
NODE_AUTH_TOKEN: ""
- name: Publish package (granular token)
if: ${{ vars.USE_NPM_TOKEN == 'true' }}
run: |
echo "Publishing $NPM_NAME@$NPM_VERSION to NPM with tag $NPM_TAG via granular token..."
npm publish "./dist/$TARBALL" --tag "$NPM_TAG" --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}