build.gradle.kts 3.18 KB
// vibe_erp web SPA — Gradle wrapper around the npm build.
//
// **Why no Kotlin/JVM source set.** This subproject has zero JVM
// code: every artifact under src/ is TypeScript/React. The Gradle
// presence is purely so `./gradlew build` runs the npm build as
// part of the standard build pipeline, and so :distribution can
// declare a normal project dependency on the produced static
// bundle instead of a fragile path reference.
//
// **Tasks.**
//   - `npmInstall`  → runs `npm install` in web/. Inputs:
//     package.json + package-lock.json. Output: node_modules/.
//   - `npmBuild`    → runs `npm run build` in web/. Inputs:
//     src/**, index.html, vite.config.ts, tsconfig*.json,
//     tailwind.config.js, postcss.config.js. Output: dist/.
//   - `assemble`    → wired to depend on npmBuild so a normal
//     `./gradlew :web:build` produces dist/.
//
// **No npm/node Gradle plugin.** I deliberately avoided
// `com.github.node-gradle.node` to keep the dep surface minimal —
// every plugin is one more thing to keep current with Gradle and
// JDK upgrades. Exec tasks are stable and the inputs/outputs
// declarations give Gradle the same up-to-date checking the plugin
// would.
//
// **Where the bundle goes.** Read by `:distribution` as a normal
// project artifact via the `webStaticBundle` configuration below;
// the consuming module copies dist/ into the Spring Boot
// classpath under `static/` at processResources time so the
// bootJar contains the entire SPA.

import org.gradle.api.tasks.Exec

plugins {
    base
}

description = "vibe_erp web SPA — Vite + React + TS, served by Spring Boot at the root path."

// Resolve npm against the system PATH. Same approach as the
// reference plug-in's installToDev task — keeps developer setup
// simple ("you must have node + npm on PATH").
val npmCommand: String = if (org.gradle.internal.os.OperatingSystem.current().isWindows) "npm.cmd" else "npm"

val npmInstall by tasks.registering(Exec::class) {
    group = "build"
    description = "Run `npm install` in web/."
    workingDir = projectDir
    commandLine(npmCommand, "install", "--no-audit", "--no-fund", "--silent")

    inputs.file("package.json")
    inputs.file("package-lock.json").optional()
    outputs.dir("node_modules")
}

val npmBuild by tasks.registering(Exec::class) {
    group = "build"
    description = "Run `npm run build` in web/. Produces dist/."
    workingDir = projectDir
    commandLine(npmCommand, "run", "build")

    dependsOn(npmInstall)

    inputs.dir("src")
    inputs.file("index.html")
    inputs.file("vite.config.ts")
    inputs.file("tsconfig.json")
    inputs.file("tsconfig.node.json")
    inputs.file("tailwind.config.js")
    inputs.file("postcss.config.js")
    inputs.file("package.json")

    outputs.dir("dist")
}

tasks.named("assemble") {
    dependsOn(npmBuild)
}

// Outgoing configuration so :distribution can consume the dist
// directory as a normal Gradle artifact. Using a single-element
// `artifacts` block keeps the consumer side trivial.
val webStaticBundle: Configuration by configurations.creating {
    isCanBeConsumed = true
    isCanBeResolved = false
}

artifacts {
    add(webStaticBundle.name, file("dist")) {
        builtBy(npmBuild)
    }
}