fix: only marshal promise resolution when created on the runtime loop… #264
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
| on: | |
| push: | |
| branches: | |
| - main # -> prerelease published under the "next" dist-tag | |
| tags: | |
| - "v*" # -> release published under the "latest" dist-tag | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version to cut, e.g. 9.1.0 (creates tag v<version> and publishes 'latest'). Leave empty for a manual 'next' build." | |
| required: false | |
| default: "" | |
| env: | |
| NPM_TAG: "next" | |
| XCODE_VERSION: "^15.0" | |
| # Minimal default token permissions for every job. Jobs that need more declare it | |
| # locally (github-release: contents:write; publish: id-token:write). Cross-repo | |
| # writes to ios-spm use a GitHub App token, not GITHUB_TOKEN. | |
| permissions: | |
| contents: read | |
| # The runtime xcframeworks are no longer shipped inside the npm packages. Each | |
| # build publishes them as GitHub Release assets and points the SwiftPM manifest | |
| # (github.com/NativeScript/ios-spm) at them via binaryTarget(url:checksum:). The | |
| # slim npm package only carries the Xcode project template + metadata generator. | |
| # | |
| # Flow: setup → build (ios, visionos) → test → github-release (assets) → | |
| # spm-update (stamp + tag ios-spm) → publish (npm) → verify-spm | |
| # | |
| # The release half (github-release, spm-update, publish, verify-spm) is gated on | |
| # repo variable ENABLE_SPM_RELEASE. Until it is 'true' (and the ios-spm App | |
| # credentials are set), the workflow runs setup/build/test only and publishes NOTHING — so the | |
| # slim package can never ship before its matching ios-spm tag exists. Turn the | |
| # variable on as part of the cutover. | |
| 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: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 22 | |
| - name: Install deps for version scripts | |
| run: npm install --no-audit --no-fund | |
| - name: Compute version and tag | |
| id: out | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| if [ -n "$INPUT_VERSION" ]; then | |
| # manual release: cut tag v<input> and publish (latest unless prerelease) | |
| NPM_VERSION="${INPUT_VERSION#v}" | |
| elif [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then | |
| # tag push (v*): the tag is authoritative. Strip the leading 'v' and | |
| # assert it matches package.json so the release can't drift from its tag. | |
| NPM_VERSION="${GITHUB_REF#refs/tags/}" | |
| NPM_VERSION="${NPM_VERSION#v}" | |
| PKG_VERSION=$(node -e "console.log(require('./package.json').version);") | |
| if [ "$NPM_VERSION" != "$PKG_VERSION" ]; then | |
| echo "::error::Tag v$NPM_VERSION does not match package.json version $PKG_VERSION. Bump package.json before tagging." >&2 | |
| exit 1 | |
| fi | |
| else | |
| # branch push (main) or manual run without a version -> "next" prerelease | |
| NPM_VERSION=$(node ./scripts/get-next-version.js) | |
| fi | |
| NPM_TAG=$(NPM_VERSION=$NPM_VERSION node ./scripts/get-npm-tag.js) | |
| echo "NPM_VERSION=$NPM_VERSION" >> $GITHUB_OUTPUT | |
| echo "NPM_TAG=$NPM_TAG" >> $GITHUB_OUTPUT | |
| # Target matrix: ios always builds/publishes. visionos is built and | |
| # published ONLY for real releases (a v* tag push, or a manual dispatch | |
| # with a version), never for the rolling "next" channel that every push | |
| # to main produces. The discriminator is the resolved dist-tag: pushes | |
| # to main (and dispatch without a version) -> "next" -> ios only. | |
| if [ "$NPM_TAG" = "next" ]; then | |
| BUILD_MATRIX='{"include":[{"target":"ios","script":"build-ios"}]}' | |
| else | |
| BUILD_MATRIX='{"include":[{"target":"ios","script":"build-ios"},{"target":"visionos","script":"build-vision"}]}' | |
| fi | |
| echo "BUILD_MATRIX=$BUILD_MATRIX" >> $GITHUB_OUTPUT | |
| echo "Resolved $NPM_VERSION (tag: $NPM_TAG); build targets: $BUILD_MATRIX" | |
| build: | |
| name: Build ${{ matrix.target }} | |
| runs-on: macos-14 | |
| needs: setup | |
| strategy: | |
| fail-fast: false | |
| # Computed in `setup`: ios always; visionos only for real releases | |
| # (npm_tag != "next"), never for the rolling "next" channel. Each entry | |
| # carries `target` (package identity, @nativescript/<target>) and `script` | |
| # (the npm build script; note the vision script is build-vision). | |
| matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }} | |
| env: | |
| NPM_VERSION: ${{ needs.setup.outputs.npm_version }} | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{env.XCODE_VERSION}} | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| persist-credentials: false # disable to prevent leaking credentials to build scripts | |
| - name: LLVM cache | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ./llvm | |
| key: ${{ hashFiles('download_llvm.sh') }} | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 22 | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3" | |
| - name: Install Dependencies | |
| run: | | |
| npm install | |
| python3 -m pip install --upgrade pip six | |
| # Ensure CMake is available without conflicting with pinned Homebrew formula | |
| if ! command -v cmake >/dev/null; then | |
| brew list cmake || brew install cmake | |
| fi | |
| # Some scripts expect cmake at /usr/local/bin; create a shim if needed | |
| 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: Set package version | |
| run: npm version $NPM_VERSION --no-git-tag-version --allow-same-version | |
| - name: Build (${{ matrix.target }}) | |
| run: npm run ${{ matrix.script }} | |
| - name: Upload npm package artifact | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: npm-package-${{ matrix.target }} | |
| path: dist/nativescript-*-${{ env.NPM_VERSION }}.tgz | |
| - name: Upload SwiftPM artifacts (xcframework zips + checksums) | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: spm-artifacts-${{ matrix.target }} | |
| path: dist/artifacts/* | |
| - name: Upload dSYMs artifact | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: dSYMs-${{ matrix.target }} | |
| path: dist/dSYMs | |
| test: | |
| name: Test | |
| runs-on: macos-14 | |
| needs: build | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{env.XCODE_VERSION}} | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| submodules: true | |
| persist-credentials: false # disable to prevent leaking credentials to build scripts | |
| - name: LLVM cache | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ./llvm | |
| key: ${{ hashFiles('download_llvm.sh') }} | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 22 | |
| - name: Install Python | |
| uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 | |
| with: | |
| python-version: "3" | |
| - name: Install Dependencies | |
| run: | | |
| npm install | |
| python3 -m pip install --upgrade pip six | |
| # Ensure CMake is available without conflicting with pinned Homebrew formula | |
| if ! command -v cmake >/dev/null; then | |
| brew list cmake || brew install cmake | |
| fi | |
| # Some scripts expect cmake at /usr/local/bin; create a shim if needed | |
| if [ ! -x /usr/local/bin/cmake ]; then | |
| sudo mkdir -p /usr/local/bin | |
| sudo ln -sf "$(command -v cmake)" /usr/local/bin/cmake | |
| fi | |
| brew install chargepoint/xcparse/xcparse | |
| npm install -g @edusperoni/junit-cli-report-viewer verify-junit-xml | |
| - name: Prepare | |
| run: npm run setup-ci | |
| - name: Prepare test folder | |
| run: | | |
| mkdir -p dist-test | |
| echo TEST_FOLDER=$(pwd)/dist-test >> $GITHUB_ENV | |
| - name: Xcode Tests | |
| uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 | |
| # try to run the tests with xcpretty. If it fails then try again without xcpretty twice for better log output | |
| # the xcode tests are a bit flaky and they should never fail on this step, as this step only collects the JS test results as junit xml | |
| with: | |
| # Build (~10m) + the runtime suite's report-wait (now up to 10m, see | |
| # TestRunnerTests.swift) need more than 20m headroom per attempt. | |
| timeout_minutes: 40 | |
| max_attempts: 2 | |
| command: set -o pipefail && xcodebuild -project v8ios.xcodeproj -scheme TestRunner -resultBundlePath $TEST_FOLDER/test_results -destination platform\=iOS\ Simulator,OS\=17.2,name\=iPhone\ 15\ Pro\ Max build test | xcpretty | |
| on_retry_command: rm -rf $TEST_FOLDER/test_results* && xcrun simctl shutdown all | |
| new_command_on_retry: xcodebuild -project v8ios.xcodeproj -scheme TestRunner -resultBundlePath $TEST_FOLDER/test_results -destination platform\=iOS\ Simulator,OS\=17.2,name\=iPhone\ 15\ Pro\ Max build test | |
| - name: Validate Test Results | |
| run: | | |
| xcparse attachments $TEST_FOLDER/test_results.xcresult $TEST_FOLDER/test-out | |
| find $TEST_FOLDER/test-out -name "*junit*.xml" -maxdepth 1 -print0 | xargs -n 1 -0 npx junit-cli-report-viewer | |
| find $TEST_FOLDER/test-out -name "*junit*.xml" -maxdepth 1 -print0 | xargs -n 1 -0 npx verify-junit-xml | |
| - name: Archive Test Result Data | |
| if: always() | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 | |
| with: | |
| name: test-results | |
| path: ${{env.TEST_FOLDER}}/test_results.xcresult | |
| # Publish the xcframework zips + dSYMs as a GitHub Release asset for EVERY | |
| # version (prerelease unless 'latest'), because the SwiftPM binaryTarget url | |
| # must resolve for `next`/`pr` consumers too — not only tagged releases. | |
| github-release: | |
| name: GitHub Release (SPM assets) | |
| runs-on: ubuntu-latest | |
| # Master switch for the SPM release flow. When unset, the workflow runs | |
| # setup/build/test only — nothing is released or published, so landing this | |
| # on main before the token is configured is a no-op for consumers. | |
| if: ${{ vars.ENABLE_SPM_RELEASE == 'true' }} | |
| permissions: | |
| contents: write | |
| needs: | |
| - setup | |
| - build | |
| - test | |
| env: | |
| NPM_VERSION: ${{ needs.setup.outputs.npm_version }} | |
| NPM_TAG: ${{ needs.setup.outputs.npm_tag }} | |
| 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: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 22 | |
| - name: Setup | |
| run: npm install --no-audit --no-fund | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: spm-artifacts-* | |
| path: spm-artifacts | |
| merge-multiple: true | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: dSYMs-* | |
| path: dist/dSYMs | |
| merge-multiple: true | |
| - name: Zip dSYMs | |
| working-directory: dist/dSYMs | |
| run: find . -maxdepth 1 -name '*.dSYM' -print | xargs -I@ zip -r @.zip @ || true | |
| - name: Partial Changelog | |
| run: npx conventional-changelog -p angular -r2 > body.md | |
| - name: Create / update release with SPM artifacts | |
| uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 | |
| with: | |
| tag: "v${{ env.NPM_VERSION }}" | |
| name: "v${{ env.NPM_VERSION }}" | |
| commit: ${{ github.sha }} | |
| artifacts: "spm-artifacts/*.xcframework.zip,dist/dSYMs/*.zip" | |
| bodyFile: "body.md" | |
| prerelease: ${{ needs.setup.outputs.npm_tag != 'latest' }} | |
| allowUpdates: true | |
| # Stamp + tag github.com/NativeScript/ios-spm to point at this release's assets. | |
| # Cross-repo write uses a short-lived GitHub App installation token, minted per | |
| # run and scoped to ios-spm only (no long-lived PAT to rotate). Enable by setting | |
| # repo variable ENABLE_SPM_RELEASE=true plus the App credentials | |
| # (variable APP_CLIENT_ID + secret APP_PRIVATE_KEY); the App must be installed on | |
| # NativeScript/ios-spm with Contents: Read and write. | |
| spm-update: | |
| name: Update ios-spm manifest | |
| runs-on: ubuntu-latest | |
| if: ${{ vars.ENABLE_SPM_RELEASE == 'true' }} | |
| needs: | |
| - setup | |
| - build | |
| - test | |
| - github-release | |
| env: | |
| NPM_VERSION: ${{ needs.setup.outputs.npm_version }} | |
| NPM_TAG: ${{ needs.setup.outputs.npm_tag }} | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Mint ios-spm App token (short-lived, scoped to ios-spm) | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ vars.APP_CLIENT_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| owner: NativeScript | |
| repositories: ios-spm | |
| permission-contents: write | |
| - name: Checkout ios (for stamping script) | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| path: ios | |
| persist-credentials: false | |
| - name: Checkout ios-spm | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| repository: NativeScript/ios-spm | |
| token: ${{ steps.app-token.outputs.token }} | |
| path: ios-spm | |
| persist-credentials: false | |
| - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 22 | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: spm-artifacts-* | |
| path: spm-artifacts | |
| merge-multiple: true | |
| - name: Stamp Package.swift | |
| run: | | |
| STRICT="" | |
| if [ "$NPM_TAG" = "latest" ]; then STRICT="--strict"; fi | |
| CHECKSUM_ARGS="" | |
| for f in spm-artifacts/checksums-*.env; do | |
| [ -f "$f" ] && CHECKSUM_ARGS="$CHECKSUM_ARGS --checksums $f" | |
| done | |
| echo "Using checksum files:$CHECKSUM_ARGS" | |
| if [ -z "$CHECKSUM_ARGS" ]; then echo "No checksum files found" >&2; exit 1; fi | |
| node ios/scripts/stamp-spm-release.mjs \ | |
| --package ios-spm/Package.swift \ | |
| --version "$NPM_VERSION" \ | |
| $CHECKSUM_ARGS $STRICT | |
| - name: Commit and tag ios-spm | |
| working-directory: ios-spm | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| git config user.name "NativeScript Bot" | |
| git config user.email "oss@nativescript.org" | |
| # persist-credentials:false keeps the token out of .git/config; push to a | |
| # token-authenticated URL instead (the App token is masked in logs). | |
| REMOTE="https://x-access-token:${GH_TOKEN}@github.com/NativeScript/ios-spm.git" | |
| git add Package.swift | |
| git commit -m "release: $NPM_VERSION" || echo "no changes to commit" | |
| git tag -f "$NPM_VERSION" | |
| git push "$REMOTE" HEAD:main | |
| git push -f "$REMOTE" "refs/tags/$NPM_VERSION" | |
| publish: | |
| name: Publish ${{ matrix.target }} | |
| runs-on: ubuntu-latest | |
| environment: npm-publish | |
| # Gated + ordered after spm-update so a slim package is never published before | |
| # its matching ios-spm tag exists (which would make it unresolvable). | |
| if: ${{ vars.ENABLE_SPM_RELEASE == 'true' }} | |
| needs: | |
| - setup | |
| - build | |
| - test | |
| - spm-update | |
| permissions: | |
| contents: read | |
| id-token: write | |
| strategy: | |
| fail-fast: false | |
| # Same targets as the build matrix (computed in `setup`): visionos is | |
| # published only for real releases, never for the "next" channel. The | |
| # unused `script` key on each entry is harmless here. | |
| matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }} | |
| env: | |
| NPM_VERSION: ${{ needs.setup.outputs.npm_version }} | |
| NPM_TAG: ${{ needs.setup.outputs.npm_tag }} | |
| 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 | |
| registry-url: "https://registry.npmjs.org" | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: npm-package-${{ matrix.target }} | |
| 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: Resolve package name | |
| run: | | |
| if [ "${{ matrix.target }}" = "ios" ]; then echo "PKG=ios" >> $GITHUB_ENV; else echo "PKG=visionos" >> $GITHUB_ENV; fi | |
| - name: Publish package (OIDC trusted publishing) | |
| if: ${{ vars.USE_NPM_TOKEN != 'true' }} | |
| run: | | |
| echo "Publishing @nativescript/$PKG@$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/nativescript-${PKG}-${NPM_VERSION}.tgz --tag $NPM_TAG --access public --provenance | |
| env: | |
| NODE_AUTH_TOKEN: "" | |
| - name: Publish package (granular token) | |
| if: ${{ vars.USE_NPM_TOKEN == 'true' }} | |
| run: | | |
| echo "Publishing @nativescript/$PKG@$NPM_VERSION to NPM with tag $NPM_TAG via granular token..." | |
| npm publish ./dist/nativescript-${PKG}-${NPM_VERSION}.tgz --tag $NPM_TAG --access public --provenance | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} | |
| # Post-publish smoke test: resolve ios-spm at the released tag and verify the | |
| # binary artifact downloads + checksum-validate against the real Release. | |
| # | |
| # Channel-aware, mirroring the build/publish matrix: `next` builds iOS only, so | |
| # its Release has no visionos assets. `swift package resolve` eagerly downloads | |
| # EVERY binaryTarget in a resolved package (not just the ones a referenced | |
| # product needs), so resolving the full ios-spm manifest on a `next` version | |
| # would try to fetch the visionos zips and 404. For `next` we therefore probe | |
| # an iOS-only package; only real releases (v* tag / manual version) resolve the | |
| # full manifest and thus also verify the visionos artifacts. | |
| verify-spm: | |
| name: Verify SPM resolution | |
| runs-on: macos-14 | |
| if: ${{ vars.ENABLE_SPM_RELEASE == 'true' }} | |
| needs: | |
| - setup | |
| - spm-update | |
| env: | |
| NPM_VERSION: ${{ needs.setup.outputs.npm_version }} | |
| NPM_TAG: ${{ needs.setup.outputs.npm_tag }} | |
| 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 | |
| - name: Assert ios-spm manifest pins this release | |
| run: | | |
| # Guard against the manifest pointing at a STALE release — e.g. a stamp | |
| # that no-ops and leaves a prior version's nsVersion + artifact | |
| # URLs/checksums in place. Those still resolve and checksum-pass (they're | |
| # internally consistent), so `swift package resolve` below won't catch | |
| # it; this explicit version assertion does. | |
| url="https://raw.githubusercontent.com/NativeScript/ios-spm/${NPM_VERSION}/Package.swift" | |
| manifest="$(curl -fsSL "$url")" | |
| if ! printf '%s\n' "$manifest" | grep -q "let nsVersion = \"${NPM_VERSION}\""; then | |
| echo "::error::ios-spm@${NPM_VERSION} does not pin nsVersion=\"${NPM_VERSION}\" (stale stamp?). Found:" >&2 | |
| printf '%s\n' "$manifest" | grep -n "nsVersion" >&2 || true | |
| exit 1 | |
| fi | |
| echo "OK: ios-spm@${NPM_VERSION} pins nsVersion=\"${NPM_VERSION}\"" | |
| - name: Generate a probe package that depends on ios-spm (real releases) | |
| # Real release (v* tag / manual version): the Release carries every asset, | |
| # so probe the full ios-spm manifest. `swift package resolve` eagerly | |
| # downloads all binaryTargets, exercising the iOS AND visionos artifacts. | |
| if: ${{ needs.setup.outputs.npm_tag != 'next' }} | |
| run: | | |
| node -e ' | |
| const fs = require("fs"); | |
| const v = process.env.NPM_VERSION; | |
| fs.mkdirSync("spmverify/Sources/Probe", { recursive: true }); | |
| fs.writeFileSync("spmverify/Package.swift", | |
| "// swift-tools-version: 5.10\n" + | |
| "import PackageDescription\n" + | |
| "let package = Package(\n" + | |
| " name: \"Probe\",\n" + | |
| " platforms: [.iOS(.v13)],\n" + | |
| " dependencies: [.package(url: \"https://github.com/NativeScript/ios-spm.git\", exact: \"" + v + "\")],\n" + | |
| " targets: [.target(name: \"Probe\", dependencies: [.product(name: \"NativeScript\", package: \"ios-spm\")])]\n" + | |
| ")\n"); | |
| fs.writeFileSync("spmverify/Sources/Probe/Probe.swift", "// probe\n"); | |
| ' | |
| - name: Generate an iOS-only probe package (next channel) | |
| # The "next" Release has no visionos assets (build matrix is iOS-only for | |
| # next), and `swift package resolve` would eagerly try to fetch every | |
| # binaryTarget in the full ios-spm manifest -> 404 on the visionos zips. | |
| # So probe an iOS-only package whose binaryTargets are the iOS artifacts | |
| # from the stamped manifest (real Release URLs + real checksums): resolve | |
| # still downloads and checksum-verifies exactly what a next consumer gets, | |
| # and never touches visionos. | |
| if: ${{ needs.setup.outputs.npm_tag == 'next' }} | |
| run: | | |
| url="https://raw.githubusercontent.com/NativeScript/ios-spm/${NPM_VERSION}/Package.swift" | |
| curl -fsSL "$url" -o manifest.swift | |
| node -e ' | |
| const fs = require("fs"); | |
| const v = process.env.NPM_VERSION; | |
| const body = fs.readFileSync("manifest.swift", "utf8"); | |
| // Pull the checksum out of the binaryTarget whose url ends in the given | |
| // artifact zip. The url is a Swift interpolation ("\(base)/<zip>"), so | |
| // match up to the closing quote; anchoring on <zip>" keeps the iOS | |
| // NativeScript slot from matching the visionos one (…visionos.xcframework.zip). | |
| const ck = (artifact) => { | |
| const re = new RegExp("url:\\s*\"[^\"]*" + artifact.replace(/\./g, "\\.") + "\"\\s*,\\s*checksum:\\s*\"([0-9a-f]{64})\""); | |
| const m = body.match(re); | |
| if (!m) { console.error("No iOS checksum for " + artifact + " in ios-spm@" + v); process.exit(1); } | |
| return m[1]; | |
| }; | |
| const nsCk = ck("NativeScript.xcframework.zip"); | |
| const tkCk = ck("TKLiveSync.xcframework.zip"); | |
| fs.mkdirSync("spmverify/Sources/Probe", { recursive: true }); | |
| fs.writeFileSync("spmverify/Package.swift", | |
| "// swift-tools-version: 5.10\n" + | |
| "import PackageDescription\n" + | |
| "let base = \"https://github.com/NativeScript/ios/releases/download/v" + v + "\"\n" + | |
| "let package = Package(\n" + | |
| " name: \"Probe\",\n" + | |
| " platforms: [.iOS(.v13)],\n" + | |
| " targets: [\n" + | |
| " .binaryTarget(name: \"NativeScript\", url: \"\\(base)/NativeScript.xcframework.zip\", checksum: \"" + nsCk + "\"),\n" + | |
| " .binaryTarget(name: \"TKLiveSync\", url: \"\\(base)/TKLiveSync.xcframework.zip\", checksum: \"" + tkCk + "\"),\n" + | |
| " .target(name: \"Probe\", dependencies: [\"NativeScript\", \"TKLiveSync\"])\n" + | |
| " ]\n" + | |
| ")\n"); | |
| fs.writeFileSync("spmverify/Sources/Probe/Probe.swift", "// probe\n"); | |
| ' | |
| - name: Resolve (downloads the xcframework zips and verifies their checksums) | |
| working-directory: spmverify | |
| run: | | |
| # Resolution fetches each binaryTarget's zip and verifies its SHA-256 | |
| # against the stamped checksum; a mismatch or a missing release asset | |
| # fails the release here. For next this covers the iOS artifacts only; | |
| # real releases resolve the full manifest and also cover visionos. | |
| swift package resolve | |
| echo "ios-spm@$NPM_VERSION resolved and checksum-verified." |