Release JS/TS SDK #157
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 JS/TS SDK | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version bump type" | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| - prepatch | |
| - preminor | |
| - premajor | |
| prerelease_type: | |
| description: "Pre-release type (used when version is prepatch/preminor/premajor)" | |
| type: choice | |
| default: "none" | |
| options: | |
| - none | |
| - alpha | |
| - beta | |
| - rc | |
| dry_run: | |
| description: "Dry run (skip publish and git push)" | |
| type: boolean | |
| default: false | |
| confirm_major: | |
| description: "Type RELEASE MAJOR to confirm a major or premajor release" | |
| required: false | |
| default: "" | |
| permissions: | |
| contents: write | |
| id-token: write # Required for npm provenance via OIDC | |
| pull-requests: write | |
| concurrency: | |
| group: js-sdk-release | |
| cancel-in-progress: false | |
| jobs: | |
| release-js-sdk: | |
| runs-on: ubuntu-latest | |
| environment: protected branches | |
| steps: | |
| - name: Verify release ref | |
| run: | | |
| if [ "${GITHUB_REF_TYPE}" != "branch" ]; then | |
| echo "❌ Error: Releases can only be triggered from branches" | |
| echo "Current ref: ${GITHUB_REF}" | |
| exit 1 | |
| fi | |
| if [ "${GITHUB_REF_NAME}" = "main" ]; then | |
| echo "✅ Official and pre-release releases are allowed from main" | |
| exit 0 | |
| fi | |
| case "${INPUTS_VERSION}" in | |
| prepatch|preminor|premajor) | |
| ;; | |
| *) | |
| echo "❌ Error: Official releases can only be triggered from main branch" | |
| echo "Current branch: ${GITHUB_REF_NAME}" | |
| echo "Requested version bump: ${INPUTS_VERSION}" | |
| exit 1 | |
| ;; | |
| esac | |
| if [ -z "${INPUTS_PRERELEASE_TYPE}" ] || [ "${INPUTS_PRERELEASE_TYPE}" = "none" ]; then | |
| echo "❌ Error: Branch releases must specify prerelease_type as alpha, beta, or rc" | |
| exit 1 | |
| fi | |
| echo "✅ Pre-release branch release allowed from ${GITHUB_REF_NAME}" | |
| env: | |
| INPUTS_VERSION: ${{ inputs.version }} | |
| INPUTS_PRERELEASE_TYPE: ${{ inputs.prerelease_type }} | |
| - name: Confirm major release | |
| if: ${{ inputs.version == 'major' || inputs.version == 'premajor' }} | |
| run: | | |
| if [ "${INPUTS_CONFIRM_MAJOR}" != "RELEASE MAJOR" ]; then | |
| echo "❌ For major/premajor releases, set confirm_major to RELEASE MAJOR" | |
| exit 1 | |
| fi | |
| env: | |
| INPUTS_CONFIRM_MAJOR: ${{ inputs.confirm_major }} | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_ACCESS_TOKEN }} | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 24 | |
| registry-url: "https://registry.npmjs.org" | |
| cache: "" # Disable cache for release workflow (publishes to npm) | |
| - name: Configure Git | |
| env: | |
| GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} | |
| run: | | |
| git config user.name "langfuse-bot" | |
| git config user.email "langfuse-bot@langfuse.com" | |
| echo "$GH_ACCESS_TOKEN" | gh auth login --with-token | |
| gh auth setup-git | |
| - 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 | |
| env: | |
| INPUTS_VERSION: ${{ inputs.version }} | |
| INPUTS_PRERELEASE_TYPE: ${{ inputs.prerelease_type }} | |
| run: | | |
| version_type="${INPUTS_VERSION}" | |
| if [ "$version_type" = "prepatch" ] || [ "$version_type" = "preminor" ] || [ "$version_type" = "premajor" ]; then | |
| prerelease_type="${INPUTS_PRERELEASE_TYPE}" | |
| if [ -z "$prerelease_type" ] || [ "$prerelease_type" = "none" ]; then | |
| echo "❌ Error: prerelease_type must be specified when version is prepatch/preminor/premajor" | |
| exit 1 | |
| fi | |
| current_version=$(node -p "require('./package.json').version") | |
| base_version="${current_version%%-*}" | |
| IFS='.' read -r major minor patch <<< "$base_version" | |
| if echo "$current_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+-'; then | |
| current_is_prerelease="true" | |
| else | |
| current_is_prerelease="false" | |
| fi | |
| case "$version_type" in | |
| premajor) | |
| if [ "$current_is_prerelease" = "true" ] && [ "$minor" -eq 0 ] && [ "$patch" -eq 0 ]; then | |
| target_major=$major | |
| else | |
| target_major=$((major + 1)) | |
| fi | |
| target_minor=0 | |
| target_patch=0 | |
| ;; | |
| preminor) | |
| target_major=$major | |
| if [ "$current_is_prerelease" = "true" ] && [ "$patch" -eq 0 ] && [ "$minor" -gt 0 ]; then | |
| target_minor=$minor | |
| else | |
| target_minor=$((minor + 1)) | |
| fi | |
| target_patch=0 | |
| ;; | |
| prepatch) | |
| target_major=$major | |
| target_minor=$minor | |
| if [ "$current_is_prerelease" = "true" ] && [ "$patch" -gt 0 ]; then | |
| target_patch=$patch | |
| else | |
| target_patch=$((patch + 1)) | |
| fi | |
| ;; | |
| esac | |
| target_base_version="${target_major}.${target_minor}.${target_patch}" | |
| if [ "$current_is_prerelease" = "true" ] && [ "$base_version" = "$target_base_version" ]; then | |
| release_increment="prerelease" | |
| else | |
| release_increment="$version_type" | |
| fi | |
| echo "release_increment=${release_increment}" >> $GITHUB_OUTPUT | |
| echo "pre_release_flag=--preRelease=${prerelease_type}" >> $GITHUB_OUTPUT | |
| echo "tag=${prerelease_type}" >> $GITHUB_OUTPUT | |
| else | |
| echo "release_increment=${version_type}" >> $GITHUB_OUTPUT | |
| echo "pre_release_flag=" >> $GITHUB_OUTPUT | |
| echo "tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| - 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: Run release-it (version, git, npm publish) | |
| id: release | |
| if: inputs.dry_run == false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} | |
| NPM_CONFIG_TAG: ${{ steps.release-params.outputs.tag }} | |
| STEPS_RELEASE_PARAMS_OUTPUTS_RELEASE_INCREMENT: ${{ steps.release-params.outputs.release_increment }} | |
| STEPS_RELEASE_PARAMS_OUTPUTS_PRE_RELEASE_FLAG: ${{ steps.release-params.outputs.pre_release_flag }} | |
| run: | | |
| pnpm exec release-it ${STEPS_RELEASE_PARAMS_OUTPUTS_RELEASE_INCREMENT} ${STEPS_RELEASE_PARAMS_OUTPUTS_PRE_RELEASE_FLAG} --ci --config .release-it.ci.json | |
| - name: Run release-it (dry run) | |
| if: inputs.dry_run == true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} | |
| NPM_CONFIG_TAG: ${{ steps.release-params.outputs.tag }} | |
| STEPS_RELEASE_PARAMS_OUTPUTS_RELEASE_INCREMENT: ${{ steps.release-params.outputs.release_increment }} | |
| STEPS_RELEASE_PARAMS_OUTPUTS_PRE_RELEASE_FLAG: ${{ steps.release-params.outputs.pre_release_flag }} | |
| run: | | |
| echo "🧪 Running in DRY RUN mode - no changes will be pushed" | |
| pnpm exec release-it ${STEPS_RELEASE_PARAMS_OUTPUTS_RELEASE_INCREMENT} ${STEPS_RELEASE_PARAMS_OUTPUTS_PRE_RELEASE_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: Notify Slack on success | |
| if: success() && inputs.dry_run == false | |
| uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| { | |
| "text": "✅ Langfuse JS/TS SDK v${{ steps.version.outputs.version }} published to npm", | |
| "blocks": [ | |
| { | |
| "type": "header", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "✅ Langfuse JS/TS SDK Released", | |
| "emoji": true | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Version:*\n`v${{ steps.version.outputs.version }}`" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*npm Tag:*\n`${{ steps.release-params.outputs.tag }}`" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Released by:*\n${{ github.actor }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Packages:*\n6 packages published" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "divider" | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*📦 Published Packages:*\n• `@langfuse/client`\n• `@langfuse/core`\n• `@langfuse/tracing`\n• `@langfuse/otel`\n• `@langfuse/openai`\n• `@langfuse/langchain`" | |
| } | |
| }, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "📋 View Release Notes", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}", | |
| "style": "primary" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "📦 View on npm", | |
| "emoji": true | |
| }, | |
| "url": "https://www.npmjs.com/package/@langfuse/client/v/${{ steps.version.outputs.version }}" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "🔧 View Workflow", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "🔒 Published with provenance attestations • 🤖 Automated release" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| - name: Notify Slack on dry run success | |
| if: success() && inputs.dry_run == true | |
| uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| { | |
| "text": "🧪 Langfuse JS/TS SDK dry run completed for v${{ steps.version.outputs.version }}", | |
| "blocks": [ | |
| { | |
| "type": "header", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "🧪 Langfuse JS/TS SDK Dry Run", | |
| "emoji": true | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "Release dry run completed successfully. No packages were published." | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "fields": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Version:*\n`v${{ steps.version.outputs.version }}`" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*npm Tag:*\n`${{ steps.release-params.outputs.tag }}`" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Triggered by:*\n${{ github.actor }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Mode:*\nDry run (testing only)" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "🔧 View Workflow", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", | |
| "style": "primary" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "📁 View Repository", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "ℹ️ This was a test run. To publish, re-run without dry run mode." | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| - name: Notify Slack on failure | |
| if: failure() | |
| uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| { | |
| "text": "❌ Langfuse JS/TS SDK release workflow failed", | |
| "blocks": [ | |
| { | |
| "type": "header", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "❌ Langfuse JS/TS SDK Release Failed", | |
| "emoji": true | |
| } | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "⚠️ The release workflow encountered an error and did not complete successfully." | |
| } | |
| }, | |
| { | |
| "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": "*Triggered by:*\n${{ github.actor }}" | |
| }, | |
| { | |
| "type": "mrkdwn", | |
| "text": "*Dry Run:*\n${{ inputs.dry_run }}" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "divider" | |
| }, | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "*🔍 Next Steps:*\n• Check the workflow logs for error details\n• Verify all prerequisites are met\n• Check npm Trusted Publishing configuration\n• Verify Slack webhook secrets are set" | |
| } | |
| }, | |
| { | |
| "type": "actions", | |
| "elements": [ | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "🔧 View Workflow Logs", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", | |
| "style": "danger" | |
| }, | |
| { | |
| "type": "button", | |
| "text": { | |
| "type": "plain_text", | |
| "text": "📖 View Documentation", | |
| "emoji": true | |
| }, | |
| "url": "${{ github.server_url }}/${{ github.repository }}/blob/main/CONTRIBUTING.md" | |
| } | |
| ] | |
| }, | |
| { | |
| "type": "context", | |
| "elements": [ | |
| { | |
| "type": "mrkdwn", | |
| "text": "🚨 Action required • Check workflow logs for details" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| - name: Rollback notification on partial failure | |
| if: failure() && steps.release.outcome == 'success' | |
| run: | | |
| echo "⚠️ Release succeeded but post-release steps failed" | |
| echo "Published version: v${STEPS_VERSION_OUTPUTS_VERSION}" | |
| echo "" | |
| echo "The release is fully complete (version bumped, npm published, GitHub release with all artifacts created)." | |
| echo "Check the workflow logs for what failed in post-release steps (e.g., Slack notifications)." | |
| env: | |
| STEPS_VERSION_OUTPUTS_VERSION: ${{ steps.version.outputs.version }} |