Last active
October 23, 2025 23:12
-
-
Save sametcn99/d338d92eb5f6f8981ccbceb2db61c9d7 to your computer and use it in GitHub Desktop.
A fully automated Bash script to migrate from ESLint + Prettier to Biome. It initializes Biome, migrates ESLint and Prettier configurations, removes old config files, and updates package.json scripts. Designed for Bun environments with full Next.js ESLint compatibility.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # migrate-to-biome.sh | |
| # --------------------------------------------------------------------------------------------------- | |
| # π§ Purpose: | |
| # Fully automate the migration from ESLint + Prettier β Biome using Bun. | |
| # | |
| # π§ What it does: | |
| # 1. Initializes Biome (if missing) | |
| # 2. Migrates ESLint & Prettier configurations safely (handles Next.js) | |
| # 3. Removes all ESLint/Prettier config files (including eslint.config.mjs) | |
| # 4. Detects and removes old lint/format scripts | |
| # 5. Removes all ESLint & Prettier dependencies | |
| # 6. Installs Biome as devDependency | |
| # 7. Adds Biome lint & format scripts with --write flag | |
| # | |
| # π§© Requirements: | |
| # - Bun installed and accessible (`bunx`, `bun add`, `bun remove`) | |
| # - jq installed (for JSON editing) | |
| # | |
| # --------------------------------------------------------------------------------------------------- | |
| # Β© 2025 Samet β’ MIT License | |
| # --------------------------------------------------------------------------------------------------- | |
| set -euo pipefail | |
| IFS=$'\n\t' | |
| # ----------------------------- # | |
| # π Helper functions | |
| # ----------------------------- # | |
| log() { echo -e "\033[36m[INFO]\033[0m $*"; } | |
| warn() { echo -e "\033[33m[WARN]\033[0m $*"; } | |
| error() { echo -e "\033[31m[ERROR]\033[0m $*" >&2; } | |
| success() { echo -e "\033[32m[SUCCESS]\033[0m $*"; } | |
| require() { | |
| if ! command -v "$1" &>/dev/null; then | |
| error "Missing required command: $1" | |
| exit 1 | |
| fi | |
| } | |
| # ----------------------------- # | |
| # π§ Pre-flight checks | |
| # ----------------------------- # | |
| require bunx | |
| require jq | |
| if [[ ! -f package.json ]]; then | |
| error "package.json not found in current directory." | |
| exit 1 | |
| fi | |
| # ----------------------------- # | |
| # βοΈ Run Biome migrations | |
| # ----------------------------- # | |
| log "Running Biome migration commands..." | |
| # 1. Initialize Biome if not found | |
| if [[ ! -f biome.json && ! -f biome.config.json ]]; then | |
| log "No Biome config found. Initializing Biome..." | |
| bunx @biomejs/biome init || true | |
| success "Biome initialized." | |
| fi | |
| # 2. Handle Next.js ESLint configs safely | |
| TEMP_ESLINT_DIR=".tmp_eslint_migrate" | |
| mkdir -p "$TEMP_ESLINT_DIR" | |
| RESTORE_ESLINT=false | |
| if [[ -f "eslint.config.mjs" ]]; then | |
| log "Temporarily patching Next.js ESLint config to avoid @rushstack crashes..." | |
| cp eslint.config.mjs "$TEMP_ESLINT_DIR/eslint.config.mjs" | |
| echo "export default {};" > eslint.config.mjs | |
| RESTORE_ESLINT=true | |
| elif [[ -f "eslint.config.js" ]]; then | |
| log "Temporarily patching eslint.config.js..." | |
| cp eslint.config.js "$TEMP_ESLINT_DIR/eslint.config.js" | |
| echo "module.exports = {};" > eslint.config.js | |
| RESTORE_ESLINT=true | |
| fi | |
| # 3. Run migration commands | |
| bunx @biomejs/biome migrate eslint --write || warn "ESLint migration encountered non-critical issues." | |
| bunx @biomejs/biome migrate prettier --write || warn "Prettier migration encountered non-critical issues." | |
| success "Biome migration steps completed." | |
| # 4. Restore ESLint config temporarily, then remove permanently | |
| if [[ "$RESTORE_ESLINT" == true ]]; then | |
| log "Restoring original eslint.config.* for cleanup..." | |
| mv "$TEMP_ESLINT_DIR"/* . || true | |
| rm -rf "$TEMP_ESLINT_DIR" | |
| fi | |
| # ----------------------------- # | |
| # π§Ή Clean up old config files | |
| # ----------------------------- # | |
| log "Cleaning up old ESLint/Prettier config files..." | |
| FILES_TO_DELETE=( | |
| "eslint.config.js" "eslint.config.mjs" | |
| ".eslintrc.js" ".eslintrc.cjs" ".eslintrc.json" ".eslintrc.yaml" ".eslintrc.yml" | |
| ".eslintignore" | |
| ".prettierrc" ".prettierrc.js" ".prettierrc.json" ".prettierrc.yaml" ".prettierrc.yml" | |
| "prettier.config.js" "prettier.config.cjs" "prettier.config.mjs" | |
| ".prettierignore" | |
| ) | |
| for file in "${FILES_TO_DELETE[@]}"; do | |
| if [[ -f "$file" ]]; then | |
| rm -f "$file" | |
| echo "ποΈ Deleted $file" | |
| fi | |
| done | |
| success "Old config files cleaned (including eslint.config.*)." | |
| # ----------------------------- # | |
| # π¦ Remove ESLint & Prettier dependencies | |
| # ----------------------------- # | |
| log "Scanning for ESLint/Prettier dependencies to remove..." | |
| REMOVE_PACKAGES=$(jq -r ' | |
| (.devDependencies // {} + .dependencies // {}) | |
| | keys | |
| | map(select(test("eslint|prettier"; "i"))) | |
| | join(" ") | |
| ' package.json) | |
| if [[ -n "$REMOVE_PACKAGES" ]]; then | |
| log "Removing old linting packages: $REMOVE_PACKAGES" | |
| bun remove $REMOVE_PACKAGES || warn "Some packages could not be removed." | |
| else | |
| log "No ESLint/Prettier dependencies found." | |
| fi | |
| # ----------------------------- # | |
| # π₯ Install Biome | |
| # ----------------------------- # | |
| log "Installing Biome as devDependency..." | |
| bun add -d @biomejs/biome | |
| success "Biome installed successfully." | |
| # ----------------------------- # | |
| # π§© Update package.json scripts | |
| # ----------------------------- # | |
| log "Scanning package.json for ESLint / Prettier scripts..." | |
| mapfile -t MATCHES < <(jq -r '.scripts | to_entries[] | select(.value | test("eslint|prettier"; "i")) | "\(.key): \(.value)"' package.json) | |
| if [[ ${#MATCHES[@]} -eq 0 ]]; then | |
| log "No ESLint/Prettier scripts found." | |
| else | |
| echo -e "\nπ§Ή Possible ESLint/Prettier scripts found:\n" | |
| for i in "${!MATCHES[@]}"; do | |
| printf " [%d] %s\n" $((i + 1)) "${MATCHES[$i]}" | |
| done | |
| echo -e "\nEnter numbers (comma-separated) of scripts to delete, or press Enter to skip:" | |
| read -r -p "> " SELECTION | |
| if [[ -n "$SELECTION" ]]; then | |
| for index in $(echo "$SELECTION" | tr ',' ' '); do | |
| if [[ "$index" =~ ^[0-9]+$ ]] && (( index >= 1 && index <= ${#MATCHES[@]} )); then | |
| NAME=$(echo "${MATCHES[$((index-1))]}" | cut -d: -f1 | xargs) | |
| jq "del(.scripts.\"$NAME\")" package.json > package.tmp.json && mv package.tmp.json package.json | |
| echo "ποΈ Removed script: $NAME" | |
| fi | |
| done | |
| else | |
| warn "Skipped script removal." | |
| fi | |
| fi | |
| # 5. Add Biome scripts with --write flag | |
| log "Adding Biome lint & format scripts..." | |
| jq '.scripts.lint = "biome lint --write" | .scripts.format = "biome format --write"' package.json > package.tmp.json && mv package.tmp.json package.json | |
| success "Updated package.json with biome scripts (with --write)." | |
| # ----------------------------- # | |
| # π― Final Notes | |
| # ----------------------------- # | |
| echo -e "\nπ \033[1mMigration complete!\033[0m" | |
| echo "β ESLint & Prettier fully removed (including eslint.config.* files)." | |
| echo "β Biome installed and configured with auto-fix scripts." | |
| echo "β‘οΈ Review biome.json for final rule adjustments." | |
| echo "β‘οΈ You can now run:" | |
| echo " bun run lint" | |
| echo " bun run format" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment