From 3248b94d4227cc40b8d26778ca0b68fc93958dcf Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Thu, 5 Nov 2026 22:57:12 -0400 Subject: [PATCH 1/2] ci(release): publish @nativescript/windows-napi with the runtimes The addon had its own tag-triggered publish (windows-napi-v*) that was never used: it needed an NPM_TOKEN secret and published prereleases without a dist-tag. It now releases from npm_release.yml with every other package, at the same version and dist-tag, via trusted publishing. - resolve-release.mjs: `napi` (also part of `all`) adds three entries: the x64 and arm64 binary packages (kind napi-binding) and the JS root (kind napi-root). --skip-arm64 also drops the arm64 binary. - npm_release.yml: napi-binding builds with `napi build --target` and copies windows..node into its npm/ package; napi-root pins its optionalDependencies to the release version. Framework-only steps are gated on kind == framework. - windows-napi.yml keeps the PR build and test; its publish job and tag trigger are removed. - package.json: repository points at NativeScript/windows (npm provenance requires it) in all three packages; prepublishOnly, which published the binary packages from the folder, is removed. --- .github/workflows/npm_release.yml | 61 ++++++++++--- .github/workflows/windows-napi.yml | 40 +------- scripts/resolve-release.mjs | 91 ++++++++++++++----- .../npm/win32-arm64-msvc/package.json | 7 +- windows-napi/npm/win32-x64-msvc/package.json | 7 +- windows-napi/package.json | 3 +- 6 files changed, 130 insertions(+), 79 deletions(-) diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 19d47f4..01716ad 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -1,17 +1,18 @@ 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. +# Publishes the classic runtime (@nativescript/windows), the four engine variants +# (@nativescript/windows-{hermes,jsc,quickjs,v8}) and the Node-API addon (@nativescript/windows-napi +# plus its two binary packages) 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 every +# package, 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 + - "v*" # -> every package at the tag's version workflow_dispatch: inputs: engine: @@ -19,7 +20,7 @@ on: type: choice required: true default: all - options: [all, classic, hermes, jsc, quickjs, v8] + options: [all, classic, hermes, jsc, quickjs, v8, napi] 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 @@ -30,7 +31,7 @@ on: required: false default: false skip_arm64: - description: "Classic only: build x64-only, skipping the arm64 cross-compiles (the engine variants are x64-only regardless)." + description: "Classic and napi: build x64-only, skipping the arm64 cross-compiles (the engine variants are x64-only regardless)." type: boolean required: false default: false @@ -50,6 +51,10 @@ permissions: # template/framework and builds the engine cdylib into packages/windows-/framework # as framework/libs/x64/nativescript.dll (x64 only, no devtools). # +# windows-napi is three packages: one per triple carrying `windows..node` (napi-binding, +# built with `napi build --target`) and the JS root (napi-root), whose optionalDependencies are +# pinned to the release version. +# # 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 @@ -95,13 +100,14 @@ jobs: 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). + # { include: [{ engine, kind, dir, npm_name, tarball, napi_target, rust_targets, + # rust_workspaces }, ...] }: the packages selected in `setup` (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 }} + NAPI_TARGET: ${{ matrix.napi_target }} SKIP_ARM64: ${{ inputs.skip_arm64 && 'true' || 'false' }} steps: - name: Harden the runner (Audit all outbound calls) @@ -115,12 +121,15 @@ jobs: with: node-version: 22 package-manager-cache: false + # The windows-napi root package is JS only. - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + if: ${{ matrix.kind != 'napi-root' }} 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 + if: ${{ matrix.kind != 'napi-root' }} 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. @@ -134,6 +143,7 @@ jobs: # profile dir is target/release for a host build and target// 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 + if: ${{ matrix.kind == 'framework' }} shell: pwsh run: | foreach ($root in @('target', (Join-Path $env:PACKAGE_DIR 'target'))) { @@ -169,14 +179,14 @@ jobs: # 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' || github.event_name == 'push' || inputs.rebuild_framework }} + if: ${{ matrix.kind == 'framework' && (matrix.engine == 'classic' || github.event_name == 'push' || 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 # Tag releases always rebuild, so a published variant never ships binaries built off CI. - if: ${{ matrix.engine != 'classic' && (github.event_name == 'push' || inputs.rebuild_framework) }} + if: ${{ matrix.kind == 'framework' && matrix.engine != 'classic' && (github.event_name == 'push' || 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. @@ -210,7 +220,34 @@ jobs: shell: pwsh working-directory: ${{ matrix.dir }} run: npm version $env:NPM_VERSION --no-git-tag-version --allow-same-version + - name: Pin windows-napi binary packages + if: ${{ matrix.kind == 'napi-root' }} + shell: pwsh + working-directory: ${{ matrix.dir }} + run: | + $ErrorActionPreference = 'Stop' + $pkg = Get-Content package.json -Raw | ConvertFrom-Json + foreach ($dep in @($pkg.optionalDependencies.PSObject.Properties)) { + $dep.Value = $env:NPM_VERSION + } + $pkg | ConvertTo-Json -Depth 10 | Set-Content package.json -Encoding utf8NoBOM + Get-Content package.json + - name: Build ${{ matrix.napi_target }} binding + if: ${{ matrix.kind == 'napi-binding' }} + shell: pwsh + working-directory: windows-napi + run: | + $ErrorActionPreference = 'Stop' + npm ci + if ($LASTEXITCODE -ne 0) { throw "npm ci failed" } + npx napi build --platform --release --target $env:NAPI_TARGET + if ($LASTEXITCODE -ne 0) { throw "napi build failed for $env:NAPI_TARGET" } + # The binary package directory is named after the platform part of the file name. + $node = "windows.$(Split-Path $env:PACKAGE_DIR -Leaf).node" + if (-not (Test-Path $node)) { throw "Build output missing: windows-napi/$node" } + Copy-Item $node (Join-Path $env:GITHUB_WORKSPACE $env:PACKAGE_DIR) - name: Build ${{ matrix.engine }} framework + if: ${{ matrix.kind == 'framework' }} shell: pwsh run: | $ErrorActionPreference = 'Stop' diff --git a/.github/workflows/windows-napi.yml b/.github/workflows/windows-napi.yml index f90431f..154262f 100644 --- a/.github/workflows/windows-napi.yml +++ b/.github/workflows/windows-napi.yml @@ -1,11 +1,7 @@ name: windows-napi -# 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. +# Builds and tests the addon on pull requests. Releases publish it from npm_release.yml. on: - push: - tags: - - 'windows-napi-v*' pull_request: paths: - 'windows-napi/**' @@ -60,37 +56,3 @@ jobs: name: bindings-${{ matrix.settings.target }} path: windows-napi/windows.*.node if-no-files-found: error - - publish: - name: publish to npm - if: startsWith(github.ref, 'refs/tags/windows-napi-v') - needs: build - runs-on: windows-latest - permissions: - contents: read - id-token: write # npm provenance - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 - with: - node-version: 20 - registry-url: https://registry.npmjs.org - - name: Install npm dependencies - working-directory: windows-napi - run: npm ci - - name: Download all binding artifacts - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - path: windows-napi/artifacts - - name: Distribute artifacts into npm/ sub-packages - working-directory: windows-napi - run: npx napi artifacts - - name: Publish - working-directory: windows-napi - # `napi prepublish` (prepublishOnly script) publishes each npm/ sub-package, - # then the root package publishes with them as optionalDependencies. - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # Applies to the sub-package publishes `napi prepublish` runs as well. - NPM_CONFIG_PROVENANCE: "true" diff --git a/scripts/resolve-release.mjs b/scripts/resolve-release.mjs index cd0466f..8c36d65 100644 --- a/scripts/resolve-release.mjs +++ b/scripts/resolve-release.mjs @@ -11,7 +11,7 @@ // // One version covers every package in a run: the classic runtime and the four engine variants are // the same framework built from the same commit and differ only in the runtime DLL, so they -// release in lockstep. +// release in lockstep. The windows-napi addon is built from the same runtime crate and follows. // // The repo has no root package.json, so this stays dependency-free — no semver/dayjs, and no // `npm install` step in front of it. @@ -33,14 +33,35 @@ const PACKAGES = { quickjs: { dir: "packages/windows-quickjs", name: "@nativescript/windows-quickjs" }, v8: { dir: "packages/windows-v8", name: "@nativescript/windows-v8" }, }; -const ENGINES = Object.keys(PACKAGES); + +// `napi`: the @nativescript/windows-napi addon, published napi-rs style as a JS root package plus +// one binary package per triple. Each is its own matrix entry; `kind` selects the build steps. +const NAPI = [ + { + key: "napi-x64", + dir: "windows-napi/npm/win32-x64-msvc", + name: "@nativescript/windows-napi-win32-x64-msvc", + kind: "napi-binding", + target: "x86_64-pc-windows-msvc", + }, + { + key: "napi-arm64", + dir: "windows-napi/npm/win32-arm64-msvc", + name: "@nativescript/windows-napi-win32-arm64-msvc", + kind: "napi-binding", + target: "aarch64-pc-windows-msvc", + }, + { key: "napi", dir: "windows-napi", name: "@nativescript/windows-napi", kind: "napi-root" }, +]; +const ENGINES = [...Object.keys(PACKAGES), "napi"]; const USAGE = `Usage: node scripts/resolve-release.mjs [--version ] [--engine ] [--skip-arm64] --version release version to cut (leading "v" allowed); empty or omitted resolves from GITHUB_REF (v* tag) or falls back to a rolling "next" prerelease --engine all (default) | ${ENGINES.join(" | ")} - --skip-arm64 build the classic package x64-only (the engine variants are x64-only regardless) + --skip-arm64 build classic x64-only and skip the windows-napi arm64 package (the engine + variants are x64-only regardless) -h, --help show this help`; let values; @@ -123,10 +144,11 @@ if (inputVersion) { // that is already released takes a patch bump, so the "next" channel never trails "latest". let base; for (const e of engines) { - const manifest = path.join(repoRoot, PACKAGES[e].dir, "package.json"); + const dir = e === "napi" ? "windows-napi" : PACKAGES[e].dir; + const manifest = path.join(repoRoot, dir, "package.json"); const declared = parseVersion( JSON.parse(fs.readFileSync(manifest, "utf8")).version, - `${PACKAGES[e].dir}/package.json version` + `${dir}/package.json version` ); if (!base || isNewer(declared, base)) base = declared; } @@ -138,30 +160,51 @@ if (inputVersion) { const tag = npmTag(parseVersion(version, "Release version"), version); // One matrix entry per package. Besides the engine key the workflow needs, for each package: +// kind framework (classic and engine variants) | napi-binding | napi-root // dir where package.json lives (npm version / npm pack run there) // npm_name the published name // tarball what `npm pack` emits ("@scope/name" -> "scope-name-.tgz") +// napi_target the Rust triple a napi-binding entry builds // rust_targets extra Rust targets the toolchain must install (classic cross-compiles arm64) // rust_workspaces the Swatinem/rust-cache `workspaces` mapping; the engine packages are excluded // from the root workspace and build into their own target/ dir -const matrix = { - include: engines.map((e) => { - const pkg = PACKAGES[e]; - const classic = e === "classic"; - return { - engine: e, - dir: pkg.dir, - npm_name: pkg.name, - tarball: `${pkg.name.replace(/^@/, "").replace("/", "-")}-${version}.tgz`, - rust_targets: classic - ? values["skip-arm64"] - ? "x86_64-pc-windows-msvc" - : "x86_64-pc-windows-msvc,aarch64-pc-windows-msvc" - : "", - rust_workspaces: classic ? ". -> target" : `. -> target\n${pkg.dir} -> target`, - }; - }), -}; +const tarball = (name) => `${name.replace(/^@/, "").replace("/", "-")}-${version}.tgz`; +const include = []; +for (const e of engines) { + if (e === "napi") { + for (const n of NAPI) { + if (n.target === "aarch64-pc-windows-msvc" && values["skip-arm64"]) continue; + include.push({ + engine: n.key, + kind: n.kind, + dir: n.dir, + npm_name: n.name, + tarball: tarball(n.name), + napi_target: n.target || "", + rust_targets: n.target || "", + rust_workspaces: ". -> target", + }); + } + continue; + } + const pkg = PACKAGES[e]; + const classic = e === "classic"; + include.push({ + engine: e, + kind: "framework", + dir: pkg.dir, + npm_name: pkg.name, + tarball: tarball(pkg.name), + napi_target: "", + rust_targets: classic + ? values["skip-arm64"] + ? "x86_64-pc-windows-msvc" + : "x86_64-pc-windows-msvc,aarch64-pc-windows-msvc" + : "", + rust_workspaces: classic ? ". -> target" : `. -> target\n${pkg.dir} -> target`, + }); +} +const matrix = { include }; if (process.env.GITHUB_OUTPUT) { fs.appendFileSync( @@ -170,5 +213,5 @@ if (process.env.GITHUB_OUTPUT) { ); } console.log( - `Resolved ${version} (dist-tag: ${tag}) for ${engines.map((e) => PACKAGES[e].name).join(", ")}` + `Resolved ${version} (dist-tag: ${tag}) for ${include.map((i) => i.npm_name).join(", ")}` ); diff --git a/windows-napi/npm/win32-arm64-msvc/package.json b/windows-napi/npm/win32-arm64-msvc/package.json index 5b4bc57..c03c5c3 100644 --- a/windows-napi/npm/win32-arm64-msvc/package.json +++ b/windows-napi/npm/win32-arm64-msvc/package.json @@ -12,7 +12,12 @@ "windows.win32-arm64-msvc.node" ], "description": "Node-API WinRT interop for Windows.", + "repository": { + "type": "git", + "url": "https://github.com/NativeScript/windows", + "directory": "windows-napi/npm/win32-arm64-msvc" + }, "engines": { "node": ">= 18" } -} \ No newline at end of file +} diff --git a/windows-napi/npm/win32-x64-msvc/package.json b/windows-napi/npm/win32-x64-msvc/package.json index eb6fe67..381d2a0 100644 --- a/windows-napi/npm/win32-x64-msvc/package.json +++ b/windows-napi/npm/win32-x64-msvc/package.json @@ -12,7 +12,12 @@ "windows.win32-x64-msvc.node" ], "description": "Node-API WinRT interop for Windows.", + "repository": { + "type": "git", + "url": "https://github.com/NativeScript/windows", + "directory": "windows-napi/npm/win32-x64-msvc" + }, "engines": { "node": ">= 18" } -} \ No newline at end of file +} diff --git a/windows-napi/package.json b/windows-napi/package.json index c151c21..b5fa9c0 100644 --- a/windows-napi/package.json +++ b/windows-napi/package.json @@ -35,12 +35,11 @@ "build": "napi build --platform --release", "build:debug": "napi build --platform", "artifacts": "napi artifacts", - "prepublishOnly": "napi prepublish -t npm --skip-gh-release", "test": "node test/run-tests.js" }, "repository": { "type": "git", - "url": "https://github.com/triniwiz/windows-runtime.git", + "url": "https://github.com/NativeScript/windows", "directory": "windows-napi" }, "devDependencies": { From 309583ea1f404a4db986f78d39175a25950dbcc5 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Thu, 5 Nov 2026 23:10:25 -0400 Subject: [PATCH 2/2] ci(release): ship windows-napi as one package with both binaries Replaces the napi-rs per-triple packages with a single @nativescript/windows-napi that carries windows.win32-x64-msvc.node and windows.win32-arm64-msvc.node. The generated loader already prefers a binary next to it, so no code changes; one publish and one trusted publisher instead of three, and cross-arch app builds get both binaries. - resolve-release.mjs: `napi` is a regular package entry; it builds every rust_target (arm64 dropped with --skip-arm64). - npm_release.yml: one "Build windows-napi" step runs `napi build --target` per triple; framework-only steps skip napi. - Removes windows-napi/npm/, optionalDependencies and the `artifacts` script; package-lock.json gets the package's real name. - packages/README.md describes the new flow. --- .github/workflows/npm_release.yml | 71 ++++++-------- packages/README.md | 18 ++-- scripts/resolve-release.mjs | 94 ++++++------------- windows-napi/.gitignore | 2 +- windows-napi/npm/win32-arm64-msvc/README.md | 3 - .../npm/win32-arm64-msvc/package.json | 23 ----- windows-napi/npm/win32-x64-msvc/README.md | 3 - windows-napi/npm/win32-x64-msvc/package.json | 23 ----- windows-napi/package-lock.json | 4 +- windows-napi/package.json | 5 - 10 files changed, 67 insertions(+), 179 deletions(-) delete mode 100644 windows-napi/npm/win32-arm64-msvc/README.md delete mode 100644 windows-napi/npm/win32-arm64-msvc/package.json delete mode 100644 windows-napi/npm/win32-x64-msvc/README.md delete mode 100644 windows-napi/npm/win32-x64-msvc/package.json diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 01716ad..abe953b 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -1,18 +1,18 @@ name: npm release -# Publishes the classic runtime (@nativescript/windows), the four engine variants -# (@nativescript/windows-{hermes,jsc,quickjs,v8}) and the Node-API addon (@nativescript/windows-napi -# plus its two binary packages) 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 every -# package, and every package needs its own trusted-publisher entry on npmjs.com. +# Publishes the classic runtime — @nativescript/windows — the four engine variants — +# @nativescript/windows-{hermes,jsc,quickjs,v8} — and the Node-API addon — @nativescript/windows-napi +# — 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 six 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*" # -> every package at the tag's version + - "v*" # -> all six packages at the tag's version workflow_dispatch: inputs: engine: @@ -51,9 +51,9 @@ permissions: # template/framework and builds the engine cdylib into packages/windows-/framework # as framework/libs/x64/nativescript.dll (x64 only, no devtools). # -# windows-napi is three packages: one per triple carrying `windows..node` (napi-binding, -# built with `napi build --target`) and the JS root (napi-root), whose optionalDependencies are -# pinned to the release version. +# napi `napi build --target ` for each of `rust_targets` puts a +# windows-napi/windows..node per arch into the one package; its loader picks +# the one for the running CPU. # # 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 @@ -100,14 +100,13 @@ jobs: needs: setup strategy: fail-fast: false - # { include: [{ engine, kind, dir, npm_name, tarball, napi_target, rust_targets, - # rust_workspaces }, ...] }: the packages selected in `setup` (scripts/resolve-release.mjs). + # { 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 }} - NAPI_TARGET: ${{ matrix.napi_target }} SKIP_ARM64: ${{ inputs.skip_arm64 && 'true' || 'false' }} steps: - name: Harden the runner (Audit all outbound calls) @@ -121,15 +120,12 @@ jobs: with: node-version: 22 package-manager-cache: false - # The windows-napi root package is JS only. - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - if: ${{ matrix.kind != 'napi-root' }} with: toolchain: stable - # classic cross-compiles the arm64 runtime and tools; the variants build for the host. + # classic and napi cross-compile arm64 as well; the variants build for the host. targets: ${{ matrix.rust_targets }} - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - if: ${{ matrix.kind != 'napi-root' }} 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. @@ -143,7 +139,7 @@ jobs: # profile dir is target/release for a host build and target// 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 - if: ${{ matrix.kind == 'framework' }} + if: ${{ matrix.engine != 'napi' }} shell: pwsh run: | foreach ($root in @('target', (Join-Path $env:PACKAGE_DIR 'target'))) { @@ -179,14 +175,14 @@ jobs: # 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.kind == 'framework' && (matrix.engine == 'classic' || github.event_name == 'push' || inputs.rebuild_framework) }} + if: ${{ matrix.engine == 'classic' || (matrix.engine != 'napi' && (github.event_name == 'push' || 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 # Tag releases always rebuild, so a published variant never ships binaries built off CI. - if: ${{ matrix.kind == 'framework' && matrix.engine != 'classic' && (github.event_name == 'push' || inputs.rebuild_framework) }} + if: ${{ matrix.engine != 'classic' && matrix.engine != 'napi' && (github.event_name == 'push' || 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. @@ -220,34 +216,27 @@ jobs: shell: pwsh working-directory: ${{ matrix.dir }} run: npm version $env:NPM_VERSION --no-git-tag-version --allow-same-version - - name: Pin windows-napi binary packages - if: ${{ matrix.kind == 'napi-root' }} - shell: pwsh - working-directory: ${{ matrix.dir }} - run: | - $ErrorActionPreference = 'Stop' - $pkg = Get-Content package.json -Raw | ConvertFrom-Json - foreach ($dep in @($pkg.optionalDependencies.PSObject.Properties)) { - $dep.Value = $env:NPM_VERSION - } - $pkg | ConvertTo-Json -Depth 10 | Set-Content package.json -Encoding utf8NoBOM - Get-Content package.json - - name: Build ${{ matrix.napi_target }} binding - if: ${{ matrix.kind == 'napi-binding' }} + - name: Build windows-napi + if: ${{ matrix.engine == 'napi' }} shell: pwsh working-directory: windows-napi + env: + RUST_TARGETS: ${{ matrix.rust_targets }} run: | $ErrorActionPreference = 'Stop' npm ci if ($LASTEXITCODE -ne 0) { throw "npm ci failed" } - npx napi build --platform --release --target $env:NAPI_TARGET - if ($LASTEXITCODE -ne 0) { throw "napi build failed for $env:NAPI_TARGET" } - # The binary package directory is named after the platform part of the file name. - $node = "windows.$(Split-Path $env:PACKAGE_DIR -Leaf).node" - if (-not (Test-Path $node)) { throw "Build output missing: windows-napi/$node" } - Copy-Item $node (Join-Path $env:GITHUB_WORKSPACE $env:PACKAGE_DIR) + foreach ($target in $env:RUST_TARGETS.Split(',')) { + npx napi build --platform --release --target $target + if ($LASTEXITCODE -ne 0) { throw "napi build failed for $target" } + } + $nodes = @(Get-ChildItem windows.*.node) + if ($nodes.Count -ne $env:RUST_TARGETS.Split(',').Count) { + throw "Expected one .node per target, found: $($nodes.Name -join ', ')" + } + $nodes | Format-Table Name, Length - name: Build ${{ matrix.engine }} framework - if: ${{ matrix.kind == 'framework' }} + if: ${{ matrix.engine != 'napi' }} shell: pwsh run: | $ErrorActionPreference = 'Stop' diff --git a/packages/README.md b/packages/README.md index a4279cf..92c9159 100644 --- a/packages/README.md +++ b/packages/README.md @@ -142,14 +142,10 @@ for the engine (above), then `npm publish` the package (each is `os:["win32"], c classic package also ships arm64 + devtools). The CLI picks one, like android's `jsEngine` — and because the packages are interchangeable, switching is a dependency swap. -**`@nativescript/windows-napi` — the Node/Bun/Deno `.node`** (`../windows-napi`): -napi-rs flow, **implemented in `.github/workflows/windows-napi.yml`**: a build matrix per triple -(`x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`) runs `napi build --platform --release ---target ` → `windows..node` artifacts (the x64 job also runs the 366-check test -suite, non-blocking on server SKUs); pushing a **`windows-napi-v*` tag** triggers the publish job: -`napi artifacts` distributes the .node files into the committed `windows-napi/npm//` -sub-packages, `napi prepublish` (the `prepublishOnly` script) publishes those, then the root -package publishes with them as `optionalDependencies`. Needs the `NPM_TOKEN` repo secret. -`index.js`/`index.d.ts`/`package-lock.json` are generated but committed (the publish job doesn't -build). If arm64 proves unbuildable (rusty_v8/libffi cross), drop the triple from the workflow -matrix, `napi.triples`, `optionalDependencies`, and `npm/win32-arm64-msvc/`. +**`@nativescript/windows-napi` — the Node/Bun/Deno `.node`** (`../windows-napi`): released by +`.github/workflows/npm_release.yml` with the packages above (`engine: napi`, also part of `all`). +The build runs `napi build --platform --release --target ` for `x86_64-pc-windows-msvc` +and `aarch64-pc-windows-msvc` and packs both `windows..node` files into the one package; +the generated loader picks the one for the running CPU. `.github/workflows/windows-napi.yml` +builds both triples and runs the test suite on pull requests. `index.js`/`index.d.ts`/ +`package-lock.json` are generated but committed. diff --git a/scripts/resolve-release.mjs b/scripts/resolve-release.mjs index 8c36d65..c184cda 100644 --- a/scripts/resolve-release.mjs +++ b/scripts/resolve-release.mjs @@ -32,36 +32,18 @@ const PACKAGES = { jsc: { dir: "packages/windows-jsc", name: "@nativescript/windows-jsc" }, quickjs: { dir: "packages/windows-quickjs", name: "@nativescript/windows-quickjs" }, v8: { dir: "packages/windows-v8", name: "@nativescript/windows-v8" }, + // The Node-API addon: one package carrying a `windows..node` per target. + napi: { dir: "windows-napi", name: "@nativescript/windows-napi" }, }; - -// `napi`: the @nativescript/windows-napi addon, published napi-rs style as a JS root package plus -// one binary package per triple. Each is its own matrix entry; `kind` selects the build steps. -const NAPI = [ - { - key: "napi-x64", - dir: "windows-napi/npm/win32-x64-msvc", - name: "@nativescript/windows-napi-win32-x64-msvc", - kind: "napi-binding", - target: "x86_64-pc-windows-msvc", - }, - { - key: "napi-arm64", - dir: "windows-napi/npm/win32-arm64-msvc", - name: "@nativescript/windows-napi-win32-arm64-msvc", - kind: "napi-binding", - target: "aarch64-pc-windows-msvc", - }, - { key: "napi", dir: "windows-napi", name: "@nativescript/windows-napi", kind: "napi-root" }, -]; -const ENGINES = [...Object.keys(PACKAGES), "napi"]; +const ENGINES = Object.keys(PACKAGES); const USAGE = `Usage: node scripts/resolve-release.mjs [--version ] [--engine ] [--skip-arm64] --version release version to cut (leading "v" allowed); empty or omitted resolves from GITHUB_REF (v* tag) or falls back to a rolling "next" prerelease --engine all (default) | ${ENGINES.join(" | ")} - --skip-arm64 build classic x64-only and skip the windows-napi arm64 package (the engine - variants are x64-only regardless) + --skip-arm64 build classic and windows-napi x64-only (the engine variants are x64-only + regardless) -h, --help show this help`; let values; @@ -144,11 +126,10 @@ if (inputVersion) { // that is already released takes a patch bump, so the "next" channel never trails "latest". let base; for (const e of engines) { - const dir = e === "napi" ? "windows-napi" : PACKAGES[e].dir; - const manifest = path.join(repoRoot, dir, "package.json"); + const manifest = path.join(repoRoot, PACKAGES[e].dir, "package.json"); const declared = parseVersion( JSON.parse(fs.readFileSync(manifest, "utf8")).version, - `${dir}/package.json version` + `${PACKAGES[e].dir}/package.json version` ); if (!base || isNewer(declared, base)) base = declared; } @@ -160,51 +141,30 @@ if (inputVersion) { const tag = npmTag(parseVersion(version, "Release version"), version); // One matrix entry per package. Besides the engine key the workflow needs, for each package: -// kind framework (classic and engine variants) | napi-binding | napi-root // dir where package.json lives (npm version / npm pack run there) // npm_name the published name // tarball what `npm pack` emits ("@scope/name" -> "scope-name-.tgz") -// napi_target the Rust triple a napi-binding entry builds -// rust_targets extra Rust targets the toolchain must install (classic cross-compiles arm64) +// rust_targets Rust targets to install and build: classic and napi also build arm64, and +// napi builds one `.node` per target // rust_workspaces the Swatinem/rust-cache `workspaces` mapping; the engine packages are excluded // from the root workspace and build into their own target/ dir -const tarball = (name) => `${name.replace(/^@/, "").replace("/", "-")}-${version}.tgz`; -const include = []; -for (const e of engines) { - if (e === "napi") { - for (const n of NAPI) { - if (n.target === "aarch64-pc-windows-msvc" && values["skip-arm64"]) continue; - include.push({ - engine: n.key, - kind: n.kind, - dir: n.dir, - npm_name: n.name, - tarball: tarball(n.name), - napi_target: n.target || "", - rust_targets: n.target || "", - rust_workspaces: ". -> target", - }); - } - continue; - } - const pkg = PACKAGES[e]; - const classic = e === "classic"; - include.push({ - engine: e, - kind: "framework", - dir: pkg.dir, - npm_name: pkg.name, - tarball: tarball(pkg.name), - napi_target: "", - rust_targets: classic - ? values["skip-arm64"] - ? "x86_64-pc-windows-msvc" - : "x86_64-pc-windows-msvc,aarch64-pc-windows-msvc" - : "", - rust_workspaces: classic ? ". -> target" : `. -> target\n${pkg.dir} -> target`, - }); -} -const matrix = { include }; +const allArchs = values["skip-arm64"] + ? "x86_64-pc-windows-msvc" + : "x86_64-pc-windows-msvc,aarch64-pc-windows-msvc"; +const matrix = { + include: engines.map((e) => { + const pkg = PACKAGES[e]; + const inRootWorkspace = e === "classic" || e === "napi"; + return { + engine: e, + dir: pkg.dir, + npm_name: pkg.name, + tarball: `${pkg.name.replace(/^@/, "").replace("/", "-")}-${version}.tgz`, + rust_targets: inRootWorkspace ? allArchs : "", + rust_workspaces: inRootWorkspace ? ". -> target" : `. -> target\n${pkg.dir} -> target`, + }; + }), +}; if (process.env.GITHUB_OUTPUT) { fs.appendFileSync( @@ -213,5 +173,5 @@ if (process.env.GITHUB_OUTPUT) { ); } console.log( - `Resolved ${version} (dist-tag: ${tag}) for ${include.map((i) => i.npm_name).join(", ")}` + `Resolved ${version} (dist-tag: ${tag}) for ${engines.map((e) => PACKAGES[e].name).join(", ")}` ); diff --git a/windows-napi/.gitignore b/windows-napi/.gitignore index 93648bb..71c31c5 100644 --- a/windows-napi/.gitignore +++ b/windows-napi/.gitignore @@ -1,5 +1,5 @@ node_modules/ -# napi-rs binary outputs (CI builds per-triple; npm// receives them at publish time) +# napi-rs binary outputs (the release build packs one per triple) *.node artifacts/ # index.js / index.d.ts / package-lock.json are generated but COMMITTED: the publish CI job diff --git a/windows-napi/npm/win32-arm64-msvc/README.md b/windows-napi/npm/win32-arm64-msvc/README.md deleted file mode 100644 index 37a6d7d..0000000 --- a/windows-napi/npm/win32-arm64-msvc/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `@nativescript/windows-napi-win32-arm64-msvc` - -This is the **aarch64-pc-windows-msvc** binary for `@nativescript/windows-napi` diff --git a/windows-napi/npm/win32-arm64-msvc/package.json b/windows-napi/npm/win32-arm64-msvc/package.json deleted file mode 100644 index c03c5c3..0000000 --- a/windows-napi/npm/win32-arm64-msvc/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@nativescript/windows-napi-win32-arm64-msvc", - "version": "0.1.0", - "os": [ - "win32" - ], - "cpu": [ - "arm64" - ], - "main": "windows.win32-arm64-msvc.node", - "files": [ - "windows.win32-arm64-msvc.node" - ], - "description": "Node-API WinRT interop for Windows.", - "repository": { - "type": "git", - "url": "https://github.com/NativeScript/windows", - "directory": "windows-napi/npm/win32-arm64-msvc" - }, - "engines": { - "node": ">= 18" - } -} diff --git a/windows-napi/npm/win32-x64-msvc/README.md b/windows-napi/npm/win32-x64-msvc/README.md deleted file mode 100644 index 0859399..0000000 --- a/windows-napi/npm/win32-x64-msvc/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `@nativescript/windows-napi-win32-x64-msvc` - -This is the **x86_64-pc-windows-msvc** binary for `@nativescript/windows-napi` diff --git a/windows-napi/npm/win32-x64-msvc/package.json b/windows-napi/npm/win32-x64-msvc/package.json deleted file mode 100644 index 381d2a0..0000000 --- a/windows-napi/npm/win32-x64-msvc/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@nativescript/windows-napi-win32-x64-msvc", - "version": "0.1.0", - "os": [ - "win32" - ], - "cpu": [ - "x64" - ], - "main": "windows.win32-x64-msvc.node", - "files": [ - "windows.win32-x64-msvc.node" - ], - "description": "Node-API WinRT interop for Windows.", - "repository": { - "type": "git", - "url": "https://github.com/NativeScript/windows", - "directory": "windows-napi/npm/win32-x64-msvc" - }, - "engines": { - "node": ">= 18" - } -} diff --git a/windows-napi/package-lock.json b/windows-napi/package-lock.json index c483153..8be08dd 100644 --- a/windows-napi/package-lock.json +++ b/windows-napi/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@nativescript/windows", + "name": "@nativescript/windows-napi", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@nativescript/windows", + "name": "@nativescript/windows-napi", "version": "0.1.0", "os": [ "win32" diff --git a/windows-napi/package.json b/windows-napi/package.json index b5fa9c0..5878bdb 100644 --- a/windows-napi/package.json +++ b/windows-napi/package.json @@ -34,7 +34,6 @@ "scripts": { "build": "napi build --platform --release", "build:debug": "napi build --platform", - "artifacts": "napi artifacts", "test": "node test/run-tests.js" }, "repository": { @@ -44,9 +43,5 @@ }, "devDependencies": { "@napi-rs/cli": "^2.18.0" - }, - "optionalDependencies": { - "@nativescript/windows-napi-win32-x64-msvc": "0.1.0", - "@nativescript/windows-napi-win32-arm64-msvc": "0.1.0" } }