// 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) } }