// 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 // /plugins-dev/, not /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) }