Skip to content

Instantly share code, notes, and snippets.

@koad
Last active July 24, 2025 17:21
Show Gist options
  • Save koad/e5deed3441f83be97aecc96115edbd39 to your computer and use it in GitHub Desktop.
Save koad/e5deed3441f83be97aecc96115edbd39 to your computer and use it in GitHub Desktop.
This script scans Meteor applications for deprecated functions and patterns that need to be updated for Meteor 3.0 compatibility. It provides detailed reports with file locations, line numbers, and migration advice.
#!/bin/bash
#
# ===================================================================
# Meteor 3.0 Migration Analysis Script
# ===================================================================
#
# Purpose:
# This script scans Meteor applications for deprecated functions and patterns
# that need to be updated for Meteor 3.0 compatibility. It provides detailed
# reports with file locations, line numbers, and migration advice.
#
# Requirements:
# - ripgrep (rg command) - much faster and more powerful than grep
#
# Usage:
# ./meteor3-migration-checker.sh [directory]
#
# Examples:
# ./meteor3-migration-checker.sh # Scans current directory recursively
# ./meteor3-migration-checker.sh ./my-app # Scans specific directory recursively
#
# Notes:
# - Searches recursively through all subdirectories by default
# - Automatically excludes node_modules, .meteor, .local, .npm, dist, and build directories
# - Focuses on JavaScript/TypeScript files (*.js, *.jsx, *.ts, *.tsx)
#
# Author: koad via claude
# Version: 1.0
# Date: July 24 2025
#
# ===================================================================
# =================================================================
# SCRIPT CONFIGURATION AND SETUP
# =================================================================
# Don't exit on errors - we want to complete the full scan even if some parts fail
# This ensures we get a complete report of all issues
set +e
# Store the start time to calculate total execution time at the end
START_TIME=$(date +%s)
# Directory to scan (default is current directory)
# The ${1:-.} syntax means "use the first argument if provided, otherwise use '.'"
SCAN_DIR="${1:-.}"
# =================================================================
# COLOR DEFINITIONS - Making Terminal Output Pretty
# =================================================================
# ANSI escape codes tell the terminal to change text colors and styles
# Format: \033[COLORm where COLOR is a number code
# These variables make the rest of the script more readable
# Text colors
RED='\033[0;31m' # Critical issues (must fix)
BRIGHT_RED='\033[1;31m' # Section headings
YELLOW='\033[0;33m' # Warnings (should fix)
GREEN='\033[0;32m' # Success messages
BLUE='\033[0;34m' # Information
CYAN='\033[0;36m' # File paths and technical details
MAGENTA='\033[0;35m' # Progress indicators
GRAY='\033[0;90m' # Less important information
WHITE='\033[1;37m' # Highlighted content
NC='\033[0m' # No Color - resets to default terminal color
# Text styles
BOLD='\033[1m'
UNDERLINE='\033[4m'
ITALIC='\033[3m'
# =================================================================
# UNICODE SYMBOLS FOR BETTER READABILITY
# =================================================================
# These symbols make the output more visual and easier to scan
CHECK="✓" # Success/completed
CROSS="✗" # Error/issue found
WARN="⚠" # Warning
INFO="ℹ" # Information
ARROW="➤" # Pointing to specific line
ROCKET="🚀" # Progress/action
MAGNIFY="🔍" # Searching
LIGHTBULB="💡" # Tip/suggestion
TOOLS="🔧" # Fix required
HOURGLASS="⏳" # Processing/waiting
# =================================================================
# CHECK FOR REQUIRED TOOLS
# =================================================================
# Check if ripgrep is installed - exit with instructions if not found
check_requirements() {
echo -e "${BLUE}${HOURGLASS} Checking requirements...${NC}"
if ! command -v rg &> /dev/null; then
echo -e "${RED}${CROSS} Error: ripgrep (rg) is not installed${NC}"
echo
echo -e "${YELLOW}Ripgrep is required for this script. Please install it:${NC}"
echo
echo -e "${CYAN}On Ubuntu/Debian:${NC}"
echo " sudo apt-get update"
echo " sudo apt-get install ripgrep"
echo
echo -e "${CYAN}On MacOS:${NC}"
echo " brew install ripgrep"
echo
echo -e "${CYAN}On CentOS/RHEL:${NC}"
echo " sudo yum install ripgrep"
echo
echo -e "${CYAN}On Windows (with Chocolatey):${NC}"
echo " choco install ripgrep"
echo
echo -e "${CYAN}Or download from: ${UNDERLINE}https://github.com/BurntSushi/ripgrep/releases${NC}"
echo
exit 1
fi
echo -e "${GREEN}${CHECK} All requirements satisfied${NC}"
echo
}
# =================================================================
# SEARCH PATTERNS
# =================================================================
# These arrays define what we're looking for in the codebase
# Each array holds patterns for a specific category of migration issues
# Critical Issues (must fix) - Breaking changes that will stop the app from running
declare -a critical_patterns=(
# Fiber-related patterns
"Fiber\.yield"
"Fiber\.current"
"Promise\.await"
"Meteor\.wrapAsync"
"fibers/future"
"Npm\.require\(['\"]fibers['\"]"
# Synchronous MongoDB operations
"\.find\(\)\.fetch\(\)"
"\.findOne\("
"\.insert\("
"\.update\("
"\.remove\("
"\.upsert\("
)
# Critical pattern descriptions - matches order with the array above
declare -a critical_descriptions=(
"Fiber.yield() - Fibers have been removed in Meteor 3"
"Fiber.current - Fibers have been removed in Meteor 3"
"Promise.await() - No longer needed, use native await"
"Meteor.wrapAsync() - No longer needed, use async/await"
"fibers/future - Future package import (removed in Meteor 3)"
"Npm.require('fibers') - Fibers dependency import"
"Collection.find().fetch() - Synchronous fetch() operation"
"Collection.findOne() - Synchronous findOne() operation"
"Collection.insert() - Synchronous insert() operation"
"Collection.update() - Synchronous update() operation"
"Collection.remove() - Synchronous remove() operation"
"Collection.upsert() - Synchronous upsert() operation"
)
# Critical fix suggestions - matches order with the arrays above
declare -a critical_fixes=(
"Replace with 'await' and make containing function async"
"Replace with modern async/await pattern"
"Replace with native 'await' keyword"
"Replace with direct use of async/await"
"Remove Future imports and use async/await pattern"
"Remove Fibers dependency import"
"Replace with 'await Collection.find().fetchAsync()'"
"Replace with 'await Collection.findOneAsync()'"
"Replace with 'await Collection.insertAsync()'"
"Replace with 'await Collection.updateAsync()'"
"Replace with 'await Collection.removeAsync()'"
"Replace with 'await Collection.upsertAsync()'"
)
# High Priority Issues (should fix) - Features that may work but are deprecated
declare -a high_patterns=(
# Method calls (recommended to update)
"Meteor\.call\([^,]+,[^,]+,\s*function"
# API renames
"Accounts\.setPassword"
"Accounts\.addEmail"
"Assets\.getText"
"Assets\.getBinary"
"Meteor\.user\(\)"
)
# High pattern descriptions
declare -a high_descriptions=(
"Meteor.call() with callback - Better to use callAsync"
"Accounts.setPassword - Renamed in Meteor 3"
"Accounts.addEmail - Renamed in Meteor 3"
"Assets.getText - Renamed in Meteor 3"
"Assets.getBinary - Renamed in Meteor 3"
"Meteor.user() - Server-side should use userAsync"
)
# High fix suggestions
declare -a high_fixes=(
"Replace with 'const result = await Meteor.callAsync(...)'"
"Replace with 'await Accounts.setPasswordAsync()'"
"Replace with 'await Accounts.addEmailAsync()'"
"Replace with 'await Assets.getTextAsync()'"
"Replace with 'await Assets.getBinaryAsync()'"
"Replace with 'await Meteor.userAsync()' on server-side"
)
# Medium Priority Issues (consider fixing) - API changes that have alternatives
declare -a medium_patterns=(
# WebApp API changes
"WebApp\.connectHandlers"
"WebApp\.rawConnectHandlers"
"WebApp\.connectApp"
)
# Medium pattern descriptions
declare -a medium_descriptions=(
"WebApp.connectHandlers - Connect API replaced with Express"
"WebApp.rawConnectHandlers - Connect API replaced with Express"
"WebApp.connectApp - Connect API replaced with Express"
)
# Medium fix suggestions
declare -a medium_fixes=(
"Replace with 'WebApp.handlers'"
"Replace with 'WebApp.rawHandlers'"
"Replace with 'WebApp.expressApp'"
)
# =================================================================
# HELPER FUNCTIONS
# =================================================================
# Print a separator line to make output more readable
# No parameters needed - just prints a line of a fixed length
print_separator() {
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Print a section heading to organize output
# $1: Text to display as a heading
print_heading() {
echo
print_separator
echo -e "${BRIGHT_RED}${BOLD}$1${NC}"
print_separator
}
# Print information about what we're searching for
# $1: Search pattern description
# $2: Current pattern number
# $3: Total number of patterns
print_searching() {
echo
echo -e "${MAGENTA}${MAGNIFY} [$2/$3] Searching for: ${CYAN}$1${NC}"
print_separator
}
# Format file paths to be more readable
# $1: File path to format
format_file_path() {
echo -e "${CYAN}${BOLD}$1${NC}"
}
# Count files in the directory to be scanned
# Takes no parameters but uses the global SCAN_DIR variable
count_files() {
# Find all JS/TS files that might contain Meteor code
# Using -not -path to exclude node_modules, .local, .meteor and other non-source directories
# This ensures we're counting the same files that ripgrep will search
local file_count=$(find "$SCAN_DIR" -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" \) \
-not -path "*/node_modules/*" \
-not -path "*/.meteor/*" \
-not -path "*/.local/*" \
-not -path "*/.npm/*" \
-not -path "*/dist/*" \
-not -path "*/build/*" \
| wc -l)
echo "$file_count"
}
# =================================================================
# MAIN SEARCH FUNCTIONS
# =================================================================
# Search for a specific pattern with ripgrep and display results
# $1: Search pattern (regex)
# $2: Pattern description
# $3: Fix suggestion
# $4: Severity color (RED, YELLOW, etc.)
# $5: Severity label (CRITICAL, HIGH, etc.)
# Sets global PATTERN_MATCH_COUNT with number of matches found
search_pattern() {
local pattern="$1"
local description="$2"
local fix="$3"
local color="$4"
local severity="$5"
PATTERN_MATCH_COUNT=0
# This command runs ripgrep with:
# -i: Case insensitive matching
# --context=2: Show 2 lines before and after the match
# -g: Glob patterns to include only JavaScript/TypeScript files
# --iglob: Glob patterns to exclude (node_modules, .local, .npm, etc.)
# --color=always: Force color output
# -n: Show line numbers
# Note: ripgrep searches recursively by default
local results=$(rg -i --context=2 -g "*.{js,jsx,ts,tsx}" \
--iglob "!node_modules" \
--iglob "!.meteor" \
--iglob "!.local" \
--iglob "!.npm" \
--iglob "!dist" \
--iglob "!build" \
--color=always \
-n "$pattern" "$SCAN_DIR" 2>/dev/null || true)
# If we found matches, display them with context
if [[ -n "$results" ]]; then
# Count lines that have the pattern (ignoring context lines)
# Get a clean count by counting lines with our pattern
count=$(rg -i --no-heading --no-line-number \
-g "*.{js,jsx,ts,tsx}" \
--iglob "!node_modules" \
--iglob "!.meteor" \
--iglob "!.local" \
--iglob "!.npm" \
--iglob "!dist" \
--iglob "!build" \
"$pattern" "$SCAN_DIR" 2>/dev/null | wc -l)
# Make sure we have valid count
if [[ -z "$count" ]] || [[ "$count" -lt 1 ]]; then
count=0
fi
echo -e "${color}${severity}: Found ${count} instances${NC}"
echo
# Much simpler approach - just echo the results directly
# This avoids complex processing that might hang
# Add some basic formatting to make it more readable
echo "$results" | sed "s/^--$//" | sed "s/^$//" | \
sed "s/^\([^:]*\):\([0-9]*\):\(.*\)/${CYAN}📁 \1${NC}\n ${GRAY}\2 │${NC} ${color}${ARROW} \3${NC}/" | \
sed "s/^\([0-9]*\)\([-:]\)\(.*\)/ ${GRAY}\1 │${NC} \3/"
echo ""
# Print the fix suggestion
echo -e "${GREEN}${LIGHTBULB} Migration: ${fix}${NC}"
echo
# Store count in global variable
PATTERN_MATCH_COUNT=$count
else
PATTERN_MATCH_COUNT=0
fi
}
# Process an entire array of patterns
# $1: Array of patterns
# $2: Array of descriptions
# $3: Array of fixes
# $4: Color for display
# $5: Severity label
# Returns: Total number of issues found
process_patterns() {
local -n patterns=$1
local -n descriptions=$2
local -n fixes=$3
local color=$4
local severity=$5
local total=0
local current=0
# Loop through all patterns
for i in "${!patterns[@]}"; do
current=$((current + 1))
# Calculate global pattern number and total for progress display
local global_current=$((global_pattern_current + current))
local global_total=$((global_pattern_total))
# Show what we're searching for
print_searching "${descriptions[$i]}" "$global_current" "$global_total"
# Search for the pattern and get count
PATTERN_MATCH_COUNT=0
search_pattern "${patterns[$i]}" "${descriptions[$i]}" "${fixes[$i]}" "$color" "$severity"
local count=$PATTERN_MATCH_COUNT
# Add to the total
total=$((total + count))
# Update statistics by severity
if [[ "$severity" == "CRITICAL" ]]; then
critical_total=$((critical_total + count))
elif [[ "$severity" == "HIGH" ]]; then
high_total=$((high_total + count))
elif [[ "$severity" == "MEDIUM" ]]; then
medium_total=$((medium_total + count))
fi
done
# Update the global pattern counter
global_pattern_current=$((global_pattern_current + ${#patterns[@]}))
# Make sure we return a valid total even if something goes wrong
if [[ $total -lt 0 ]]; then
total=0
fi
return $total
}
# =================================================================
# MAIN EXECUTION
# =================================================================
# Initialize global variables and counters for statistics
PATTERN_MATCH_COUNT=0
critical_total=0
high_total=0
medium_total=0
global_pattern_current=0
global_pattern_total=$((${#critical_patterns[@]} + ${#high_patterns[@]} + ${#medium_patterns[@]}))
# Print banner
echo -e "${BRIGHT_RED}${BOLD}"
echo " __ __ _ _____ __ __ _ _ _ "
echo " | \/ | ___| |_ ___ ___ _ __ |___ / | \/ (_) __ _ _ __ __ _| |_(_) ___ _ __ "
echo " | |\/| |/ _ \ __/ _ \/ _ \| '__| |_ \ | |\/| | |/ _\` | '__/ _\` | __| |/ _ \| '_ \ "
echo " | | | | __/ || __/ (_) | | ___) | | | | | | (_| | | | (_| | |_| | (_) | | | |"
echo " |_| |_|\___|\__\___|\___/|_| |____/___|_| |_|_|\__, |_| \__,_|\__|_|\___/|_| |_|"
echo " |_____| |___/ "
echo -e "${NC}"
echo -e "${BLUE}${BOLD}A comprehensive Meteor 3.0 migration analysis tool${NC}"
echo -e "${GRAY}Analyzes your Meteor codebase for deprecated functions and patterns${NC}"
echo
# Check for required tools
check_requirements
# Show scan information
echo -e "${BLUE}${INFO} Scan Information:${NC}"
echo -e " ${GRAY}Directory:${NC} ${CYAN}$SCAN_DIR${NC}"
# Count files to scan
file_count=$(count_files)
echo -e " ${GRAY}Files to scan:${NC} ${CYAN}$file_count${NC} JavaScript/TypeScript files"
echo -e " ${GRAY}Patterns to check:${NC} ${CYAN}$global_pattern_total${NC} deprecated functions and patterns"
echo
# Begin scan
print_heading "BEGINNING SCAN"
echo -e "${BLUE}${ROCKET} Starting comprehensive scan for Meteor 3.0 migration issues...${NC}"
echo
# Process each category of patterns
print_heading "CRITICAL ISSUES (Must Fix)"
echo -e "${RED}These issues will break your application in Meteor 3.0${NC}"
process_patterns critical_patterns critical_descriptions critical_fixes "$RED" "CRITICAL"
critical_count=$?
print_heading "HIGH PRIORITY ISSUES (Should Fix)"
echo -e "${YELLOW}These issues are important to address for Meteor 3.0${NC}"
process_patterns high_patterns high_descriptions high_fixes "$YELLOW" "HIGH"
high_count=$?
print_heading "MEDIUM PRIORITY ISSUES (Consider Fixing)"
echo -e "${BLUE}These issues are recommended to address but may not break functionality${NC}"
process_patterns medium_patterns medium_descriptions medium_fixes "$BLUE" "MEDIUM"
medium_count=$?
# Make sure totals are valid
if [[ $critical_total -lt 0 ]]; then critical_total=0; fi
if [[ $high_total -lt 0 ]]; then high_total=0; fi
if [[ $medium_total -lt 0 ]]; then medium_total=0; fi
# Calculate total issues
total_issues=$((critical_total + high_total + medium_total))
# Calculate execution time
END_TIME=$(date +%s)
EXECUTION_TIME=$((END_TIME - START_TIME))
# Print summary
print_heading "SCAN COMPLETE"
echo -e "${GREEN}${CHECK} Scan completed in ${CYAN}${EXECUTION_TIME}${NC} seconds"
echo
echo -e "${BLUE}${INFO} Summary:${NC}"
echo -e " ${GRAY}Files scanned:${NC} ${CYAN}$file_count${NC}"
echo -e " ${GRAY}Patterns checked:${NC} ${CYAN}$global_pattern_total${NC}"
echo -e " ${GRAY}Total issues found:${NC} ${CYAN}$total_issues${NC}"
echo
echo -e " ${RED}Critical issues:${NC} ${CYAN}$critical_total${NC} ${GRAY}(Must fix for Meteor 3.0)${NC}"
echo -e " ${YELLOW}High priority issues:${NC} ${CYAN}$high_total${NC} ${GRAY}(Should fix for best practices)${NC}"
echo -e " ${BLUE}Medium priority issues:${NC} ${CYAN}$medium_total${NC} ${GRAY}(Consider fixing)${NC}"
echo
# Add migration advice based on findings
if [ $total_issues -eq 0 ]; then
echo -e "${GREEN}${CHECK} Great news! No migration issues found. Your app appears ready for Meteor 3.0.${NC}"
elif [ $critical_total -eq 0 ]; then
echo -e "${YELLOW}${WARN} Your app has some non-critical issues but may work with Meteor 3.0.${NC}"
echo -e "${YELLOW}${LIGHTBULB} Recommendation: Address the high priority issues for better compatibility.${NC}"
else
echo -e "${RED}${CROSS} Your app needs updates before it will work with Meteor 3.0.${NC}"
echo -e "${RED}${LIGHTBULB} Focus first on fixing the ${CYAN}$critical_total${NC} ${RED}critical issues.${NC}"
fi
echo
echo -e "${BLUE}${INFO} Next Steps:${NC}"
echo -e " ${GRAY}1. Fix critical issues first (red items)${NC}"
echo -e " ${GRAY}2. Address high priority issues (yellow items)${NC}"
echo -e " ${GRAY}3. Consider fixing medium priority issues (blue items)${NC}"
echo -e " ${GRAY}4. Run Meteor with ${CYAN}--release 3.3.0${NC} ${GRAY}to test${NC}"
echo
echo -e "${BLUE}${INFO} For more information, see:${NC}"
echo -e " ${CYAN}https://docs.meteor.com/v3-migration-guide${NC}"
echo
# Exit with status code based on findings (helpful for CI/CD pipelines)
if [ $critical_total -gt 0 ]; then
exit 1 # Critical issues found
elif [ $total_issues -gt 0 ]; then
exit 0 # Issues found but not critical
else
exit 0 # No issues found
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment