fix(task): make task dev / task dev:all work on Windows (#6392)

## Summary

`#6145` (port picker) and `#6244` (gradle unification) combined to break
`task dev` / `task dev:all` on Windows. Two independent regressions,
both addressed here.

### 1. `find-free-port.ps1` panic (`#6145`)

```
$ task --dry dev
The argument 'scriptsfind-free-port.ps1' to the -File parameter does not exist.
panic: ended up with a non-nil exitStatus.err but a zero exitStatus.code
```

- **Backslash stripped.** `Taskfile.yml` had
`scripts\find-free-port.ps1`. go-task pipes the `sh:` block through
mvdan/sh, which treats `\` as a POSIX escape and silently drops it,
leaving `scriptsfind-free-port.ps1`. PowerShell can't find the file,
exits non-zero, mvdan/sh panics on the inconsistent exit status.
Switched to a forward slash.
- **`-Preferred 8080,5173` is brittle.** Relies on PowerShell parsing
the comma-list into `[int[]]`. Dropped the named flag; switched the
script's `param` to `[Parameter(ValueFromRemainingArguments =
$true)][int[]]$Preferred`; pass each port as its own positional token
(matching how the bash variant is called).

### 2. `bash gradlew` can't find Java on Windows (`#6244`)

```
[backend:dev] ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
```

`#6244` unified all gradle invocations under `bash gradlew` on the
assumption that Git-Bash inherits Windows-side env. It doesn't (and
neither does WSL bash, which can also shadow `bash` on a developer's
PATH). The standard Adoptium/Temurin installer sets `JAVA_HOME` in the
Windows env only, so `bash gradlew` fails before Spring Boot even loads.

Restored the per-platform branch for every backend task: `cmd /c
".\gradlew.bat ..."` on Windows, `./gradlew ...` on Linux/macOS. Two
Windows-specific gotchas to be aware of for future edits:

- mvdan/sh strips `\` outside of double quotes, so `cmd /c
.\gradlew.bat` ends up as `.gradlew.bat`. The entire payload must be
wrapped in double quotes (`cmd /c "..."`).
- Modern Windows excludes cwd from cmd.exe's search path, so the leading
`.\` is required — bare `cmd /c gradlew.bat` errors with "is not
recognized" even from the repo root.

## Test plan

- [x] `task --dry dev` / `task --dry dev:all` on Windows resolve ports
cleanly and dispatch the inner tasks
- [x] `powershell -NoProfile -File scripts/find-free-port.ps1 8080 5173
5001` prints three ports
- [x] `task backend:dev PORT=8081` on Windows starts Spring Boot in ~9s;
`/api/v1/info/status` returns `{"status":"UP"}`
- [x] `task dev:all` on Windows brings up all three services healthy:
backend `/api/v1/info/status` 200, engine `/health` 200, frontend `/`
200
- [ ] Linux / macOS path unaffected (only the Windows branches changed
in behaviour)
This commit is contained in:
ConnorYoh
2026-05-20 12:57:41 +00:00
committed by GitHub
parent 2399da6893
commit b146d9994d
3 changed files with 70 additions and 19 deletions
+66 -15
View File
@@ -1,7 +1,19 @@
version: '3' version: '3'
# All gradle invocations go through `bash gradlew` (the sh wrapper) so they work # Gradle invocation strategy:
# uniformly on Linux/macOS (native bash) and Windows-with-Git-Bash. # - Linux/macOS: `./gradlew` runs the POSIX shell wrapper natively.
# - Windows: `cmd /c ".\gradlew.bat ..."` invokes the .bat wrapper through
# cmd.exe so it inherits the user's Windows-side `JAVA_HOME`
# and PATH. Routing through `bash gradlew` on Windows ends up
# under WSL or Git-Bash, neither of which inherits
# Adoptium/Temurin's default Windows-only Java env - gradlew
# then errors with "JAVA_HOME is not set and no 'java' command
# could be found".
#
# The entire `.\gradlew.bat ...` payload is double-quoted so
# the leading `.\` survives mvdan/sh's POSIX backslash
# stripping; cmd.exe also requires `.\` (not bare `gradlew.bat`)
# because modern Windows excludes cwd from cmd's search path.
tasks: tasks:
dev: dev:
@@ -13,43 +25,67 @@ tasks:
env: env:
SERVER_PORT: '{{.PORT}}' SERVER_PORT: '{{.PORT}}'
cmds: cmds:
- '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}bash gradlew :stirling-pdf:bootRun' - cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
platforms: [windows]
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
platforms: [linux, darwin]
dev:bundled: dev:bundled:
desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)" desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)"
ignore_error: true ignore_error: true
cmds: cmds:
- bash gradlew clean bootRun -PbuildWithFrontend=true - cmd: cmd /c ".\gradlew.bat clean bootRun -PbuildWithFrontend=true"
platforms: [windows]
- cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
platforms: [linux, darwin]
build: build:
desc: "Full backend build" desc: "Full backend build"
cmds: cmds:
- bash gradlew clean build - cmd: cmd /c ".\gradlew.bat clean build"
platforms: [windows]
- cmd: ./gradlew clean build
platforms: [linux, darwin]
build:fast: build:fast:
desc: "Build without tests" desc: "Build without tests"
cmds: cmds:
- bash gradlew clean build -x test - cmd: cmd /c ".\gradlew.bat clean build -x test"
platforms: [windows]
- cmd: ./gradlew clean build -x test
platforms: [linux, darwin]
build:ci: build:ci:
desc: "Build for CI (formatting checked separately)" desc: "Build for CI (formatting checked separately)"
cmds: cmds:
- bash gradlew build -PnoSpotless - cmd: cmd /c ".\gradlew.bat build -PnoSpotless"
platforms: [windows]
- cmd: ./gradlew build -PnoSpotless
platforms: [linux, darwin]
test: test:
desc: "Run backend tests" desc: "Run backend tests"
cmds: cmds:
- bash gradlew test - cmd: cmd /c ".\gradlew.bat test"
platforms: [windows]
- cmd: ./gradlew test
platforms: [linux, darwin]
format: format:
desc: "Auto-fix code formatting" desc: "Auto-fix code formatting"
cmds: cmds:
- bash gradlew spotlessApply - cmd: cmd /c ".\gradlew.bat spotlessApply"
platforms: [windows]
- cmd: ./gradlew spotlessApply
platforms: [linux, darwin]
format:check: format:check:
desc: "Check code formatting" desc: "Check code formatting"
cmds: cmds:
- bash gradlew spotlessCheck - cmd: cmd /c ".\gradlew.bat spotlessCheck"
platforms: [windows]
- cmd: ./gradlew spotlessCheck
platforms: [linux, darwin]
fix: fix:
desc: "Auto-fix backend" desc: "Auto-fix backend"
@@ -59,7 +95,10 @@ tasks:
swagger: swagger:
desc: "Generate OpenAPI docs" desc: "Generate OpenAPI docs"
cmds: cmds:
- bash gradlew :stirling-pdf:copySwaggerDoc - cmd: cmd /c ".\gradlew.bat :stirling-pdf:copySwaggerDoc"
platforms: [windows]
- cmd: ./gradlew :stirling-pdf:copySwaggerDoc
platforms: [linux, darwin]
sources: sources:
- app/core/src/main/java/**/*.java - app/core/src/main/java/**/*.java
- app/proprietary/src/main/java/**/*.java - app/proprietary/src/main/java/**/*.java
@@ -77,19 +116,31 @@ tasks:
desc: "Print project version" desc: "Print project version"
silent: true silent: true
cmds: cmds:
- bash gradlew printVersion --quiet | tail -1 - cmd: cmd /c ".\gradlew.bat printVersion --quiet" | tail -1
platforms: [windows]
- cmd: ./gradlew printVersion --quiet | tail -1
platforms: [linux, darwin]
licenses:check: licenses:check:
desc: "Check dependency licenses" desc: "Check dependency licenses"
cmds: cmds:
- bash gradlew checkLicense --no-parallel - cmd: cmd /c ".\gradlew.bat checkLicense --no-parallel"
platforms: [windows]
- cmd: ./gradlew checkLicense --no-parallel
platforms: [linux, darwin]
licenses:generate: licenses:generate:
desc: "Check and generate dependency license report" desc: "Check and generate dependency license report"
cmds: cmds:
- bash gradlew checkLicense generateLicenseReport --no-parallel - cmd: cmd /c ".\gradlew.bat checkLicense generateLicenseReport --no-parallel"
platforms: [windows]
- cmd: ./gradlew checkLicense generateLicenseReport --no-parallel
platforms: [linux, darwin]
clean: clean:
desc: "Clean build artifacts" desc: "Clean build artifacts"
cmds: cmds:
- bash gradlew clean - cmd: cmd /c ".\gradlew.bat clean"
platforms: [windows]
- cmd: ./gradlew clean
platforms: [linux, darwin]
+3 -3
View File
@@ -3,7 +3,7 @@ version: '3'
output: prefixed output: prefixed
vars: vars:
FIND_FREE_PORT_PS: powershell -NoProfile -File scripts\find-free-port.ps1 -Preferred FIND_FREE_PORT_PS: powershell -NoProfile -File scripts/find-free-port.ps1
FIND_FREE_PORT_SH: bash scripts/find-free-port.sh FIND_FREE_PORT_SH: bash scripts/find-free-port.sh
includes: includes:
@@ -45,7 +45,7 @@ tasks:
desc: "Start backend + frontend concurrently on free ports" desc: "Start backend + frontend concurrently on free ports"
vars: vars:
PORTS: PORTS:
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}' sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}' FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
deps: deps:
@@ -62,7 +62,7 @@ tasks:
desc: "Start backend + frontend + engine concurrently on free ports" desc: "Start backend + frontend + engine concurrently on free ports"
vars: vars:
PORTS: PORTS:
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173,5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}' sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}' BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}' FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
ENGINE_PORT: '{{index (splitList "\n" .PORTS) 2}}' ENGINE_PORT: '{{index (splitList "\n" .PORTS) 2}}'
+1 -1
View File
@@ -4,7 +4,7 @@
# emits a random free port in 20000-49999. Probes by attempting to bind a # emits a random free port in 20000-49999. Probes by attempting to bind a
# TcpListener on loopback. Tracks picks within this run so outputs are # TcpListener on loopback. Tracks picks within this run so outputs are
# guaranteed distinct from each other. # guaranteed distinct from each other.
param([int[]]$Preferred) param([Parameter(ValueFromRemainingArguments = $true)][int[]]$Preferred)
$script:picked = @() $script:picked = @()