publish.yml•8.66 kB
name: Build and Publish to NPM
on:
workflow_dispatch:
inputs:
version_to_publish:
description: "Tag - select from recent tags, Custom - enter version manually, Default - use package.json version"
required: true
type: choice
default: "default"
options:
- tag
- custom
- default
tag_selection:
description: "Required when Tag is selected - copy from available tags shown in workflow logs"
required: false
type: string
custom_version:
description: "Required when Custom is selected (e.g., 3.0.0, 1.2.3-beta, 2.1.0-alpha.1)"
required: false
type: string
jobs:
build-and-publish:
runs-on: ubuntu-latest
environment: prod-deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: "yarn"
- name: Install deps
run: yarn install
- name: Show available tags
run: |
echo "📋 Available tags for selection:"
echo ""
git tag --sort=-version:refname | head -5 | while IFS= read -r tag; do
if [ -n "$tag" ]; then
echo " 🔸 $tag"
fi
done
echo ""
echo "💡 Copy one of the tags above and paste it in the 'tag_selection' field"
- name: Get available tags
id: get-tags
run: |
echo "Available tags in this repository:"
git tag --sort=-version:refname | head -10
# Count available tags
TAG_COUNT=$(git tag --list | wc -l)
echo "Total tags found: $TAG_COUNT"
if [ "$TAG_COUNT" -eq 0 ]; then
echo "No tags found in repository"
echo "recent_tags=" >> $GITHUB_OUTPUT
echo "tag_count=0" >> $GITHUB_OUTPUT
else
# Get recent 3 tags (or all if less than 3)
RECENT_TAGS=$(git tag --sort=-version:refname | head -3)
echo "Recent tags: $RECENT_TAGS"
echo "recent_tags=$RECENT_TAGS" >> $GITHUB_OUTPUT
echo "tag_count=$TAG_COUNT" >> $GITHUB_OUTPUT
# Format tags for display
echo "Available recent tags:"
echo "$RECENT_TAGS" | nl -w2 -s') '
fi
- name: Determine version
id: determine-version
run: |
if [ "${{ inputs.version_to_publish }}" = "tag" ]; then
# Tag selection mode - validate tag_selection input
TAG_SELECTION="${{ inputs.tag_selection }}"
if [ -z "$TAG_SELECTION" ] || [ "$TAG_SELECTION" = "" ]; then
echo "❌ ERROR: Tag selection is required when 'Tag' is selected"
echo ""
echo "📋 Available tags (copy one from above):"
git tag --sort=-version:refname | head -5 | while IFS= read -r tag; do
if [ -n "$tag" ]; then
echo " 🔸 $tag"
fi
done
echo ""
echo "💡 Please copy a tag name and paste it in the 'tag_selection' field."
exit 1
fi
# Verify the selected tag exists
if git tag --list | grep -q "^$TAG_SELECTION$"; then
VERSION="$TAG_SELECTION"
echo "✅ Using selected tag: $VERSION"
else
echo "❌ ERROR: Tag '$TAG_SELECTION' does not exist in this repository"
echo ""
echo "📋 Available tags:"
git tag --sort=-version:refname | head -5 | while IFS= read -r tag; do
if [ -n "$tag" ]; then
echo " 🔸 $tag"
fi
done
exit 1
fi
elif [ "${{ inputs.version_to_publish }}" = "custom" ]; then
# Custom version selected - validate input
CUSTOM_VERSION="${{ inputs.custom_version }}"
if [ -z "$CUSTOM_VERSION" ] || [ "$CUSTOM_VERSION" = "" ]; then
echo "ERROR: Custom version is required when 'Custom' is selected"
echo "Please enter a version in the 'custom_version' field (e.g., 1.0.0, 2.1.0-beta)"
exit 1
fi
# Validate version format (basic semver check)
if ! echo "$CUSTOM_VERSION" | grep -E '^([0-9]+\.){2}[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$' > /dev/null; then
echo "ERROR: Invalid version format '$CUSTOM_VERSION'"
echo "Version must follow semantic versioning (e.g., 1.0.0, 2.1.0-beta, 3.0.0-alpha.1)"
exit 1
fi
VERSION="$CUSTOM_VERSION"
echo "Using custom version: $VERSION"
elif [ "${{ inputs.version_to_publish }}" = "default" ]; then
# Default mode - use package.json version
VERSION=$(node -p "require('./package.json').version")
echo "✅ Using default version from package.json: $VERSION"
else
echo "❌ ERROR: Invalid version selection type"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Check current version
id: check-version
run: |
TARGET_VERSION=${{ steps.determine-version.outputs.version }}
# Remove 'v' prefix if present
CLEAN_TARGET=$(echo "$TARGET_VERSION" | sed 's/^v//')
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Target version: $CLEAN_TARGET"
echo "Current version: $CURRENT_VERSION"
if [ "$CLEAN_TARGET" = "$CURRENT_VERSION" ]; then
echo "Version is already $CURRENT_VERSION, skipping version update"
echo "skip_version_update=true" >> $GITHUB_OUTPUT
else
echo "Version needs update from $CURRENT_VERSION to $CLEAN_TARGET"
echo "skip_version_update=false" >> $GITHUB_OUTPUT
echo "clean_version=$CLEAN_TARGET" >> $GITHUB_OUTPUT
fi
- name: Update package.json version
if: steps.check-version.outputs.skip_version_update != 'true'
run: |
CLEAN_VERSION=${{ steps.check-version.outputs.clean_version }}
echo "DEBUG: skip_version_update = ${{ steps.check-version.outputs.skip_version_update }}"
echo "DEBUG: clean_version = $CLEAN_VERSION"
echo "DEBUG: Target version from determine-version = ${{ steps.determine-version.outputs.version }}"
echo "Setting package.json version to: $CLEAN_VERSION"
npm version "$CLEAN_VERSION" --no-git-tag-version
- name: Build
run: npm run build
- name: Generate build hash
id: build-hash
run: |
BUILD_HASH=$(find dist -type f -exec sha256sum {} \; | sha256sum | cut -d' ' -f1)
echo "build_hash=$BUILD_HASH" >> $GITHUB_OUTPUT
echo "Build hash: $BUILD_HASH"
# Always use clean version (without v prefix) for build info
PACKAGE_VERSION=${{ steps.determine-version.outputs.version }}
CLEAN_VERSION=$(echo "$PACKAGE_VERSION" | sed 's/^v//')
BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "{\"version\":\"$CLEAN_VERSION\",\"buildHash\":\"$BUILD_HASH\",\"buildTime\":\"$BUILD_TIME\"}" > dist/build-info.json
- name: Check if package should be published
id: check-publish
run: |
PACKAGE_NAME=$(node -p "require('./package.json').name")
# Use the clean version (without v prefix) for npm check
PACKAGE_VERSION=${{ steps.determine-version.outputs.version }}
CLEAN_VERSION=$(echo "$PACKAGE_VERSION" | sed 's/^v//')
if npm view $PACKAGE_NAME@$CLEAN_VERSION version > /dev/null 2>&1; then
echo "Version $CLEAN_VERSION of $PACKAGE_NAME is already published on NPM. Skipping publish."
echo "should_publish=false" >> $GITHUB_OUTPUT
else
echo "Version $CLEAN_VERSION of $PACKAGE_NAME is new. Proceeding to publish."
echo "should_publish=true" >> $GITHUB_OUTPUT
fi
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts-${{ steps.build-hash.outputs.build_hash }}
path: |
dist/
!dist/**/*.map
- name: Publish new version
if: steps.check-publish.outputs.should_publish == 'true'
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}