build.gradle.kts 6.3 KB
// distribution — assembles the runnable vibe_erp fat-jar.
//
// This module is the only one that pulls every PBC and platform module
// together into a bootable Spring Boot application. It is referenced by
// the Dockerfile (stage 1) which produces the shipping image documented
// in the architecture spec, sections 10 and 11.
//
// **Web SPA bundling.** The `:web` subproject is a Gradle wrapper around
// the Vite/React build. We declare a `webStaticBundle` consumer
// configuration against it; the `bundleWebStatic` Sync task copies
// the dist/ directory into `${buildDir}/web-static/static/` and that
// parent directory is added to the main resources source set so Spring
// Boot's `classpath:/static/**` resource resolver picks the SPA up at
// runtime. The result is a single fat-jar containing both the JVM
// backend and the SPA — no nginx, no separate static-file server, no
// CORS.

plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.spring)
    alias(libs.plugins.spring.boot)
    alias(libs.plugins.spring.dependency.management)
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

dependencies {
    implementation(project(":platform:platform-bootstrap"))
    implementation(project(":platform:platform-persistence"))
    implementation(project(":platform:platform-plugins"))
    implementation(project(":platform:platform-security"))
    implementation(project(":platform:platform-events"))
    implementation(project(":platform:platform-metadata"))
    implementation(project(":platform:platform-i18n"))
    implementation(project(":platform:platform-workflow"))
    implementation(project(":platform:platform-jobs"))
    implementation(project(":platform:platform-files"))
    implementation(project(":platform:platform-reports"))
    implementation(project(":pbc:pbc-identity"))
    implementation(project(":pbc:pbc-catalog"))
    implementation(project(":pbc:pbc-partners"))
    implementation(project(":pbc:pbc-inventory"))
    implementation(project(":pbc:pbc-warehousing"))
    implementation(project(":pbc:pbc-orders-sales"))
    implementation(project(":pbc:pbc-orders-purchase"))
    implementation(project(":pbc:pbc-finance"))
    implementation(project(":pbc:pbc-production"))
    implementation(project(":pbc:pbc-quality"))

    implementation(libs.spring.boot.starter)
    implementation(libs.spring.boot.starter.web)
    implementation(libs.spring.boot.starter.data.jpa)
    implementation(libs.spring.boot.starter.actuator)
    implementation(libs.kotlin.stdlib)
    implementation(libs.kotlin.reflect)
    implementation(libs.jackson.module.kotlin)
    runtimeOnly(libs.postgres)
    runtimeOnly(libs.liquibase.core)

    testImplementation(libs.spring.boot.starter.test)
}

// Configure Spring Boot's main class once, on the `springBoot` extension.
// This is the only place that drives BOTH bootJar and bootRun — setting
// mainClass on `tasks.bootJar` alone leaves `bootRun` unable to resolve
// the entry point because :distribution has no Kotlin sources of its own.
springBoot {
    mainClass.set("org.vibeerp.platform.bootstrap.VibeErpApplicationKt")
    // buildInfo() writes build metadata (group, artifact, version, build
    // timestamp) into META-INF/build-info.properties at build time. Spring
    // Boot's BuildInfoAutoConfiguration picks it up at runtime and exposes
    // it as a BuildProperties bean that MetaController injects to fill in
    // a real version/build time on GET /api/v1/_meta/info. Without this
    // the fallback in MetaController was stuck at "0.1.0-SNAPSHOT".
    buildInfo()
}

// The fat-jar produced here is what the Dockerfile copies into the
// runtime image as /app/vibe-erp.jar.
tasks.bootJar {
    archiveFileName.set("vibe-erp.jar")
}

// `./gradlew :distribution:bootRun` — used by `make run` for local dev.
// Activates the dev profile so application-dev.yaml on the classpath is
// layered on top of application.yaml. Also depends on the reference
// plug-in's `installToDev` task so the JAR is staged in `plugins-dev/`
// before the app boots — that's where application-dev.yaml's
// `vibeerp.plugins.directory: ./plugins-dev` setting points.
tasks.bootRun {
    systemProperty("spring.profiles.active", "dev")
    // Run with the repo root as the working directory so the relative
    // paths in application-dev.yaml (./plugins-dev, ./files-dev) resolve
    // to the right place — the plug-in JAR is staged at
    // <repo>/plugins-dev/, not <repo>/distribution/plugins-dev/.
    workingDir = rootProject.layout.projectDirectory.asFile
    dependsOn(":reference-customer:plugin-printing-shop:installToDev")
    // Make sure the SPA dist/ is staged into the classpath before
    // Spring Boot starts; otherwise an in-IDE bootRun with no prior
    // build serves a 404 at the root path. processResources already
    // depends on bundleWebStatic, but listing it explicitly here
    // makes the relationship visible to anyone reading bootRun.
    dependsOn("bundleWebStatic")
}

// ─── Web SPA bundling ────────────────────────────────────────────────
//
// Resolvable configuration that pulls the `webStaticBundle` artifact
// from `:web`. The `:web` subproject's outgoing configuration declares
// the dist/ directory as its single artifact, built by the npmBuild
// Exec task. We don't need attribute matchers because there's only
// one artifact in the configuration.
val webStaticBundle: Configuration by configurations.creating {
    isCanBeConsumed = false
    isCanBeResolved = true
}

dependencies {
    webStaticBundle(project(mapOf("path" to ":web", "configuration" to "webStaticBundle")))
}

// Stage the SPA dist into ${buildDir}/web-static/static/ so the
// directory layout matches Spring Boot's classpath:/static/ convention.
// Adding the parent dir as a resource source set is what then makes
// the JAR's classpath include those files at the right path.
val bundleWebStatic by tasks.registering(Sync::class) {
    group = "build"
    description = "Stage the :web SPA bundle into the distribution resources tree."
    from({ webStaticBundle })
    into(layout.buildDirectory.dir("web-static/static"))
}

sourceSets.named("main") {
    resources.srcDir(layout.buildDirectory.dir("web-static"))
}

tasks.named("processResources") {
    dependsOn(bundleWebStatic)
}