#!/bin/bash
# A comprehensive script to automate versioning, building, and publishing of a Node.js project.
set -e # Exit on any error
# --- Configuration ---
# Your primary git branch
MAIN_BRANCH="main"
DOCKER_HUB_USERNAME="hexsleeves"
DOCKER_HUB_REPO="tailscale-mcp-server"
# --- Colors for output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# --- Helper Functions ---
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
prompt_yes_no() {
local prompt="$1"
local response
while true; do
read -p "$prompt (y/n): " response
case $response in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
*) echo "Please answer yes (y) or no (n)." ;;
esac
done
}
# --- Package & Git Info Functions ---
get_current_version() {
bun -p "require('./package.json').version"
}
get_package_name() {
bun -p "require('./package.json').name"
}
get_github_owner() {
# Tries to automatically determine the GitHub user/org from the git remote URL
local remote_url
remote_url=$(git remote get-url origin 2>/dev/null) || return
# Extracts 'user' from 'git@github.com:user/repo.git' or 'https://github.com/user/repo.git'
echo "$remote_url" | sed -n -e 's#.*github.com[:/]\([^/]*\)/.*#\1#p'
}
# --- Core Workflow Functions ---
bump_version() {
local bump_type="$1"
local new_version
if [ "$bump_type" == "custom" ]; then
read -p "Enter the custom version number: " new_version
npm version "$new_version" --no-git-tag-version
else
# Use npm version to bump and capture the new version
new_version=$(npm version "$bump_type" --no-git-tag-version | sed 's/v//')
fi
print_success "Version bumped to $new_version"
# Commit the version change
print_status "Committing version bump..."
git add package.json package-lock.json
git commit -m "chore: bump version to $new_version"
# Create git tag
print_status "Creating git tag v$new_version..."
git tag "v$new_version"
echo "$new_version"
}
publish_npm() {
local version="$1"
print_status "Preparing to publish to npm..."
if prompt_yes_no "Run 'bun run build' first?"; then
bun run build
fi
if prompt_yes_no "Run tests before publishing?"; then
bun test
fi
if prompt_yes_no "Ready to publish version $version to npm?"; then
bun publish --access public
print_success "Published to npm successfully!"
else
print_warning "NPM publish aborted by user."
fi
}
build_docker() {
local version="$1"
local docker_name="$DOCKER_HUB_USERNAME/$DOCKER_HUB_REPO"
print_status "Building Docker image: $docker_name:$version"
docker build -t "$docker_name:$version" -t "$docker_name:latest" .
print_success "Built Docker image: $docker_name:$version"
}
publish_docker() {
local version="$1"
local package_name="$2"
local docker_name
docker_name="$DOCKER_HUB_USERNAME/$DOCKER_HUB_REPO"
build_docker "$version"
if prompt_yes_no "Push image '$docker_name' to Docker Hub?"; then
print_status "Pushing to Docker Hub: $docker_name:$version..."
docker push "$docker_name:$version"
print_status "Pushing to Docker Hub: $docker_name:latest..."
docker push "$docker_name:latest"
print_success "Published to Docker Hub successfully!"
else
print_warning "Docker Hub push aborted by user."
fi
}
publish_ghcr() {
local version="$1"
local package_name="$2"
local docker_name
local github_owner
local ghcr_image
docker_name="$DOCKER_HUB_USERNAME/$DOCKER_HUB_REPO"
github_owner=$(get_github_owner)
if [ -z "$github_owner" ]; then
print_warning "Could not determine GitHub owner from git remote."
read -p "Enter GitHub username/organization: " github_owner
if [ -z "$github_owner" ]; then
print_error "GitHub owner is required for GHCR publish."
fi
else
print_status "Detected GitHub owner: $github_owner"
fi
lowercase_github_owner=$(echo "$github_owner" | tr '[:upper:]' '[:lower:]')
ghcr_image="ghcr.io/$lowercase_github_owner/$DOCKER_HUB_REPO"
print_status "Tagging image for GHCR: $ghcr_image:$version"
docker tag "$docker_name:latest" "$ghcr_image:$version"
docker tag "$docker_name:latest" "$ghcr_image:latest"
if prompt_yes_no "Push image to GitHub Container Registry?"; then
print_status "Pushing to GHCR..."
docker push "$ghcr_image:$version"
docker push "$ghcr_image:latest"
print_success "Published to GitHub Container Registry successfully!"
else
print_warning "GHCR push aborted by user."
fi
}
# --- Main Script Logic ---
main() {
print_status "๐ Starting publish workflow..."
# --- Pre-flight Checks ---
if ! git rev-parse --git-dir >/dev/null 2>&1; then
print_error "Not a git repository. Aborting."
fi
if ! git diff-index --quiet HEAD --; then
print_warning "You have uncommitted changes."
if ! prompt_yes_no "Continue anyway?"; then
print_error "Aborted by user."
fi
fi
local current_version
local package_name
current_version=$(get_current_version)
package_name=$(get_package_name)
print_status "Package: $package_name"
print_status "Current version: $current_version"
# --- Version Bumping ---
local new_version="$current_version"
local bump_type=""
echo ""
print_status "Select a version bump type:"
# Pre-calculate version bumps to avoid repeated npx semver calls
local patch_ver minor_ver major_ver
patch_ver=$(npx semver "$current_version" -i patch)
minor_ver=$(npx semver "$current_version" -i minor)
major_ver=$(npx semver "$current_version" -i major)
local options=(
"patch ($patch_ver)"
"minor ($minor_ver)"
"major ($major_ver)"
"prerelease"
"custom"
"skip"
)
PS3="Your choice: " # Prompt for the select menu
select opt in "${options[@]}"; do
case $opt in
"patch"*)
bump_type="patch"
break
;;
"minor"*)
bump_type="minor"
break
;;
"major"*)
bump_type="major"
break
;;
"prerelease")
bump_type="prerelease"
break
;;
"custom")
bump_type="custom"
break
;;
"skip")
bump_type="skip"
break
;;
*)
print_warning "Invalid option. Please try again."
;;
esac
done
if [[ -n "$bump_type" && "$bump_type" != "skip" ]]; then
new_version=$(bump_version "$bump_type")
else
print_status "Skipping version bump. Using current version: $current_version"
fi
echo ""
print_status "Working with version: $new_version"
# --- Publishing Options ---
local did_publish_docker=false
echo ""
if prompt_yes_no "Publish to npm?"; then
publish_npm "$new_version"
fi
echo ""
if prompt_yes_no "Build and publish Docker image?"; then
# We only need to build the Docker image once
publish_docker "$new_version" "$package_name"
did_publish_docker=true
fi
echo ""
if prompt_yes_no "Publish to GitHub Container Registry?"; then
if ! $did_publish_docker; then
print_warning "Docker image was not built in the previous step."
if prompt_yes_no "Build it now?"; then
build_docker "$new_version"
else
print_error "Cannot publish to GHCR without a built image."
fi
fi
publish_ghcr "$new_version" "$package_name"
fi
# --- Final Git Push ---
if [ "$new_version" != "$current_version" ]; then
echo ""
if prompt_yes_no "Push git commits and tags to origin?"; then
git push origin "$MAIN_BRANCH"
git push origin "v$new_version"
print_success "Git changes and tags pushed!"
fi
fi
echo ""
print_success "๐ Publish workflow completed!"
print_status "Final version: $new_version"
}
# Run the main function with all passed arguments
main "$@"
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/HexSleeves/tailscale-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server