Commits
Tarun Sukhani authored d34cc0301c0
fix(gradle-plugin): build the Nuxt SPA once per invocation via a playFrontendSpa task (PF-169)
buildFrontendAndCopySpa was called from inside PlayDistTask.dist and
PlayBundleTask.bundle. Gradle's run-each-task-at-most-once rule applies to the
task graph, so a call made from an action body escapes it entirely: `gradle
playDist playBundle` — the single invocation a CI packaging stage naturally
uses — ran a full Nuxt production build twice.
Promote the helper to a registered playFrontendSpa task that both packaging
tasks dependsOn, and drop the two in-action calls. The helper body is
unchanged. An onlyIf on frontend/ being a directory preserves its early return,
so apps with no Nuxt frontend are unaffected (the task reports SKIPPED).
dependsOn ordering guarantees public/spa exists before either packaging action
walks it to force-include the SPA in its zip.
The task deliberately keeps outputs.upToDateWhen { false } and declares no
inputs/outputs. A Nuxt build legitimately reads files outside frontend/ (a
nuxt.config.ts can widen Vite's fs.allow to import docs from the repo root),
plus non-file inputs like the Node and corepack-pinned pnpm versions, so an
input set scoped to frontend/ would report UP-TO-DATE and silently ship a stale
SPA in a release artifact.
Named playFrontendSpa rather than the buildFrontendSpa of the ticket text, so
it matches the play*-prefix of every other task in the play1 group; renaming
after release would break anyone invoking it directly.
Verified against a real scaffolded app, not only the task graph: one invocation
naming both tasks runs the SPA build exactly once and both zips contain
public/spa; each task alone still rebuilds and packages it; a frontend-less app
skips it and builds both artifacts; missing pnpm and a generate that produces
no output still fail with their existing messages.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>