Skip to content

Release JS/TS SDK

Release JS/TS SDK #96

Workflow file for this run

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