Release JS/TS SDK #96
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: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version bump type" | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| - prerelease | |
| prerelease_type: | |
| description: 'Pre-release type (only used if version is "prerelease")' | |
| type: choice | |
| default: "" | |
| options: | |
| - "" | |
| - alpha | |
| - beta | |
| - rc | |
| dry_run: | |
| description: "Dry run (skip publish and git push)" | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| id-token: write # Required for npm provenance via OIDC | |
| pull-requests: write | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify branch | |
| run: | | |
| if [ "${{ github.ref }}" != "refs/heads/main" ]; then | |
| echo "❌ Error: Releases can only be triggered from main branch" | |
| echo "Current ref: ${{ github.ref }}" | |
| exit 1 | |
| fi | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v3 | |
| with: | |
| version: 9.15.0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Configure Git | |
| run: | | |
| git config user.name "langfuse-bot" | |
| git config user.email "langfuse-bot@langfuse.com" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Verify lockfile integrity | |
| run: | | |
| echo "Verifying pnpm lockfile has not been tampered with..." | |
| git diff --exit-code pnpm-lock.yaml || { | |
| echo "❌ Error: pnpm-lock.yaml was modified during install" | |
| exit 1 | |
| } | |
| - name: Determine release parameters | |
| id: release-params | |
| run: | | |
| if [ "${{ inputs.version }}" = "prerelease" ]; then | |
| if [ -z "${{ inputs.prerelease_type }}" ]; then | |
| echo "❌ Error: prerelease_type must be specified when version is 'prerelease'" | |
| exit 1 | |
| fi | |
| echo "prerelease_flag=--preRelease=${{ inputs.prerelease_type }}" >> $GITHUB_OUTPUT | |
| echo "tag=${{ inputs.prerelease_type }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "prerelease_flag=${{ inputs.version }}" >> $GITHUB_OUTPUT | |
| echo "tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Run release-it (version bump and git operations) | |
| if: inputs.dry_run == false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| release-it ${{ steps.release-params.outputs.prerelease_flag }} --ci --config .release-it.ci.json | |
| - name: Run release-it (dry run) | |
| if: inputs.dry_run == true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| echo "🧪 Running in DRY RUN mode - no changes will be pushed" | |
| release-it ${{ steps.release-params.outputs.prerelease_flag }} --ci --config .release-it.ci.json --dry-run | |
| - name: Get version | |
| id: version | |
| run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: Verify build artifacts | |
| run: | | |
| echo "Verifying all packages have dist folders..." | |
| for pkg in packages/*/package.json; do | |
| pkg_dir=$(dirname $pkg) | |
| pkg_name=$(basename $pkg_dir) | |
| if [ ! -d "$pkg_dir/dist" ]; then | |
| echo "❌ Error: Missing dist folder for $pkg_name" | |
| exit 1 | |
| fi | |
| echo "✅ $pkg_name: dist folder exists" | |
| done | |
| echo "Checking for suspicious files in dist folders..." | |
| if find packages/*/dist -type f \( -name "*.sh" -o -name "*.exe" -o -name "*.bat" -o -name "*.cmd" \) | grep -q .; then | |
| echo "❌ Error: Found suspicious executable files in dist folders" | |
| find packages/*/dist -type f \( -name "*.sh" -o -name "*.exe" -o -name "*.bat" -o -name "*.cmd" \) | |
| exit 1 | |
| fi | |
| echo "✅ No suspicious files found" | |
| echo "Verifying package sizes are reasonable..." | |
| for dist_dir in packages/*/dist; do | |
| size=$(du -sh "$dist_dir" | cut -f1) | |
| echo " $(basename $(dirname $dist_dir)): $size" | |
| done | |
| - name: Publish to npm with provenance | |
| id: npm_publish | |
| if: inputs.dry_run == false | |
| run: | | |
| echo "Publishing packages to npm with provenance attestations..." | |
| pnpm -r publish --access public --no-git-checks --tag ${{ steps.release-params.outputs.tag }} --provenance | |
| # NOTE: No NODE_AUTH_TOKEN needed - npm CLI auto-detects OIDC environment for Trusted Publishing | |
| - name: Dry run - Skip npm publish | |
| if: inputs.dry_run == true | |
| run: | | |
| echo "🧪 DRY RUN: Would publish packages with tag '${{ steps.release-params.outputs.tag }}'" | |
| echo "Packages that would be published:" | |
| pnpm -r exec pwd | |
| - name: Create release artifacts archive | |
| if: inputs.dry_run == false | |
| run: | | |
| mkdir -p release-artifacts | |
| for pkg in packages/*/dist; do | |
| pkg_name=$(basename $(dirname $pkg)) | |
| echo "Creating archive for $pkg_name..." | |
| tar -czf release-artifacts/${pkg_name}-${{ steps.version.outputs.version }}.tar.gz -C $(dirname $pkg) dist | |
| done | |
| echo "Build artifacts:" | |
| ls -lh release-artifacts/ | |
| - name: Upload release artifacts to GitHub Release | |
| if: inputs.dry_run == false | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.version.outputs.version }} | |
| files: release-artifacts/*.tar.gz | |
| fail_on_unmatched_files: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Notify Slack on success | |
| if: success() && inputs.dry_run == false | |
| uses: slackapi/slack-github-action@v1.26.0 | |
| with: | |
| payload: | | |
| { | |
| "text": "✅ Release v${{ steps.version.outputs.version }} published successfully", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "✅ *Release v${{ steps.version.outputs.version }} published successfully*" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Version:*\nv${{ steps.version.outputs.version }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Tag:*\n${{ steps.release-params.outputs.tag }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Workflow:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Release:*\n<${{ github.server_url }}/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}|View Release>" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_RELEASES }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| - name: Notify Slack on dry run success | |
| if: success() && inputs.dry_run == true | |
| uses: slackapi/slack-github-action@v1.26.0 | |
| with: | |
| payload: | | |
| { | |
| "text": "🧪 Dry run completed successfully for v${{ steps.version.outputs.version }}", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "🧪 *Dry run completed successfully*" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Version:*\nv${{ steps.version.outputs.version }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Tag:*\n${{ steps.release-params.outputs.tag }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Workflow:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Note:*\nNo packages were published (dry run mode)" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| - name: Notify Slack on failure | |
| if: failure() | |
| uses: slackapi/slack-github-action@v1.26.0 | |
| with: | |
| payload: | | |
| { | |
| "text": "❌ Release workflow failed", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "❌ *Release workflow failed*" | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Requested Version:*\n${{ inputs.version }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Pre-release Type:*\n${{ inputs.prerelease_type || 'N/A' }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Dry Run:*\n${{ inputs.dry_run }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Workflow:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} | |
| SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK | |
| - name: Rollback notification on partial failure | |
| if: failure() && steps.npm_publish.outcome == 'success' | |
| run: | | |
| echo "⚠️ CRITICAL: npm publish succeeded but subsequent steps failed" | |
| echo "Published version: v${{ steps.version.outputs.version }}" | |
| echo "Manual intervention may be required" | |
| echo "" | |
| echo "Options:" | |
| echo "1. Re-run the workflow to complete GitHub release creation" | |
| echo "2. Manually create GitHub release for tag v${{ steps.version.outputs.version }}" | |
| echo "3. If necessary, deprecate the npm version: npm deprecate @langfuse/package@${{ steps.version.outputs.version }}" |