go-template/rename.sh

192 lines
7.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# rename.sh — rename this template to your actual project.
#
# Usage:
# ./rename.sh # interactive prompts
# ./rename.sh acme-corp my-svc # non-interactive (org, project)
#
# What it changes:
# go.mod module path
# **/*.go import paths
# .mockery.yaml package path
# config/dev.yaml app.name
# .devcontainer/devcontainer.json name field
# README.md heading + module path references
# CLAUDE.md Module line
set -euo pipefail
# ── Colour helpers ────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "${GREEN}${RESET} $*"; }
warn() { echo -e "${YELLOW}!${RESET} $*"; }
error() { echo -e "${RED}${RESET} $*" >&2; }
heading() { echo -e "\n${BOLD}$*${RESET}"; }
# ── Default Git host ──────────────────────────────────────────────────────────
# Change this if you ever migrate to a different server.
DEFAULT_HOST="gitea.djmil.dev"
# ── Validation ────────────────────────────────────────────────────────────────
validate_slug() {
local val="$1" label="$2"
if [[ -z "$val" ]]; then
error "$label cannot be empty."
return 1
fi
if [[ ! "$val" =~ ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$ ]]; then
error "$label must be lowercase alphanumeric (hyphens/dots/underscores allowed, no leading/trailing)."
return 1
fi
}
# ── Convert kebab-case / snake_case to Title Case ─────────────────────────────
to_title() {
echo "$1" | sed -E 's/[-_]/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1'
}
# ── Determine script's own directory (works with symlinks) ───────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# ── Check we're in the right repo ────────────────────────────────────────────
if [[ ! -f go.mod ]]; then
error "go.mod not found. Run this script from the project root."
exit 1
fi
CURRENT_MODULE=$(grep '^module ' go.mod | awk '{print $2}')
TEMPLATE_MODULE="${DEFAULT_HOST}/djmil/go-template"
if [[ "$CURRENT_MODULE" != "$TEMPLATE_MODULE" ]]; then
warn "Module is already '$CURRENT_MODULE' (not the default template value)."
warn "Continuing will replace '$CURRENT_MODULE' with your new path."
echo
fi
# ── Gather inputs ─────────────────────────────────────────────────────────────
heading "Go Template — Project Renamer"
echo "This script rewrites the module path and project name throughout the codebase."
echo
if [[ $# -ge 2 ]]; then
NEW_ORG="$1"
NEW_PROJECT="$2"
else
while true; do
read -rp "Org / username (e.g. djmil): " NEW_ORG
validate_slug "$NEW_ORG" "Org/username" && break
done
while true; do
read -rp "Project name (e.g. my-service): " NEW_PROJECT
validate_slug "$NEW_PROJECT" "Project name" && break
done
fi
validate_slug "$NEW_ORG" "Org/username"
validate_slug "$NEW_PROJECT" "Project name"
NEW_MODULE="${DEFAULT_HOST}/${NEW_ORG}/${NEW_PROJECT}"
OLD_MODULE="$CURRENT_MODULE"
OLD_PROJECT=$(basename "$OLD_MODULE") # e.g. go-template
NEW_DISPLAY=$(to_title "$NEW_PROJECT") # e.g. My Service
OLD_DISPLAY=$(to_title "$OLD_PROJECT") # e.g. Go Template
# ── Preview ───────────────────────────────────────────────────────────────────
heading "Changes to be applied"
printf " %-22s %s → %s\n" "Module path:" "$OLD_MODULE" "$NEW_MODULE"
printf " %-22s %s → %s\n" "Project name:" "$OLD_PROJECT" "$NEW_PROJECT"
printf " %-22s %s → %s\n" "Display name:" "$OLD_DISPLAY" "$NEW_DISPLAY"
echo
if [[ $# -lt 2 ]]; then
read -rp "Apply these changes? [y/N] " CONFIRM
case "$CONFIRM" in
[yY][eE][sS]|[yY]) ;;
*) echo "Aborted."; exit 0 ;;
esac
fi
# ── Helper: portable in-place sed ────────────────────────────────────────────
# macOS sed requires an extension argument for -i; GNU sed does not.
sedi() {
if sed --version &>/dev/null 2>&1; then
# GNU sed
sed -i "$@"
else
# BSD / macOS sed
sed -i '' "$@"
fi
}
# ── Apply substitutions ───────────────────────────────────────────────────────
heading "Applying changes"
# 1. go.mod — module declaration
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" go.mod
info "go.mod"
# 2. All Go source files — import paths
GO_FILES=$(find . \
-not -path './.git/*' \
-not -path './bin/*' \
-name '*.go' \
-type f)
CHANGED_GO=0
for f in $GO_FILES; do
if grep -q "$OLD_MODULE" "$f" 2>/dev/null; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" "$f"
CHANGED_GO=$((CHANGED_GO + 1))
fi
done
info "${CHANGED_GO} Go source file(s)"
# 3. .mockery.yaml — package path
if [[ -f .mockery.yaml ]]; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" .mockery.yaml
info ".mockery.yaml"
fi
# 4. config/dev.yaml — app.name
if [[ -f config/dev.yaml ]]; then
sedi "s|name: \"${OLD_PROJECT}\"|name: \"${NEW_PROJECT}\"|g" config/dev.yaml
info "config/dev.yaml"
fi
# 5. .devcontainer/devcontainer.json — "name" field
if [[ -f .devcontainer/devcontainer.json ]]; then
sedi "s|\"name\": \"${OLD_DISPLAY}\"|\"name\": \"${NEW_DISPLAY}\"|g" \
.devcontainer/devcontainer.json
info ".devcontainer/devcontainer.json"
fi
# 6. README.md — heading + all module path occurrences
if [[ -f README.md ]]; then
sedi "s|^# ${OLD_PROJECT}$|# ${NEW_PROJECT}|g" README.md
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" README.md
sedi "s|name: \"${OLD_PROJECT}\"|name: \"${NEW_PROJECT}\"|g" README.md
info "README.md"
fi
# 7. CLAUDE.md — Module line
if [[ -f CLAUDE.md ]]; then
sedi "s|${OLD_MODULE}|${NEW_MODULE}|g" CLAUDE.md
info "CLAUDE.md"
fi
# ── Post-rename suggestions ───────────────────────────────────────────────────
heading "Done"
echo "Module is now: ${BOLD}${NEW_MODULE}${RESET}"
echo
echo "Recommended next steps:"
echo " go mod tidy # sync go.sum after path change"
echo " make mocks # regenerate mocks with new import paths"
echo " make build # verify it compiles"
echo " make test # verify tests pass"
echo
warn "If you renamed the directory on disk, update git remote too:"
echo " git remote set-url origin git@${DEFAULT_HOST}:${NEW_ORG}/${NEW_PROJECT}.git"