Skip to main content
Glama
bbernstein
by bbernstein
release.yml22.6 kB
name: Create Release on: workflow_dispatch: inputs: version_bump: description: 'Version bump type' required: true type: choice options: - patch - minor - major default: 'patch' is_prerelease: description: 'Create as prerelease (beta)' required: false type: boolean default: false release_name: description: 'Release name (optional - leave blank for auto-generated)' required: false type: string permissions: contents: write pull-requests: write jobs: release: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: '20' - name: Configure Git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Get current version from git tags id: current_version run: | # Get all version tags and find highest stable and highest beta separately # This fixes issues where git's version sort doesn't follow semver rules ALL_TAGS=$(git tag -l 'v*' | sed 's/^v//') echo "All version tags:" echo "$ALL_TAGS" | head -10 # Find highest stable version (no prerelease suffix) HIGHEST_STABLE=$(echo "$ALL_TAGS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 || echo "") # Find highest beta version HIGHEST_BETA=$(echo "$ALL_TAGS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$' | sort -V | tail -n 1 || echo "") echo "Highest stable: ${HIGHEST_STABLE:-none}" echo "Highest beta: ${HIGHEST_BETA:-none}" # Export both for use in version calculation echo "highest_stable=${HIGHEST_STABLE}" >> $GITHUB_OUTPUT echo "highest_beta=${HIGHEST_BETA}" >> $GITHUB_OUTPUT # Determine "current" version for display (highest of either) if [ -n "$HIGHEST_STABLE" ] && [ -n "$HIGHEST_BETA" ]; then # Compare base versions BETA_BASE=$(echo "$HIGHEST_BETA" | sed 's/-beta\.[0-9]\+$//') if [ "$(printf '%s\n%s' "$HIGHEST_STABLE" "$BETA_BASE" | sort -V | tail -n 1)" = "$HIGHEST_STABLE" ]; then CURRENT_VERSION="$HIGHEST_STABLE" else CURRENT_VERSION="$HIGHEST_BETA" fi elif [ -n "$HIGHEST_BETA" ]; then CURRENT_VERSION="$HIGHEST_BETA" elif [ -n "$HIGHEST_STABLE" ]; then CURRENT_VERSION="$HIGHEST_STABLE" else CURRENT_VERSION="0.0.0" fi echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT echo "Current version for display: $CURRENT_VERSION" - name: Calculate new version id: new_version run: | HIGHEST_STABLE="${{ steps.current_version.outputs.highest_stable }}" HIGHEST_BETA="${{ steps.current_version.outputs.highest_beta }}" VERSION_BUMP="${{ inputs.version_bump }}" IS_PRERELEASE="${{ inputs.is_prerelease }}" echo "Highest stable: ${HIGHEST_STABLE:-none}" echo "Highest beta: ${HIGHEST_BETA:-none}" echo "Version bump: $VERSION_BUMP" echo "Is prerelease: $IS_PRERELEASE" # Helper function to bump version bump_version() { local version="$1" local bump_type="$2" IFS='.' read -ra parts <<< "$version" local major="${parts[0]:-0}" local minor="${parts[1]:-0}" local patch="${parts[2]:-0}" case "$bump_type" in major) major=$((major + 1)); minor=0; patch=0 ;; minor) minor=$((minor + 1)); patch=0 ;; patch) patch=$((patch + 1)) ;; esac echo "${major}.${minor}.${patch}" } # Parse beta info if exists BETA_BASE="" BETA_NUMBER=0 if [ -n "$HIGHEST_BETA" ]; then if [[ "$HIGHEST_BETA" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-beta\.([0-9]+)$ ]]; then BETA_BASE="${BASH_REMATCH[1]}" BETA_NUMBER="${BASH_REMATCH[2]}" fi fi # Default stable to 0.0.0 if not set HIGHEST_STABLE="${HIGHEST_STABLE:-0.0.0}" if [ "$IS_PRERELEASE" = "true" ]; then # Creating a prerelease (beta) if [ -n "$BETA_BASE" ]; then # There's an existing beta series # Check if stable release exists for this beta's base version STABLE_GTE_BETA_BASE="false" if [ "$HIGHEST_STABLE" != "0.0.0" ]; then COMPARE_RESULT=$(printf '%s\n%s' "$HIGHEST_STABLE" "$BETA_BASE" | sort -V | tail -n 1) if [ "$COMPARE_RESULT" = "$HIGHEST_STABLE" ]; then STABLE_GTE_BETA_BASE="true" fi fi echo "Beta base: $BETA_BASE, Stable >= Beta base: $STABLE_GTE_BETA_BASE" if [ "$STABLE_GTE_BETA_BASE" = "true" ]; then # Stable release exists for beta base version (or higher) # Start a NEW beta series from bumped stable version NEW_BASE=$(bump_version "$HIGHEST_STABLE" "$VERSION_BUMP") NEW_VERSION="${NEW_BASE}-beta.1" echo "Scenario: Stable exists for beta base -> New beta series from $HIGHEST_STABLE" else # No stable release for beta base yet, continue the beta series BETA_NUMBER=$((BETA_NUMBER + 1)) NEW_VERSION="${BETA_BASE}-beta.${BETA_NUMBER}" echo "Scenario: Continue beta series -> ${BETA_BASE}-beta.${BETA_NUMBER}" fi else # No existing beta, start new beta series from stable NEW_BASE=$(bump_version "$HIGHEST_STABLE" "$VERSION_BUMP") NEW_VERSION="${NEW_BASE}-beta.1" echo "Scenario: No beta exists -> First beta from stable $HIGHEST_STABLE" fi else # Creating a stable release if [ -n "$BETA_BASE" ]; then # Check if this beta should be finalized # Only finalize if no stable >= beta base exists COMPARE_RESULT=$(printf '%s\n%s' "$HIGHEST_STABLE" "$BETA_BASE" | sort -V | tail -n 1) if [ "$HIGHEST_STABLE" = "0.0.0" ] || [ "$COMPARE_RESULT" != "$HIGHEST_STABLE" ]; then # No stable release for beta base yet (stable < beta base) -> finalize beta NEW_VERSION="$BETA_BASE" echo "Scenario: Finalize beta -> $BETA_BASE" else # Stable already >= beta base -> normal bump from stable NEW_VERSION=$(bump_version "$HIGHEST_STABLE" "$VERSION_BUMP") echo "Scenario: Stable bump (beta already finalized) -> $NEW_VERSION" fi else # No beta, normal stable bump NEW_VERSION=$(bump_version "$HIGHEST_STABLE" "$VERSION_BUMP") echo "Scenario: Normal stable bump -> $NEW_VERSION" fi fi echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT echo "New version: $NEW_VERSION" - name: Update package.json version run: | npm version ${{ steps.new_version.outputs.version }} --no-git-tag-version - name: Update package-lock.json run: | npm install --package-lock-only - name: Commit version bump run: | set -e git add package.json package-lock.json git commit -m "chore: bump version to ${{ steps.new_version.outputs.version }}" git push - name: Create Git tag run: | set -e VERSION="${{ steps.new_version.outputs.version }}" # Check if tag exists on remote (skip if exists - idempotent) if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q "v${VERSION}"; then echo "Tag v${VERSION} already exists on remote. Skipping creation." else git tag -a "v${VERSION}" -m "Release v${VERSION}" git push origin "v${VERSION}" fi - name: Determine release name id: release_name run: | if [ -z "${{ inputs.release_name }}" ]; then echo "name=v${{ steps.new_version.outputs.version }}" >> $GITHUB_OUTPUT else echo "name=${{ inputs.release_name }}" >> $GITHUB_OUTPUT fi - name: Check if prerelease id: prerelease_check run: | VERSION="${{ steps.new_version.outputs.version }}" IS_PRERELEASE="false" # Check for semver-compatible beta format: X.Y.Z-beta.[N] if [[ "$VERSION" =~ -beta\.[0-9]+$ ]]; then IS_PRERELEASE="true" fi echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT echo "Is Prerelease: $IS_PRERELEASE" - name: Create GitHub Release uses: actions/github-script@v8 with: script: | const tagName = 'v${{ steps.new_version.outputs.version }}'; // Check if release already exists try { await github.rest.repos.getReleaseByTag({ owner: context.repo.owner, repo: context.repo.repo, tag: tagName }); core.info(`Release ${tagName} already exists. Skipping creation.`); return; } catch (error) { if (error.status !== 404) { throw error; } } // Create the release await github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, tag_name: tagName, name: '${{ steps.release_name.outputs.name }}', draft: false, prerelease: ${{ steps.prerelease_check.outputs.is_prerelease == 'true' }}, generate_release_notes: true }); - name: Install dependencies and build run: | npm ci npm run build - name: Install production dependencies for archive run: | # Remove all dependencies and reinstall only production dependencies # This reduces archive size and avoids including dev tools rm -rf node_modules npm ci --omit=dev - name: Create release archive run: | # Create directory structure for extraction mkdir -p lacylights-mcp cp -r src/ dist/ node_modules/ package.json package-lock.json lacylights-mcp/ tar -czf lacylights-mcp-${{ steps.new_version.outputs.version }}.tar.gz \ --exclude='.git' \ --exclude='.github' \ --exclude='node_modules/.cache' \ --exclude='*.test.ts' \ --exclude='__tests__' \ lacylights-mcp/ rm -rf lacylights-mcp - name: Upload release asset env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | gh release upload v${{ steps.new_version.outputs.version }} \ lacylights-mcp-${{ steps.new_version.outputs.version }}.tar.gz \ --clobber - name: Calculate SHA256 checksum id: checksum run: | ARTIFACT="lacylights-mcp-${{ steps.new_version.outputs.version }}.tar.gz" SHA256=$(sha256sum "$ARTIFACT" | awk '{print $1}') FILE_SIZE=$(wc -c < "$ARTIFACT" | tr -d ' ') # Check if this is a prerelease (semver beta format: X.Y.Z-beta.[N]) VERSION="${{ steps.new_version.outputs.version }}" IS_PRERELEASE="false" if [[ "$VERSION" =~ -beta\.[0-9]+$ ]]; then IS_PRERELEASE="true" fi echo "sha256=$SHA256" >> $GITHUB_OUTPUT echo "artifact=$ARTIFACT" >> $GITHUB_OUTPUT echo "file_size=$FILE_SIZE" >> $GITHUB_OUTPUT echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT echo "SHA256: $SHA256" echo "File Size: $FILE_SIZE bytes" echo "Is Prerelease: $IS_PRERELEASE" - name: Upload to S3 env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | COMPONENT="mcp" VERSION="${{ steps.new_version.outputs.version }}" ARTIFACT="${{ steps.checksum.outputs.artifact }}" echo "Uploading $ARTIFACT to S3..." aws s3 cp "$ARTIFACT" \ "s3://${{ secrets.AWS_DIST_BUCKET }}/releases/${COMPONENT}/$ARTIFACT" \ --content-type "application/gzip" echo "✓ Upload successful" - name: Create version-specific metadata JSON env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | set -e COMPONENT="mcp" VERSION="${{ steps.new_version.outputs.version }}" ARTIFACT="${{ steps.checksum.outputs.artifact }}" SHA256="${{ steps.checksum.outputs.sha256 }}" FILE_SIZE="${{ steps.checksum.outputs.file_size }}" IS_PRERELEASE="${{ steps.checksum.outputs.is_prerelease }}" # Create version-specific metadata JSON cat > "${VERSION}.json" <<EOF { "version": "$VERSION", "url": "https://dist.lacylights.com/releases/${COMPONENT}/$ARTIFACT", "sha256": "$SHA256", "releaseDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "isPrerelease": $IS_PRERELEASE, "fileSize": $FILE_SIZE } EOF echo "Uploading ${VERSION}.json..." aws s3 cp "${VERSION}.json" \ "s3://${{ secrets.AWS_DIST_BUCKET }}/releases/${COMPONENT}/${VERSION}.json" \ --content-type "application/json" \ --cache-control "max-age=31536000, immutable" \ --metadata-directive REPLACE echo "✓ ${VERSION}.json uploaded" - name: Update latest.json if: steps.prerelease_check.outputs.is_prerelease == 'false' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | COMPONENT="mcp" VERSION="${{ steps.new_version.outputs.version }}" ARTIFACT="${{ steps.checksum.outputs.artifact }}" SHA256="${{ steps.checksum.outputs.sha256 }}" FILE_SIZE="${{ steps.checksum.outputs.file_size }}" # Create latest.json cat > latest.json <<EOF { "version": "$VERSION", "url": "https://dist.lacylights.com/releases/${COMPONENT}/$ARTIFACT", "sha256": "$SHA256", "releaseDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "isPrerelease": false, "fileSize": $FILE_SIZE } EOF echo "Uploading latest.json..." aws s3 cp latest.json \ "s3://${{ secrets.AWS_DIST_BUCKET }}/releases/${COMPONENT}/latest.json" \ --content-type "application/json" \ --cache-control "max-age=300, must-revalidate" \ --metadata-directive REPLACE echo "✓ latest.json updated" - name: Update prerelease.json if: steps.prerelease_check.outputs.is_prerelease == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | COMPONENT="mcp" VERSION="${{ steps.new_version.outputs.version }}" ARTIFACT="${{ steps.checksum.outputs.artifact }}" SHA256="${{ steps.checksum.outputs.sha256 }}" FILE_SIZE="${{ steps.checksum.outputs.file_size }}" # Create prerelease.json cat > prerelease.json <<EOF { "version": "$VERSION", "url": "https://dist.lacylights.com/releases/${COMPONENT}/$ARTIFACT", "sha256": "$SHA256", "releaseDate": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "isPrerelease": true, "fileSize": $FILE_SIZE } EOF echo "Uploading prerelease.json..." aws s3 cp prerelease.json \ "s3://${{ secrets.AWS_DIST_BUCKET }}/releases/${COMPONENT}/prerelease.json" \ --content-type "application/json" \ --cache-control "max-age=300, must-revalidate" \ --metadata-directive REPLACE echo "✓ prerelease.json updated" - name: Update DynamoDB env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | COMPONENT="mcp" VERSION="${{ steps.new_version.outputs.version }}" ARTIFACT="${{ steps.checksum.outputs.artifact }}" SHA256="${{ steps.checksum.outputs.sha256 }}" FILE_SIZE="${{ steps.checksum.outputs.file_size }}" IS_PRERELEASE="${{ steps.checksum.outputs.is_prerelease }}" echo "Updating DynamoDB..." aws dynamodb put-item \ --table-name lacylights-releases \ --item "{ \"component\": {\"S\": \"$COMPONENT\"}, \"version\": {\"S\": \"$VERSION\"}, \"url\": {\"S\": \"https://dist.lacylights.com/releases/${COMPONENT}/$ARTIFACT\"}, \"sha256\": {\"S\": \"$SHA256\"}, \"releaseDate\": {\"S\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}, \"isPrerelease\": {\"BOOL\": $IS_PRERELEASE}, \"fileSize\": {\"N\": \"$FILE_SIZE\"} }" echo "✓ DynamoDB updated: component=$COMPONENT, version=$VERSION" - name: Update versions.json index env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_DIST_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_DIST_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_DIST_REGION }} run: | set -e COMPONENT="mcp" echo "Querying DynamoDB for all $COMPONENT versions..." # Query all versions for this component from DynamoDB VERSIONS_DATA=$(aws dynamodb query \ --table-name lacylights-releases \ --key-condition-expression "component = :comp" \ --expression-attribute-values '{":comp": {"S": "'"$COMPONENT"'"}}' \ --projection-expression "#v, releaseDate, isPrerelease, sha256, #u, fileSize" \ --expression-attribute-names '{"#v": "version", "#u": "url"}' \ --output json) # Build versions.json using jq with proper semver sorting # Filters out records with null/invalid versions echo "$VERSIONS_DATA" | jq -r ' .Items | map(select(.version.S != null and (.version.S | test("^[0-9]+\\.[0-9]+\\.[0-9]+(-.+)?$")))) | map({ version: .version.S, releaseDate: .releaseDate.S, isPrerelease: (.isPrerelease.BOOL // false), sha256: .sha256.S, url: .url.S, fileSize: (.fileSize.N // "0" | tonumber) }) | # Add numeric semver components for proper semantic version sorting # Split version on "." and "-" to handle both base version and prerelease suffix map(. + { _v: ( .version | split("-")[0] | split(".") | map(tonumber? // 0) ) }) | # Sort: stable versions first (by semantic version desc), then prereleases (by semantic version desc) ( map(select(.isPrerelease | not)) | sort_by(._v) | reverse ) + ( map(select(.isPrerelease)) | sort_by(._v) | reverse ) | # Remove temporary sort key map(del(._v)) ' > versions.json echo "Generated versions.json with $(jq length versions.json) versions" cat versions.json # Upload versions.json aws s3 cp versions.json \ "s3://${{ secrets.AWS_DIST_BUCKET }}/releases/${COMPONENT}/versions.json" \ --content-type "application/json" \ --cache-control "max-age=300, must-revalidate" \ --metadata-directive REPLACE echo "✓ versions.json updated for $COMPONENT" - name: Summary run: | echo "## Release Created Successfully! 🎉" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version:** v${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Bump Type:** ${{ inputs.version_bump }}" >> $GITHUB_STEP_SUMMARY echo "**Previous Version:** v${{ steps.current_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Release URL:** https://github.com/${{ github.repository }}/releases/tag/v${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "**Release Asset:** lacylights-mcp-${{ steps.new_version.outputs.version }}.tar.gz" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Distribution" >> $GITHUB_STEP_SUMMARY echo "**Download URL:** https://dist.lacylights.com/releases/mcp/${{ steps.checksum.outputs.artifact }}" >> $GITHUB_STEP_SUMMARY echo "**SHA256:** \`${{ steps.checksum.outputs.sha256 }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**API Endpoints:**" >> $GITHUB_STEP_SUMMARY echo "- Latest: https://dist.lacylights.com/releases/mcp/latest.json" >> $GITHUB_STEP_SUMMARY echo "- All versions: https://dist.lacylights.com/releases/mcp/versions.json" >> $GITHUB_STEP_SUMMARY

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bbernstein/lacylights-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server