#!/usr/bin/env bash # init_kotlin_app — Scaffolder canonico de apps Android Kotlin Compose del registry. # # Genera la estructura canonica (MainActivity.kt, build.gradle.kts, app.md, # Roborazzi screenshot tests), apuntando al composite build kotlin/functions/ui # para FnTheme + FnTokens, inicializa git + repo Gitea dataforge/. # # Uso: # init_kotlin_app [--project

] [--desc "..."] [--tags "a,b"] [--package ] # # Por defecto sin proyecto (apps//), package = com.fnregistry.. set -euo pipefail # Carga helpers del registry FN_ROOT="${FN_REGISTRY_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" # shellcheck source=/dev/null source "$FN_ROOT/bash/functions/infra/ensure_repo_synced.sh" init_kotlin_app() { local name="" local project="" local desc="" local tags="" local pkg_id="" while [[ $# -gt 0 ]]; do case "$1" in --project) project="$2"; shift 2 ;; --desc) desc="$2"; shift 2 ;; --tags) tags="$2"; shift 2 ;; --package) pkg_id="$2"; shift 2 ;; -*) echo "init_kotlin_app: flag desconocido: $1" >&2; return 2 ;; *) if [[ -z "$name" ]]; then name="$1"; else echo "init_kotlin_app: argumento extra: $1" >&2; return 2 fi shift ;; esac done if [[ -z "$name" ]]; then echo "init_kotlin_app: se requiere " >&2 echo "Uso: init_kotlin_app [--project

] [--desc \"...\"] [--tags \"a,b\"] [--package ]" >&2 return 2 fi # Validar snake_case if [[ ! "$name" =~ ^[a-z][a-z0-9_]*$ ]]; then echo "init_kotlin_app: nombre '$name' debe ser snake_case (solo letras minusculas, digitos y _)" >&2 return 2 fi [[ -z "$desc" ]] && desc="App Android Kotlin Compose" [[ -z "$pkg_id" ]] && pkg_id="com.fnregistry.$name" # Resolver dir destino local rel_dir abs_dir if [[ -n "$project" ]]; then if [[ ! -f "$FN_ROOT/projects/$project/project.md" ]]; then echo "init_kotlin_app: proyecto '$project' no existe (falta projects/$project/project.md)" >&2 return 1 fi rel_dir="projects/$project/apps/$name" else rel_dir="apps/$name" fi abs_dir="$FN_ROOT/$rel_dir" if [[ -e "$abs_dir" ]]; then echo "init_kotlin_app: $rel_dir ya existe" >&2 return 1 fi # Convertir package id a path (com.fnregistry.my_app -> com/fnregistry/my_app) local pkg_path pkg_path="$(echo "$pkg_id" | tr '.' '/')" # Calcular path relativo al composite build de kotlin/functions/ui # Desde apps// -> ../../kotlin/functions/ui # Desde projects/

/apps/ -> ../../../../kotlin/functions/ui local ui_rel_path if [[ -n "$project" ]]; then ui_rel_path="../../../../kotlin/functions/ui" else ui_rel_path="../../kotlin/functions/ui" fi # ---- Crear estructura de directorios ---- mkdir -p "$abs_dir/app/src/main/kotlin/$pkg_path" mkdir -p "$abs_dir/app/src/main/res/values" mkdir -p "$abs_dir/app/src/test/kotlin/$pkg_path" mkdir -p "$abs_dir/app/src/androidTest/kotlin/$pkg_path" mkdir -p "$abs_dir/gradle/wrapper" echo "init_kotlin_app: creando $rel_dir ..." # ---- settings.gradle.kts ---- cat > "$abs_dir/settings.gradle.kts" < "$abs_dir/build.gradle.kts" <<'EOF' // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id("com.android.application") version "8.4.0" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false } EOF # ---- app/build.gradle.kts ---- cat > "$abs_dir/app/build.gradle.kts" < "$abs_dir/gradle.properties" <<'EOF' org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true kotlin.code.style=official android.nonTransitiveRClass=true EOF # ---- gradle/wrapper/gradle-wrapper.properties ---- cat > "$abs_dir/gradle/wrapper/gradle-wrapper.properties" <<'EOF' distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists EOF # ---- gradlew + wrapper jar (vendored real wrapper) ---- local tmpl_wrapper="$FN_ROOT/bash/functions/pipelines/templates/kotlin/wrapper" if [[ -f "$tmpl_wrapper/gradlew" && -f "$tmpl_wrapper/gradle-wrapper.jar" ]]; then cp "$tmpl_wrapper/gradlew" "$abs_dir/gradlew" cp "$tmpl_wrapper/gradle-wrapper.jar" "$abs_dir/gradle/wrapper/gradle-wrapper.jar" chmod +x "$abs_dir/gradlew" else echo "init_kotlin_app: WARN templates/kotlin/wrapper missing, fallback gradlew stub" cat > "$abs_dir/gradlew" <<'EOF' #!/usr/bin/env bash echo "gradlew stub — install gradle wrapper or replace with real one" >&2 exit 2 EOF chmod +x "$abs_dir/gradlew" fi # ---- local.properties (Android SDK location, gitignored, per-machine) ---- local sdk_path="${ANDROID_SDK_DIR:-$HOME/android-sdk}" if [[ ! -d "$sdk_path" ]] && [[ -d "$HOME/Android/Sdk" ]]; then sdk_path="$HOME/Android/Sdk" fi cat > "$abs_dir/local.properties" < "$abs_dir/app/src/main/AndroidManifest.xml" < EOF # ---- res/values/strings.xml ---- cat > "$abs_dir/app/src/main/res/values/strings.xml" < $name EOF # ---- MainActivity.kt ---- cat > "$abs_dir/app/src/main/kotlin/$pkg_path/MainActivity.kt" < "$abs_dir/app/src/test/kotlin/$pkg_path/ExampleScreenshotTest.kt" < "$abs_dir/app/src/androidTest/kotlin/$pkg_path/MainActivityTest.kt" <() @Test fun appLaunchesAndShowsReadyText() { composeTestRule .onNodeWithText("$name ready") .assertIsDisplayed() } } EOF # ---- app.md frontmatter ---- local repo_url="https://gitea.organic-machine.com/dataforge/$name" local tags_yaml="[kotlin, compose, android]" if [[ -n "$tags" ]]; then tags_yaml="[$(echo "$tags" | sed 's/,/, /g'), kotlin, compose, android]" fi cat > "$abs_dir/app.md" < "$abs_dir/.gitignore" <<'EOF' .gradle/ build/ local.properties *.iml .idea/ captures/ .externalNativeBuild/ .cxx/ *.apk *.aab # NOTE: app/src/test/snapshots/ is committed (Roborazzi goldens are test refs). EOF # ---- README.md ---- cat > "$abs_dir/README.md" <&2 else echo "init_kotlin_app: GITEA_URL/GITEA_TOKEN no seteados, omitiendo creacion de repo Gitea" >&2 (cd "$abs_dir" && git init -b master >/dev/null 2>&1 || git init >/dev/null 2>&1 \ && git add -A \ && git commit -m "feat: scaffold $name via init_kotlin_app" --quiet) fi # ---- fn index si hay proyecto ---- if [[ -n "$project" && -x "$FN_ROOT/fn" ]]; then (cd "$FN_ROOT" && ./fn index >/dev/null 2>&1) || \ echo "init_kotlin_app: warning — fn index fallo" >&2 fi echo "" echo "init_kotlin_app: $rel_dir creada" echo "" echo " Archivos:" echo " $rel_dir/settings.gradle.kts" echo " $rel_dir/app/build.gradle.kts" echo " $rel_dir/app/src/main/kotlin/$pkg_path/MainActivity.kt" echo " $rel_dir/app/src/test/kotlin/$pkg_path/ExampleScreenshotTest.kt" echo " $rel_dir/app/src/androidTest/kotlin/$pkg_path/MainActivityTest.kt" echo " $rel_dir/app.md" echo "" echo " Pasos siguientes:" echo " fn run gradle_unit_test_bash_infra $rel_dir" echo " fn run gradle_screenshot_test_bash_infra $rel_dir" echo " fn run gradle_assemble_debug_bash_infra $rel_dir" echo "" echo " Para tests instrumentados (emulador):" echo " fn run android_emulator_start_bash_infra Medium_Phone_API_35" echo " fn run gradle_instrumented_test_bash_infra $rel_dir" echo " fn run android_emulator_stop_bash_infra" echo "" echo " Package: $pkg_id" echo " FnTheme composite: $ui_rel_path" } # Permitir invocacion directa via 'fn run init_kotlin_app ...' if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then init_kotlin_app "$@" fi