---
name: Publish
on:
push:
branches:
- main
jobs:
check:
runs-on: ubuntu-latest
permissions: {}
outputs:
VERSION_EXISTS: ${{ steps.check-version.outputs.VERSION_EXISTS }}
VERSION: ${{ steps.get-version.outputs.VERSION }}
RELEASE_CHANNEL: ${{ steps.npm-tag.outputs.RELEASE_CHANNEL }}
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Get version
id: get-version
shell: bash
run: |
set +e
VERSION=v$(jq -r '.version' < package.json)
echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Check if version already exists
id: check-version
shell: bash
run: |
set +e
git rev-parse "${{ steps.get-version.outputs.VERSION }}" >/dev/null 2>&1
if [[ $? -eq 0 ]]; then
echo "VERSION_EXISTS=true" >> "$GITHUB_OUTPUT"
else
echo "VERSION_EXISTS=false" >> "$GITHUB_OUTPUT"
fi
- name: Get npm tag
id: npm-tag
shell: bash
run: |
set -e
VERSION="${{ steps.get-version.outputs.VERSION }}"
# Extract the release channel (latest, alpha, beta, rc)
if [[ $VERSION =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-(.+))?$ ]]; then
if [[ -n "${BASH_REMATCH[2]}" ]]; then
CAPTURED_CHANNEL="${BASH_REMATCH[2]}"
# The captured channel might have more dots, cases like
# v1.2.3-alpha.1 For such cases we only want the channel relevant
# part which is alpha.
RELEASE_CHANNEL="${CAPTURED_CHANNEL%%.*}"
else
RELEASE_CHANNEL="latest"
fi
else
echo "::error title=Invalid Version::Encountered unexpected version ${{ steps.get-version.outputs.VERSION }}, cannot proceed!"
exit 1
fi
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> "$GITHUB_OUTPUT"
- name: Output deployment info
run: echo "::notice title=Deployment Info::Deploying version ${{ steps.get-version.outputs.VERSION }} to channel ${{ steps.npm-tag.outputs.RELEASE_CHANNEL }}"
publish:
runs-on: ubuntu-latest
environment: Production
permissions:
contents: write
id-token: write
needs:
- check
if: needs.check.outputs.VERSION_EXISTS == 'false'
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version-file: package.json
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
- name: Build package
run: |
pnpm install --frozen-lockfile
pnpm run build
- name: Publish to NPM
run: pnpm publish --tag ${{ needs.check.outputs.RELEASE_CHANNEL }} --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish git release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create ${{ needs.check.outputs.VERSION }} --title "${{ needs.check.outputs.VERSION }}" --generate-notes --target ${{ github.sha }} ${{ (needs.check.outputs.RELEASE_CHANNEL != 'latest' && '--prerelease') || ''}}
- name: Wait for package to be available on npm
run: |
PACKAGE_NAME=$(jq -r '.name' < package.json)
VERSION="${{ needs.check.outputs.VERSION }}"
# Strip the 'v' prefix for npm
NPM_VERSION="${VERSION#v}"
MAX_ATTEMPTS=30
SLEEP_SECONDS=10
echo "Waiting for ${PACKAGE_NAME}@${NPM_VERSION} to be available on npm..."
for i in $(seq 1 $MAX_ATTEMPTS); do
if npm view "${PACKAGE_NAME}@${NPM_VERSION}" version >/dev/null 2>&1; then
echo "✓ Package ${PACKAGE_NAME}@${NPM_VERSION} is now available on npm"
exit 0
fi
echo "Attempt $i/$MAX_ATTEMPTS: Package not yet available, waiting ${SLEEP_SECONDS}s..."
sleep $SLEEP_SECONDS
done
echo "::error::Package ${PACKAGE_NAME}@${NPM_VERSION} did not become available after $((MAX_ATTEMPTS * SLEEP_SECONDS)) seconds"
exit 1
docker-push:
needs: [check, publish]
uses: ./.github/workflows/docker-publish.yml
permissions:
contents: read
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
mcp-publish:
needs: [check, docker-push]
if: needs.check.outputs.VERSION_EXISTS == 'false'
uses: ./.github/workflows/mcp-publish.yml
permissions:
id-token: write
contents: read