Release #19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release tag to publish, for example v1.0.0-rc3' | |
| required: true | |
| default: 'v1.0.0-rc3' | |
| workflow_call: | |
| inputs: | |
| version: | |
| description: 'Release tag to publish, for example v1.0.0-rc3' | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| jobs: | |
| test: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'push' && github.ref || inputs.version }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20.19.0' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v6 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run tests | |
| run: pnpm test | |
| - name: Build package | |
| run: pnpm build | |
| native: | |
| # Native OS/variant packages must be built and packed on their matching runner. | |
| # The publish job only publishes these tarballs; it must not repack native packages. | |
| name: ${{ matrix.package_dir }} | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - package_dir: native-linux-x64-gnu | |
| runner: ubuntu-24.04 | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: true | |
| - package_dir: native-linux-x64-musl | |
| runner: ubuntu-24.04 | |
| rust_target: x86_64-unknown-linux-musl | |
| install_musl: true | |
| smoke: false | |
| glibc_container: false | |
| - package_dir: native-linux-arm64-gnu | |
| runner: ubuntu-24.04-arm | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: true | |
| - package_dir: native-linux-arm64-musl | |
| runner: ubuntu-24.04-arm | |
| rust_target: aarch64-unknown-linux-musl | |
| install_musl: true | |
| smoke: false | |
| glibc_container: false | |
| - package_dir: native-darwin-x64 | |
| runner: macos-15-intel | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: false | |
| - package_dir: native-darwin-arm64 | |
| runner: macos-15 | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: false | |
| - package_dir: native-win32-x64-msvc | |
| runner: windows-2025 | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: false | |
| - package_dir: native-win32-arm64-msvc | |
| runner: windows-11-arm | |
| rust_target: '' | |
| install_musl: false | |
| smoke: true | |
| glibc_container: false | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event_name == 'push' && github.ref || inputs.version }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20.19.0' | |
| - name: Enable pnpm | |
| shell: bash | |
| run: | | |
| corepack enable | |
| corepack prepare pnpm@10.18.0 --activate | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Install musl toolchain | |
| if: matrix.install_musl | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y musl-tools | |
| - name: Ensure Rust target | |
| if: ${{ !matrix.glibc_container }} | |
| shell: bash | |
| run: | | |
| if ! command -v rustup >/dev/null 2>&1; then | |
| curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable | |
| echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" | |
| export PATH="$HOME/.cargo/bin:$PATH" | |
| fi | |
| rustup default stable | |
| if [ -n "${{ matrix.rust_target }}" ]; then | |
| rustup target add "${{ matrix.rust_target }}" | |
| fi | |
| cargo --version | |
| - name: Build native addon | |
| shell: bash | |
| run: | | |
| if [ "${{ matrix.glibc_container }}" = "true" ]; then | |
| container_name="stax-xml-glibc-${{ matrix.package_dir }}-${GITHUB_RUN_ID:-local}-$$" | |
| cleanup() { docker rm -f "$container_name" >/dev/null 2>&1 || true; } | |
| trap cleanup EXIT | |
| docker run --name "$container_name" -v "$PWD:/work" -w /work node:20-bullseye bash -lc ' | |
| set -euo pipefail | |
| apt-get update | |
| apt-get install -y --no-install-recommends ca-certificates curl build-essential pkg-config python3 git | |
| corepack enable | |
| corepack prepare pnpm@10.18.0 --activate | |
| curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable | |
| . "$HOME/.cargo/env" | |
| export CI=true | |
| pnpm --dir packages/native-aggregate run build:native | |
| ldd --version > /tmp/stax-xml-ldd-version.txt 2>&1 | |
| sed -n "1p" /tmp/stax-xml-ldd-version.txt | |
| ' | |
| docker cp "$container_name:/work/packages/native-aggregate/stax_xml_native_aggregate.node" packages/native-aggregate/stax_xml_native_aggregate.node | |
| cleanup | |
| trap - EXIT | |
| exit 0 | |
| fi | |
| if [[ "${{ matrix.rust_target }}" == *musl ]]; then | |
| export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=-crt-static" | |
| fi | |
| if [ -n "${{ matrix.rust_target }}" ]; then | |
| pnpm --dir packages/native-aggregate run build:native -- --target "${{ matrix.rust_target }}" | |
| else | |
| pnpm --dir packages/native-aggregate run build:native | |
| fi | |
| - name: Stage platform package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| pnpm --dir packages/native-aggregate run stage:platform -- "${{ matrix.package_dir }}" | |
| - name: Smoke platform package | |
| if: matrix.smoke | |
| run: node packages/native-aggregate/scripts/smoke-platform-package.mjs "packages/${{ matrix.package_dir }}" | |
| - name: Pack platform package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "artifacts/${{ matrix.package_dir }}" | |
| node packages/native-aggregate/scripts/assert-pack-contents.mjs "packages/${{ matrix.package_dir }}" | |
| npm pack --json "./packages/${{ matrix.package_dir }}" --pack-destination "$PWD/artifacts/${{ matrix.package_dir }}" | |
| - name: Upload platform package | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: native-package-${{ matrix.package_dir }} | |
| path: artifacts/${{ matrix.package_dir }}/*.tgz | |
| if-no-files-found: error | |
| wasm: | |
| # The wasm package is platform-neutral, so packaging on Ubuntu is sufficient. | |
| name: native-wasm32-wasi | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event_name == 'push' && github.ref || inputs.version }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20.19.0' | |
| - name: Enable pnpm | |
| shell: bash | |
| run: | | |
| corepack enable | |
| corepack prepare pnpm@10.18.0 --activate | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Ensure Rust wasm target | |
| shell: bash | |
| run: | | |
| rustup default stable | |
| rustup target add wasm32-wasip1-threads | |
| cargo --version | |
| - name: Build wasm addon | |
| run: pnpm --dir packages/native-aggregate run build:wasm | |
| - name: Pack wasm package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p artifacts/native-wasm32-wasi | |
| node packages/native-aggregate/scripts/assert-pack-contents.mjs packages/native-wasm32-wasi | |
| npm pack --json ./packages/native-wasm32-wasi --pack-destination "$PWD/artifacts/native-wasm32-wasi" | |
| - name: Upload wasm package | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: native-package-native-wasm32-wasi | |
| path: artifacts/native-wasm32-wasi/*.tgz | |
| if-no-files-found: error | |
| publish: | |
| runs-on: ubuntu-24.04 | |
| needs: | |
| - test | |
| - native | |
| - wasm | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.event_name == 'push' && github.ref || inputs.version }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Ensure npm supports trusted publishing | |
| run: npm install -g npm@^11.5.1 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v6 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile --ignore-scripts | |
| - name: Build facade package | |
| run: pnpm build | |
| - name: Resolve npm dist-tag | |
| id: npm-dist-tag | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="$(node -p "require('./packages/stax-xml/package.json').version")" | |
| if [[ "$VERSION" == *-* ]]; then | |
| echo "tag=next" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=latest" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Download native package tarballs | |
| uses: actions/download-artifact@v6 | |
| with: | |
| pattern: native-package-* | |
| path: artifacts/native-packages | |
| merge-multiple: true | |
| - name: Publish native package tarballs | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| for tgz in artifacts/native-packages/*.tgz; do | |
| package_json="$(tar -xOf "$tgz" package/package.json)" | |
| name="$(node -e "const fs=require('node:fs'); console.log(JSON.parse(fs.readFileSync(0, 'utf8')).name)" <<< "$package_json")" | |
| version="$(node -e "const fs=require('node:fs'); console.log(JSON.parse(fs.readFileSync(0, 'utf8')).version)" <<< "$package_json")" | |
| published_version="$(npm dist-tag ls "$name" 2>/dev/null | awk -v tag="${{ steps.npm-dist-tag.outputs.tag }}" '$1 == tag ":" { print $2 }')" | |
| if [ "$published_version" = "$version" ]; then | |
| echo "$name@$version is already published on dist-tag ${{ steps.npm-dist-tag.outputs.tag }}; skipping." | |
| else | |
| npm publish "$tgz" --access public --tag "${{ steps.npm-dist-tag.outputs.tag }}" | |
| fi | |
| done | |
| - name: Publish facade package | |
| working-directory: packages/stax-xml | |
| run: | | |
| VERSION="$(node -p "require('./package.json').version")" | |
| if npm view "stax-xml@$VERSION" version >/dev/null 2>&1; then | |
| echo "stax-xml@$VERSION is already published; skipping npm publish." | |
| else | |
| npm publish --access public --tag "${{ steps.npm-dist-tag.outputs.tag }}" | |
| fi | |
| - name: Smoke published native resolution | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| temp_dir="$(mktemp -d)" | |
| cleanup() { rm -rf "$temp_dir"; } | |
| trap cleanup EXIT | |
| cd "$temp_dir" | |
| npm init -y >/dev/null | |
| npm pkg set type=module >/dev/null | |
| for attempt in 1 2 3 4 5; do | |
| if npm install --omit=dev --ignore-scripts --loglevel=error "stax-xml@${{ steps.npm-dist-tag.outputs.version }}"; then | |
| break | |
| fi | |
| if [ "$attempt" = 5 ]; then | |
| exit 1 | |
| fi | |
| echo "npm install attempt $attempt/5 failed; retrying after registry propagation delay..." | |
| sleep 15 | |
| done | |
| node --input-type=module <<'NODE' | |
| import { readFile } from 'node:fs/promises'; | |
| import { join } from 'node:path'; | |
| import * as staxXml from 'stax-xml'; | |
| import { | |
| detectRuntimePlatform, | |
| getStaxXmlNativePackageName, | |
| resolveStaxXmlRuntimeBackend, | |
| } from 'stax-xml/runtime'; | |
| const cwd = process.cwd(); | |
| const facadePackageJsonPath = join(cwd, 'node_modules', 'stax-xml', 'package.json'); | |
| const facadePackageJson = JSON.parse(await readFile(facadePackageJsonPath, 'utf8')); | |
| const platform = detectRuntimePlatform(); | |
| const expectedPackage = getStaxXmlNativePackageName(platform); | |
| if (typeof staxXml.StaxXmlParserSync !== 'function') { | |
| throw new Error('stax-xml root import did not expose StaxXmlParserSync.'); | |
| } | |
| if (!expectedPackage) { | |
| throw new Error('No native package is expected for ' + platform.platform + '/' + platform.arch + '.'); | |
| } | |
| const expectedVersion = facadePackageJson.optionalDependencies?.[expectedPackage]; | |
| if (expectedVersion !== facadePackageJson.version) { | |
| throw new Error( | |
| 'Expected ' + expectedPackage + '@' + facadePackageJson.version + | |
| ' in stax-xml optionalDependencies, found ' + expectedVersion + '.', | |
| ); | |
| } | |
| const nativePackageJsonPath = join(cwd, 'node_modules', ...expectedPackage.split('/'), 'package.json'); | |
| const nativePackageJson = JSON.parse(await readFile(nativePackageJsonPath, 'utf8')); | |
| if (nativePackageJson.version !== facadePackageJson.version) { | |
| throw new Error( | |
| 'Installed native package version mismatch: ' + expectedPackage + '@' + nativePackageJson.version + | |
| ', facade ' + facadePackageJson.version + '.', | |
| ); | |
| } | |
| const backend = await resolveStaxXmlRuntimeBackend(); | |
| if (backend.kind !== 'native' || backend.packageName !== expectedPackage) { | |
| throw new Error( | |
| 'Expected native backend ' + expectedPackage + ', got ' + backend.kind + ' ' + | |
| (backend.packageName ?? '(none)') + '.', | |
| ); | |
| } | |
| const nativeModule = backend.module; | |
| if (typeof nativeModule?.parseAggregateStringUtf8 !== 'function') { | |
| throw new Error(expectedPackage + ' did not expose parseAggregateStringUtf8.'); | |
| } | |
| const aggregate = nativeModule.parseAggregateStringUtf8( | |
| '<root><item id="1">ok</item></root>', | |
| 'count-only', | |
| ); | |
| const eventCount = aggregate.eventCount ?? aggregate.event_count; | |
| if (!Number.isInteger(eventCount) || eventCount <= 0) { | |
| throw new Error('Unexpected native aggregate event count: ' + eventCount); | |
| } | |
| console.log(JSON.stringify({ | |
| facadeVersion: facadePackageJson.version, | |
| platform, | |
| expectedPackage, | |
| nativeVersion: nativePackageJson.version, | |
| backendKind: backend.kind, | |
| eventCount, | |
| }, null, 2)); | |
| NODE | |
| - name: Generate changelog | |
| if: ${{ !contains(steps.npm-dist-tag.outputs.version, '-') }} | |
| run: pnpm dlx changelogithub | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |