NPM Trusted Release #6
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: NPM Trusted Release | |
| # Publishes one or more NativeScript iOS npm packages | |
| # (@nativescript/ios-v8, @nativescript/ios-hermes, @nativescript/ios-jsc, | |
| # @nativescript/ios-quickjs, @nativescript/react-native) via npm trusted | |
| # publishing (OIDC). | |
| # | |
| # Each package must be configured on npmjs.com with a trusted publisher that | |
| # points at this repository + workflow + environment. With `engine: all`, the | |
| # workflow fans out across the four iOS engine packages via a matrix; use | |
| # `engine: react-native` to publish @nativescript/react-native. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| engine: | |
| description: "Package to release (engine package, react-native, or 'all' for every iOS engine)" | |
| required: true | |
| type: choice | |
| default: v8 | |
| options: | |
| - v8 | |
| - hermes | |
| - jsc | |
| - quickjs | |
| - react-native | |
| - all | |
| release-type: | |
| description: "Version bump (patch/minor/major publish to 'latest'; prerelease uses 'preid' as the dist-tag)" | |
| required: false | |
| type: choice | |
| default: prerelease | |
| options: | |
| - prerelease | |
| - patch | |
| - minor | |
| - major | |
| version: | |
| description: "Exact npm version to publish; overrides release-type/preid. Use a prerelease version for preview publishes, e.g. 9.0.0-preview.0" | |
| required: false | |
| type: string | |
| preid: | |
| description: "Prerelease identifier (used only when release-type=prerelease; also becomes the npm dist-tag, e.g. next | canary)" | |
| required: false | |
| type: string | |
| default: next | |
| dry-run: | |
| description: "Run release steps without making changes (no git push, no publish)" | |
| required: false | |
| type: boolean | |
| default: true | |
| concurrency: | |
| # Avoid overlapping publishes on the same ref/package selection. | |
| group: npm-trusted-release-${{ github.ref }}-${{ inputs.engine }} | |
| cancel-in-progress: false | |
| env: | |
| XCODE_VERSION: "26.2.0" | |
| jobs: | |
| matrix: | |
| name: Resolve package matrix | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| outputs: | |
| targets: ${{ steps.compute.outputs.targets }} | |
| steps: | |
| - name: Compute matrix | |
| id: compute | |
| env: | |
| ENGINE: ${{ inputs.engine }} | |
| run: | | |
| set -euo pipefail | |
| case "$ENGINE" in | |
| all) | |
| echo 'targets=["v8","hermes","jsc","quickjs"]' >> "$GITHUB_OUTPUT" | |
| ;; | |
| v8|hermes|jsc|quickjs|react-native) | |
| printf 'targets=["%s"]\n' "$ENGINE" >> "$GITHUB_OUTPUT" | |
| ;; | |
| *) | |
| echo "Unsupported engine: $ENGINE" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| build: | |
| name: Build ${{ matrix.target }} | |
| needs: matrix | |
| runs-on: macos-26 | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: ${{ fromJson(needs.matrix.outputs.targets) }} | |
| outputs: | |
| # Per-target outputs aren't natively supported with matrices, so each job | |
| # uploads its computed metadata alongside the tarball artifact. | |
| placeholder: noop | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | |
| with: | |
| fetch-depth: 0 | |
| submodules: recursive | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install Python | |
| uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 | |
| with: | |
| python-version: "3" | |
| - name: Install Dependencies | |
| run: | | |
| npm install | |
| python3 -m pip install --upgrade pip six | |
| if ! command -v ld64.lld >/dev/null; then | |
| brew list lld || brew install lld | |
| fi | |
| if ! command -v cmake >/dev/null; then | |
| brew list cmake || brew install cmake | |
| fi | |
| if [ ! -x /usr/local/bin/cmake ]; then | |
| sudo mkdir -p /usr/local/bin | |
| sudo ln -sf "$(command -v cmake)" /usr/local/bin/cmake | |
| fi | |
| - name: Bump version | |
| id: bump | |
| shell: bash | |
| env: | |
| RELEASE_TYPE: ${{ inputs.release-type }} | |
| PACKAGE_VERSION: ${{ inputs.version }} | |
| PREID: ${{ inputs.preid }} | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| release_type="$RELEASE_TYPE" | |
| package_version="$PACKAGE_VERSION" | |
| preid="$PREID" | |
| target="$TARGET" | |
| if [ "$target" = "react-native" ]; then | |
| pkg_dir="packages/react-native" | |
| package_name="@nativescript/react-native" | |
| tarball_basename="nativescript-react-native" | |
| npm_tag_target="react-native" | |
| else | |
| pkg_dir="packages/ios-${target}" | |
| package_name="@nativescript/ios-${target}" | |
| tarball_basename="nativescript-ios-${target}" | |
| npm_tag_target="ios-${target}" | |
| echo "IOS_VARIANT=ios-${target}" >> "$GITHUB_ENV" | |
| fi | |
| pushd "$pkg_dir" >/dev/null | |
| if [ -n "$package_version" ]; then | |
| npm version "$package_version" --no-git-tag-version >/dev/null | |
| elif [ "$release_type" = "prerelease" ]; then | |
| npm version prerelease --preid "$preid" --no-git-tag-version >/dev/null | |
| else | |
| npm version "$release_type" --no-git-tag-version >/dev/null | |
| fi | |
| NPM_VERSION=$(node -e "console.log(require('./package.json').version)") | |
| popd >/dev/null | |
| NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node ./scripts/get-npm-tag.js "$npm_tag_target") | |
| if [ -n "$package_version" ] && [ "$release_type" = "prerelease" ] && [ "$NPM_TAG" = "latest" ]; then | |
| echo "Exact prerelease publishes must include a prerelease identifier (for example 9.0.0-preview.0)." >&2 | |
| exit 1 | |
| fi | |
| echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" | |
| echo "PACKAGE_DIR=$pkg_dir" >> "$GITHUB_OUTPUT" | |
| echo "PACKAGE_NAME=$package_name" >> "$GITHUB_OUTPUT" | |
| echo "TARBALL_BASENAME=$tarball_basename" >> "$GITHUB_OUTPUT" | |
| echo "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG)" | |
| - name: Build iOS engine (--${{ matrix.target }}) | |
| if: ${{ matrix.target != 'react-native' }} | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: ./scripts/build_all_ios.sh "--${TARGET}" | |
| - name: Build @nativescript/react-native | |
| if: ${{ matrix.target == 'react-native' }} | |
| run: | | |
| ./scripts/build_all_react_native.sh | |
| ./scripts/build_react_native_turbomodule.sh | |
| - name: Record metadata | |
| shell: bash | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} | |
| PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} | |
| NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} | |
| NPM_TAG: ${{ steps.bump.outputs.NPM_TAG }} | |
| TARBALL_BASENAME: ${{ steps.bump.outputs.TARBALL_BASENAME }} | |
| run: | | |
| set -euo pipefail | |
| package_dir="$PACKAGE_DIR" | |
| tarball_file="${TARBALL_BASENAME}-${NPM_VERSION}.tgz" | |
| mkdir -p "$package_dir/dist" | |
| cat > "$package_dir/dist/release-meta.json" <<EOF | |
| { | |
| "target": "$TARGET", | |
| "package_dir": "$package_dir", | |
| "package_name": "$PACKAGE_NAME", | |
| "version": "$NPM_VERSION", | |
| "tag": "$NPM_TAG", | |
| "tarball": "$tarball_file" | |
| } | |
| EOF | |
| - name: Upload npm package artifact | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: npm-package-${{ matrix.target }} | |
| path: | | |
| ${{ steps.bump.outputs.PACKAGE_DIR }}/dist/${{ steps.bump.outputs.TARBALL_BASENAME }}-${{ steps.bump.outputs.NPM_VERSION }}.tgz | |
| ${{ steps.bump.outputs.PACKAGE_DIR }}/dist/release-meta.json | |
| - name: Upload dSYMs artifact | |
| if: ${{ matrix.target != 'react-native' }} | |
| uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | |
| with: | |
| name: NativeScript-dSYMs-${{ matrix.target }} | |
| path: dist/dSYMs | |
| publish: | |
| name: Publish ${{ matrix.target }} | |
| needs: | |
| - matrix | |
| - build | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: ${{ inputs.dry-run && 'npm-publish-dry-run' || 'npm-publish' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: ${{ fromJson(needs.matrix.outputs.targets) }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 | |
| with: | |
| egress-policy: audit | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| registry-url: "https://registry.npmjs.org" | |
| - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | |
| with: | |
| name: npm-package-${{ matrix.target }} | |
| path: npm-package/${{ matrix.target }} | |
| - name: Update npm (required for OIDC trusted publishing) | |
| run: | | |
| corepack enable npm | |
| corepack install -g npm@11.6.2 | |
| test "$(npm --version)" = "11.6.2" | |
| test "$(npx --version)" = "11.6.2" | |
| - name: Read release metadata | |
| id: meta | |
| shell: bash | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| meta="npm-package/${TARGET}/release-meta.json" | |
| if [ ! -f "$meta" ]; then | |
| echo "Missing release metadata at $meta" >&2 | |
| exit 1 | |
| fi | |
| NPM_VERSION=$(node -e "console.log(require('./$meta').version)") | |
| NPM_TAG=$(node -e "console.log(require('./$meta').tag)") | |
| PACKAGE_NAME=$(node -e "console.log(require('./$meta').package_name)") | |
| TARBALL=$(node -e "console.log(require('./$meta').tarball)") | |
| echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" | |
| echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT" | |
| echo "TARBALL=$TARBALL" >> "$GITHUB_OUTPUT" | |
| - name: Publish package (OIDC trusted publishing) | |
| if: ${{ vars.USE_NPM_TOKEN != 'true' }} | |
| shell: bash | |
| env: | |
| NPM_VERSION: ${{ steps.meta.outputs.NPM_VERSION }} | |
| NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} | |
| PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} | |
| TARBALL: ${{ steps.meta.outputs.TARBALL }} | |
| TARGET: ${{ matrix.target }} | |
| DRY_RUN: ${{ inputs.dry-run }} | |
| NODE_AUTH_TOKEN: "" | |
| run: | | |
| set -euo pipefail | |
| TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" | |
| PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) | |
| if [ "$DRY_RUN" = "true" ]; then | |
| PUBLISH_ARGS+=(--dry-run) | |
| fi | |
| echo "Publishing ${PACKAGE_NAME}@${NPM_VERSION} (tag: $NPM_TAG, dry-run: $DRY_RUN) via OIDC trusted publishing..." | |
| unset NODE_AUTH_TOKEN | |
| rm -f ~/.npmrc || true | |
| if [ -n "${NPM_CONFIG_USERCONFIG:-}" ]; then | |
| rm -f "$NPM_CONFIG_USERCONFIG" || true | |
| fi | |
| npm publish "${PUBLISH_ARGS[@]}" | |
| - name: Publish package (granular token fallback) | |
| if: ${{ vars.USE_NPM_TOKEN == 'true' }} | |
| shell: bash | |
| env: | |
| NPM_VERSION: ${{ steps.meta.outputs.NPM_VERSION }} | |
| NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} | |
| PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} | |
| TARBALL: ${{ steps.meta.outputs.TARBALL }} | |
| TARGET: ${{ matrix.target }} | |
| DRY_RUN: ${{ inputs.dry-run }} | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" | |
| PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) | |
| if [ "$DRY_RUN" = "true" ]; then | |
| PUBLISH_ARGS+=(--dry-run) | |
| fi | |
| echo "Publishing ${PACKAGE_NAME}@${NPM_VERSION} (tag: $NPM_TAG, dry-run: $DRY_RUN) via granular token..." | |
| npm publish "${PUBLISH_ARGS[@]}" | |
| summary: | |
| name: Release summary | |
| if: always() | |
| needs: | |
| - matrix | |
| - build | |
| - publish | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Print summary | |
| env: | |
| PACKAGE_SELECTION: ${{ inputs.engine }} | |
| RELEASE_TYPE: ${{ inputs.release-type }} | |
| PACKAGE_VERSION: ${{ inputs.version }} | |
| PREID: ${{ inputs.preid }} | |
| DRY_RUN: ${{ inputs.dry-run }} | |
| TARGETS: ${{ needs.matrix.outputs.targets }} | |
| BUILD_RESULT: ${{ needs.build.result }} | |
| PUBLISH_RESULT: ${{ needs.publish.result }} | |
| run: | | |
| echo "Package selection: $PACKAGE_SELECTION" | |
| echo "Release type: $RELEASE_TYPE" | |
| echo "Exact version: $PACKAGE_VERSION" | |
| echo "Preid: $PREID" | |
| echo "Dry run: $DRY_RUN" | |
| echo "Targets: $TARGETS" | |
| echo "Build result: $BUILD_RESULT" | |
| echo "Publish result: $PUBLISH_RESULT" |