name: Publish to npm on: push: tags: - "v*" pull_request: types: [closed] branches: [main] jobs: publish: name: Publish runs-on: ubuntu-latest timeout-minutes: 10 environment: npm-publish permissions: contents: write id-token: write # Run on tag push OR when a release/* PR is merged if: >- github.event_name == 'push' || (github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v')) steps: - uses: actions/checkout@v4 - name: Resolve version id: version run: | if [ "${{ github.event_name }}" = "push" ]; then echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" else BRANCH="${{ github.event.pull_request.head.ref }}" echo "version=${BRANCH#release/v}" >> "$GITHUB_OUTPUT" fi - name: Create release tag if: github.event_name == 'pull_request' run: | git tag "v${{ steps.version.outputs.version }}" git push origin "v${{ steps.version.outputs.version }}" - uses: oven-sh/setup-bun@v2 - uses: actions/setup-node@v4 with: node-version: 24 registry-url: "https://registry.npmjs.org" - run: corepack enable - run: corepack prepare pnpm@10.17.1 --activate - run: bun install --frozen-lockfile - run: bun run build - run: bun run verify:packed-manifests - name: Publish packages env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | VERSION="${{ steps.version.outputs.version }}" FAILED=0 publish_pkg() { local filter="$1" local name="$2" # Check if this version is already published if npm view "${name}@${VERSION}" version >/dev/null 2>&1; then echo "⏭️ ${name}@${VERSION} already published — skipping" return 0 fi echo "📦 Publishing ${name}@${VERSION}..." if pnpm --filter "$filter" publish --access public --no-git-checks; then echo "✅ ${name}@${VERSION} published" else echo "❌ ${name}@${VERSION} failed to publish" FAILED=1 fi } publish_pkg "@hyperframes/core" "@hyperframes/core" publish_pkg "@hyperframes/engine" "@hyperframes/engine" publish_pkg "@hyperframes/producer" "@hyperframes/producer" publish_pkg "@hyperframes/studio" "@hyperframes/studio" # CLI is @hyperframes/cli in the monorepo but published as unscoped "hyperframes" on npm. # Rewrite the name in package.json before publishing, then use npm publish directly # since pnpm --filter won't match the rewritten name. if npm view "hyperframes@${VERSION}" version >/dev/null 2>&1; then echo "⏭️ hyperframes@${VERSION} already published — skipping" else node -e " const fs = require('fs'); const p = 'packages/cli/package.json'; const pkg = JSON.parse(fs.readFileSync(p, 'utf8')); pkg.name = 'hyperframes'; fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n'); " echo "📦 Publishing hyperframes@${VERSION}..." if (cd packages/cli && npm publish --access public); then echo "✅ hyperframes@${VERSION} published" else echo "❌ hyperframes@${VERSION} failed to publish" FAILED=1 fi fi if [ "$FAILED" -ne 0 ]; then echo "::error::One or more packages failed to publish" exit 1 fi - name: Create GitHub Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ steps.version.outputs.version }}" # Skip if release already exists (idempotent re-runs) if gh release view "v${VERSION}" --repo "${{ github.repository }}" >/dev/null 2>&1; then echo "Release v${VERSION} already exists — skipping" else gh release create "v${VERSION}" \ --repo "${{ github.repository }}" \ --title "v${VERSION}" \ --generate-notes fi