#!/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"