# Build Script Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Create an interactive bash build script that lets the user build signed release APKs without Android Studio.

**Architecture:** A single `build.sh` at the project root. Organized as functions called from a main menu loop. Keystore management, signing, and APK output are self-contained flows. No modifications to `build.gradle.kts` — signing is injected via Gradle CLI properties.

**Tech Stack:** Bash, `keytool` (JDK), Gradle wrapper (`./gradlew`)

---

### Task 1: Update .gitignore

**Files:**
- Modify: `.gitignore`

**Step 1: Add dist/ and keystore/ entries**

Add these lines to `.gitignore` after the existing keystore section:

```gitignore
# Build script output
dist/
keystore/
```

Note: `*.jks` and `*.keystore` are already gitignored, but `keystore/` covers the directory and any non-key files in it. `dist/` prevents built APKs from being committed (backup to `*.apk` already being ignored).

**Step 2: Verify**

Run: `echo "test" > dist/test && git status`
Expected: `dist/` does not appear in untracked files.

**Step 3: Clean up and commit**

```bash
rm -rf dist/
git add .gitignore
git commit -m "chore: gitignore dist/ and keystore/ directories"
```

---

### Task 2: Create build.sh scaffold

**Files:**
- Create: `build.sh`

**Step 1: Create the script with shebang, color constants, and banner function**

```bash
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m' # No Color

# --- Config ---
KEYSTORE_DIR="keystore"
KEYSTORE_FILE="$KEYSTORE_DIR/radio247-release.jks"
KEY_ALIAS="radio247"
DIST_DIR="dist"
GRADLE_BUILD_FILE="app/build.gradle.kts"

banner() {
    echo ""
    echo -e "${CYAN}╔══════════════════════════════════╗${NC}"
    echo -e "${CYAN}║${BOLD}     Radio 247 Build Tool         ${NC}${CYAN}║${NC}"
    echo -e "${CYAN}╚══════════════════════════════════╝${NC}"
    echo ""
}

info()    { echo -e "${BLUE}▸${NC} $1"; }
success() { echo -e "${GREEN}✓${NC} $1"; }
warn()    { echo -e "${YELLOW}⚠${NC} $1"; }
error()   { echo -e "${RED}✗${NC} $1"; }

main_menu() {
    echo -e "${BOLD}What would you like to do?${NC}"
    echo ""
    echo -e "  ${BOLD}1)${NC} Build Release APK  ${DIM}(signed, shareable)${NC}"
    echo -e "  ${BOLD}2)${NC} Build Debug APK    ${DIM}(quick, for testing)${NC}"
    echo -e "  ${BOLD}3)${NC} Manage Keystore    ${DIM}(create or check signing key)${NC}"
    echo -e "  ${BOLD}4)${NC} Clean              ${DIM}(remove build artifacts)${NC}"
    echo -e "  ${BOLD}5)${NC} Exit"
    echo ""

    local choice
    read -rp "> " choice
    case $choice in
        1) build_release ;;
        2) build_debug ;;
        3) manage_keystore ;;
        4) clean_build ;;
        5) echo ""; exit 0 ;;
        *) warn "Invalid choice. Pick 1-5." ;;
    esac
}

# Placeholder functions — implemented in subsequent tasks
build_release()    { warn "Not yet implemented"; }
build_debug()      { warn "Not yet implemented"; }
manage_keystore()  { warn "Not yet implemented"; }
clean_build()      { warn "Not yet implemented"; }

# --- Main ---
banner
while true; do
    main_menu
    echo ""
done
```

**Step 2: Make executable and test**

Run: `chmod +x build.sh && ./build.sh`
Expected: Banner displays, menu shows 5 options, typing 1-4 shows "Not yet implemented", typing 5 exits.

**Step 3: Commit**

```bash
git add build.sh
git commit -m "feat: add build.sh scaffold with menu"
```

---

### Task 3: Implement helper functions

**Files:**
- Modify: `build.sh`

**Step 1: Add version extraction function**

Replace the placeholder section with these helpers (place them after the config block, before `banner()`):

```bash
get_version_name() {
    grep 'versionName' "$GRADLE_BUILD_FILE" | head -1 | sed 's/.*"\(.*\)".*/\1/'
}

preflight_check() {
    local ok=true

    if ! command -v java &>/dev/null && [[ -z "${JAVA_HOME:-}" ]]; then
        error "Java not found. Install JDK 17+ or set JAVA_HOME."
        ok=false
    fi

    if [[ ! -x "./gradlew" ]]; then
        error "Gradle wrapper not found or not executable."
        ok=false
    fi

    if [[ "$ok" != true ]]; then
        exit 1
    fi
}

ensure_dist_dir() {
    mkdir -p "$DIST_DIR"
}
```

**Step 2: Add preflight call to main**

Update the main section at the bottom to call `preflight_check` before the loop:

```bash
# --- Main ---
preflight_check
banner
while true; do
    main_menu
    echo ""
done
```

**Step 3: Verify version extraction**

Run: `source build.sh` is not ideal for testing functions, so test inline:

```bash
grep 'versionName' app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/'
```

Expected: `1.0`

**Step 4: Commit**

```bash
git add build.sh
git commit -m "feat: add preflight checks and version extraction"
```

---

### Task 4: Implement keystore creation

**Files:**
- Modify: `build.sh`

**Step 1: Replace the `manage_keystore` placeholder**

```bash
manage_keystore() {
    if [[ -f "$KEYSTORE_FILE" ]]; then
        success "Keystore exists at ${BOLD}$KEYSTORE_FILE${NC}"
        echo ""
        info "Keystore details:"
        keytool -list -keystore "$KEYSTORE_FILE" -alias "$KEY_ALIAS" -storepass "$(read_keystore_password)" 2>/dev/null || warn "Could not read keystore (wrong password?)"
        return
    fi

    create_keystore
}

read_keystore_password() {
    local password
    read -rsp "Keystore password: " password
    echo >&2
    echo "$password"
}

create_keystore() {
    echo -e "${BOLD}No keystore found. Let's create one.${NC}"
    echo ""

    local cn org password password_confirm

    read -rp "Your name (for the certificate): " cn
    [[ -z "$cn" ]] && { error "Name is required."; return 1; }

    read -rp "Organization (optional, Enter to skip): " org

    while true; do
        read -rsp "Password (min 6 chars): " password
        echo
        if [[ ${#password} -lt 6 ]]; then
            warn "Password must be at least 6 characters."
            continue
        fi
        read -rsp "Confirm password: " password_confirm
        echo
        if [[ "$password" != "$password_confirm" ]]; then
            warn "Passwords don't match. Try again."
            continue
        fi
        break
    done

    mkdir -p "$KEYSTORE_DIR"

    local dname="CN=$cn"
    [[ -n "$org" ]] && dname="$dname, O=$org"

    info "Creating keystore..."

    keytool -genkeypair \
        -alias "$KEY_ALIAS" \
        -keyalg RSA \
        -keysize 2048 \
        -validity 10000 \
        -keystore "$KEYSTORE_FILE" \
        -storepass "$password" \
        -keypass "$password" \
        -dname "$dname" \
        2>/dev/null

    if [[ -f "$KEYSTORE_FILE" ]]; then
        echo ""
        success "Keystore created at ${BOLD}$KEYSTORE_FILE${NC}"
        echo ""
        warn "IMPORTANT: Back up this file and remember your password."
        warn "If you lose either, you cannot update the app on devices"
        warn "that already have this version installed."
    else
        error "Keystore creation failed."
        return 1
    fi
}
```

**Step 2: Test keystore creation**

Run: `./build.sh` → pick option 3
Expected: Prompts for name, org, password. Creates `keystore/radio247-release.jks`.

**Step 3: Test existing keystore detection**

Run: `./build.sh` → pick option 3 again
Expected: Shows "Keystore exists" message.

**Step 4: Clean up test keystore and commit**

```bash
rm -rf keystore/
git add build.sh
git commit -m "feat: add keystore creation and management"
```

---

### Task 5: Implement release build

**Files:**
- Modify: `build.sh`

**Step 1: Replace the `build_release` placeholder**

```bash
build_release() {
    info "Preparing release build..."
    echo ""

    if [[ ! -f "$KEYSTORE_FILE" ]]; then
        warn "No keystore found. You need one to sign a release APK."
        echo ""
        local yn
        read -rp "Create one now? (y/n) " yn
        case $yn in
            [Yy]*) create_keystore || return 1 ;;
            *)     return 0 ;;
        esac
        echo ""
    fi

    local password
    read -rsp "Keystore password: " password
    echo ""
    echo ""

    info "Building release APK... (this may take a minute)"
    echo ""

    local store_file
    store_file="$(cd "$(dirname "$KEYSTORE_FILE")" && pwd)/$(basename "$KEYSTORE_FILE")"

    if ./gradlew assembleRelease \
        -Pandroid.injected.signing.store.file="$store_file" \
        -Pandroid.injected.signing.store.password="$password" \
        -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
        -Pandroid.injected.signing.key.password="$password"; then

        copy_apk "release"
    else
        echo ""
        error "Build failed. Check the output above for details."
        return 1
    fi
}

copy_apk() {
    local build_type="$1"
    local version
    version="$(get_version_name)"

    local source_apk="app/build/outputs/apk/$build_type/app-$build_type.apk"

    if [[ ! -f "$source_apk" ]]; then
        error "APK not found at $source_apk"
        return 1
    fi

    ensure_dist_dir

    local dest_name="radio247-v${version}-${build_type}.apk"
    local dest_path="$DIST_DIR/$dest_name"

    cp "$source_apk" "$dest_path"

    local size
    size="$(du -h "$dest_path" | cut -f1 | xargs)"

    echo ""
    success "Build complete!"
    echo ""
    echo -e "  ${BOLD}APK:${NC}  $dest_path"
    echo -e "  ${BOLD}Size:${NC} $size"
    echo ""
    echo -e "  ${DIM}Share this file with anyone — they can install it${NC}"
    echo -e "  ${DIM}on Android 9+ by opening the APK on their device.${NC}"
}
```

**Step 2: Test (requires keystore)**

Run: `./build.sh` → pick option 1
Expected: If no keystore, offers to create one. Then builds, copies APK to `dist/radio247-v1.0-release.apk`.

**Step 3: Commit**

```bash
git add build.sh
git commit -m "feat: add release build with signing"
```

---

### Task 6: Implement debug build and clean

**Files:**
- Modify: `build.sh`

**Step 1: Replace the `build_debug` placeholder**

```bash
build_debug() {
    info "Building debug APK..."
    echo ""

    if ./gradlew assembleDebug; then
        copy_apk "debug"
    else
        echo ""
        error "Build failed. Check the output above for details."
        return 1
    fi
}
```

**Step 2: Replace the `clean_build` placeholder**

```bash
clean_build() {
    info "Cleaning build artifacts..."

    ./gradlew clean 2>/dev/null

    if [[ -d "$DIST_DIR" ]]; then
        rm -rf "$DIST_DIR"
        success "Removed $DIST_DIR/"
    fi

    success "Clean complete."
}
```

**Step 3: Test debug build**

Run: `./build.sh` → pick option 2
Expected: Builds debug APK, copies to `dist/radio247-v1.0-debug.apk`.

**Step 4: Test clean**

Run: `./build.sh` → pick option 4
Expected: Runs `gradle clean`, removes `dist/`, prints "Clean complete."

**Step 5: Commit**

```bash
git add build.sh
git commit -m "feat: add debug build and clean commands"
```

---

### Task 7: End-to-end verification

**Step 1: Fresh clean**

```bash
./build.sh  # pick 4 (Clean)
rm -rf keystore/
```

**Step 2: Full release flow**

```bash
./build.sh  # pick 1 (Build Release APK)
# Should prompt for keystore creation, then build
# Verify: dist/radio247-v1.0-release.apk exists
ls -la dist/
```

**Step 3: Debug build**

```bash
./build.sh  # pick 2 (Build Debug APK)
# Verify: dist/radio247-v1.0-debug.apk exists
ls -la dist/
```

**Step 4: Keystore check**

```bash
./build.sh  # pick 3 (Manage Keystore)
# Should show "Keystore exists" message
```

**Step 5: Final commit if any changes needed**

```bash
git add -A
git commit -m "feat: build script complete"
```
