name: Release on: push: branches: - main workflow_dispatch: concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: check-release: name: Check for new version runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read outputs: should_release: ${{ steps.check.outputs.should_release }} needs_github_release: ${{ steps.check.outputs.needs_github_release }} version: ${{ steps.check.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" registry-url: "https://registry.npmjs.org" - name: Compare package.json version to npm and check GitHub release id: check run: | LOCAL_VERSION=$(node -p "require('./packages/native-sdk/package.json').version") echo "Local version: $LOCAL_VERSION" NPM_VERSION=$(npm view @native-sdk/cli version 2>/dev/null || echo "0.0.0") echo "npm version: $NPM_VERSION" if [ "$LOCAL_VERSION" != "$NPM_VERSION" ]; then echo "Version changed: $NPM_VERSION -> $LOCAL_VERSION" echo "should_release=true" >> "$GITHUB_OUTPUT" echo "needs_github_release=true" >> "$GITHUB_OUTPUT" else echo "Version unchanged on npm, skipping publish" echo "should_release=false" >> "$GITHUB_OUTPUT" TAG="v$LOCAL_VERSION" EXISTING_ASSETS=$(gh release view "$TAG" --json assets --jq '.assets[].name' 2>/dev/null || true) missing=0 for asset in \ CHECKSUMS.txt \ native-sdk-darwin-arm64 \ native-sdk-darwin-x64 \ native-sdk-linux-arm64 \ native-sdk-linux-x64 \ native-sdk-linux-musl-arm64 \ native-sdk-linux-musl-x64 \ native-sdk-win32-arm64.exe \ native-sdk-win32-x64.exe do if ! printf '%s\n' "$EXISTING_ASSETS" | grep -Fx "$asset" >/dev/null; then echo "Missing release asset: $asset" missing=1 fi done if [ "$missing" -eq 0 ]; then echo "GitHub release $TAG exists with native assets" echo "needs_github_release=false" >> "$GITHUB_OUTPUT" else echo "GitHub release $TAG is missing native assets, will create/update it" echo "needs_github_release=true" >> "$GITHUB_OUTPUT" fi fi echo "version=$LOCAL_VERSION" >> "$GITHUB_OUTPUT" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} github-release: name: Create GitHub Release needs: check-release if: needs.check-release.outputs.needs_github_release == 'true' runs-on: macos-14 timeout-minutes: 10 permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Zig uses: mlugg/setup-zig@v2 with: version: 0.16.0 - name: Extract changelog entry run: | VERSION="${{ needs.check-release.outputs.version }}" awk '//{found=1; next} //{exit} found{print}' CHANGELOG.md > /tmp/release-notes.md LINES=$(wc -l < /tmp/release-notes.md | tr -d ' ') if [ "$LINES" -lt 2 ]; then echo "Error: No release notes found between and markers in CHANGELOG.md" exit 1 fi echo "Extracted release notes for $VERSION ($LINES lines)" - name: Build native release assets (all platforms) run: | # Cross-compiles the CLI for all eight platforms and writes the # flat release assets + CHECKSUMS.txt into zig-out/release/. bash packages/native-sdk/scripts/build-binaries.sh - name: Create GitHub Release run: | VERSION="${{ needs.check-release.outputs.version }}" TAG="v$VERSION" if gh release view "$TAG" &>/dev/null; then echo "Release $TAG already exists, updating assets" else echo "Creating release $TAG..." gh release create "$TAG" \ --title "$TAG" \ --notes-file /tmp/release-notes.md fi gh release upload "$TAG" \ zig-out/release/native-sdk-* \ zig-out/release/CHECKSUMS.txt \ --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish: name: Publish CLI to npm needs: [check-release, github-release] if: >- always() && needs.check-release.outputs.should_release == 'true' && (needs.github-release.result == 'success' || needs.github-release.result == 'skipped') runs-on: ubuntu-latest timeout-minutes: 15 environment: Release permissions: contents: read id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" registry-url: "https://registry.npmjs.org" # Publishing uses npm trusted publishing (OIDC): the job's id-token # permission lets npm mint short-lived credentials, so no npm token # secret exists anywhere in this repo. All nine packages — # @native-sdk/cli plus the eight @native-sdk/cli-* platform packages # under packages/native-sdk/npm/ — must each be configured on # npmjs.com with a GitHub Actions trusted publisher pointing at # repository vercel-labs/native, workflow release.yml, # environment Release. If a package is missing that configuration, # npm publish fails loudly with an OIDC authentication error before # anything is uploaded for that package. Trusted publishing requires # npm >= 11.5.1, satisfied by the npm bundled with Node 24. # @native-sdk/core (packages/core) joins that publish set the moment # its "private" flag drops (see the publish step below) — its # npmjs.com trusted-publisher configuration must exist BEFORE that # flip lands. - name: Check version sync run: npm --prefix packages/native-sdk run version:check - name: Check package scripts run: npm --prefix packages/native-sdk run scripts:check - name: Download release binaries run: | VERSION="${{ needs.check-release.outputs.version }}" mkdir -p /tmp/native-sdk-release gh release download "v$VERSION" \ --pattern 'native-sdk-*' \ --pattern 'CHECKSUMS.txt' \ --dir /tmp/native-sdk-release (cd /tmp/native-sdk-release && sha256sum -c CHECKSUMS.txt) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Stage binaries into the platform packages run: | # Release-asset name -> npm platform package (same table as # packages/native-sdk/scripts/build-binaries.sh). stage() { asset="$1"; key="$2"; ext="$3" mkdir -p "packages/native-sdk/npm/$key/bin" cp "/tmp/native-sdk-release/$asset" "packages/native-sdk/npm/$key/bin/native$ext" chmod 755 "packages/native-sdk/npm/$key/bin/native$ext" } stage native-sdk-darwin-arm64 darwin-arm64 "" stage native-sdk-darwin-x64 darwin-x64 "" stage native-sdk-linux-arm64 linux-arm64-gnu "" stage native-sdk-linux-x64 linux-x64-gnu "" stage native-sdk-linux-musl-arm64 linux-arm64-musl "" stage native-sdk-linux-musl-x64 linux-x64-musl "" stage native-sdk-win32-arm64.exe win32-arm64 ".exe" stage native-sdk-win32-x64.exe win32-x64 ".exe" - name: Publish to npm run: | VERSION="${{ needs.check-release.outputs.version }}" # Platform packages first, so the main package's # optionalDependencies pins are resolvable the moment it lands. # Re-runs skip anything already on the registry at this version. publish_dir() { dir="$1" name=$(node -p "require('./$dir/package.json').name") if npm view "$name@$VERSION" version >/dev/null 2>&1; then echo "$name@$VERSION already published, skipping" return 0 fi (cd "$dir" && npm publish --provenance --access public) } for dir in packages/native-sdk/npm/*/; do publish_dir "${dir%/}" done # @native-sdk/core (packages/core) rides the same version and # release gate as the CLI (sync-version.js stamps it; the # version check above refuses a half-bumped tree). # # ============================ LOUD FLIP REQUIREMENT ========== # packages/core/package.json keeps "private": true until the # 0.5.0 cut BY DESIGN, and that field IS the publish switch: # while private, the guard below skips the package (npm would # refuse to publish it anyway); the 0.5.0 change that drops # "private" makes this same step start publishing it with no # workflow edit. Before dropping the flag, configure the # @native-sdk/core trusted publisher on npmjs.com (see the note # above) or the first public release fails here. # ============================================================= if [ "$(node -p "require('./packages/core/package.json').private === true")" = "true" ]; then echo "@native-sdk/core is private (pre-0.5.0): skipping publish by design" else publish_dir packages/core fi publish_dir packages/native-sdk