Skip to content

Release JS/TS SDK

Release JS/TS SDK #153

Workflow file for this run

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 }}