2026-04-15 15:16:57 +01:00
|
|
|
version: '3'
|
|
|
|
|
|
2026-05-20 13:57:41 +01:00
|
|
|
# Gradle invocation strategy:
|
|
|
|
|
# - 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.
|
2026-05-11 15:50:07 +01:00
|
|
|
|
2026-04-15 15:16:57 +01:00
|
|
|
tasks:
|
|
|
|
|
dev:
|
|
|
|
|
desc: "Start backend dev server"
|
2026-06-11 13:20:01 +01:00
|
|
|
cmds:
|
|
|
|
|
- task: dev:proprietary
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT}}'
|
|
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL}}'
|
2026-06-18 11:08:50 +01:00
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
|
2026-06-22 17:22:36 +01:00
|
|
|
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
|
|
|
|
|
dev:proprietary:
|
|
|
|
|
desc: "Start backend dev server in proprietary mode"
|
2026-06-29 14:35:07 +01:00
|
|
|
# `dotenv:` reads from the root Taskfile's directory (".") because this
|
|
|
|
|
# subtaskfile is included with `dir: .`. Local overrides in
|
|
|
|
|
# .env.proprietary.local win over the committed .env.proprietary defaults.
|
|
|
|
|
dotenv: ['app/.env.proprietary.local', 'app/.env.proprietary']
|
2026-04-15 15:16:57 +01:00
|
|
|
ignore_error: true
|
2026-04-28 17:26:04 +01:00
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT | default "8080"}}'
|
|
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL | default ""}}'
|
2026-06-18 11:08:50 +01:00
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}'
|
2026-05-28 18:26:23 +01:00
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}'
|
2026-06-22 17:22:36 +01:00
|
|
|
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN | default ""}}'
|
2026-08-27 10:32:32 +00:00
|
|
|
# Set by dev:linked. Inline rather than in `env:` so an empty value emits nothing
|
|
|
|
|
# and cannot blank the committed default.
|
|
|
|
|
ACCOUNT_LINK_SAAS_BASE_URL: '{{.ACCOUNT_LINK_SAAS_BASE_URL | default ""}}'
|
2026-04-28 17:26:04 +01:00
|
|
|
env:
|
|
|
|
|
SERVER_PORT: '{{.PORT}}'
|
2026-04-15 15:16:57 +01:00
|
|
|
cmds:
|
2026-08-27 10:32:32 +00:00
|
|
|
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}{{if .ACCOUNT_LINK_SAAS_BASE_URL}}STIRLING_BILLING_ACCOUNT_LINK_ENABLED=true STIRLING_BILLING_ACCOUNT_LINK_SAAS_BASE_URL={{.ACCOUNT_LINK_SAAS_BASE_URL}} {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
|
2026-05-20 13:57:41 +01:00
|
|
|
platforms: [windows]
|
2026-08-27 10:32:32 +00:00
|
|
|
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}{{if .ACCOUNT_LINK_SAAS_BASE_URL}}STIRLING_BILLING_ACCOUNT_LINK_ENABLED=true STIRLING_BILLING_ACCOUNT_LINK_SAAS_BASE_URL={{.ACCOUNT_LINK_SAAS_BASE_URL}} {{end}}./gradlew :stirling-pdf:bootRun'
|
2026-05-20 13:57:41 +01:00
|
|
|
platforms: [linux, darwin]
|
2026-05-11 15:50:07 +01:00
|
|
|
|
|
|
|
|
dev:bundled:
|
|
|
|
|
desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)"
|
|
|
|
|
ignore_error: true
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat clean bootRun -PbuildWithFrontend=true"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
2026-08-19 14:46:00 +00:00
|
|
|
# SaaS backend. dev:saas -> the PR's preview branch, staging:saas -> shared v3,
|
|
|
|
|
# PROFILES=none -> production against your own SAAS_DB_*. Production has no named
|
|
|
|
|
# task on purpose. Use `none`, not an empty value: Go template `default` treats ""
|
|
|
|
|
# as absent and would resolve back to dev.
|
|
|
|
|
|
2026-05-26 15:40:44 +01:00
|
|
|
dev:saas:
|
2026-08-19 14:46:00 +00:00
|
|
|
desc: "Start SaaS backend against the current PR's Supabase preview branch"
|
|
|
|
|
dotenv: ['app/.env.saas.local', 'app/.env.saas']
|
|
|
|
|
vars:
|
|
|
|
|
PROFILES: '{{.PROFILES | default "dev"}}'
|
|
|
|
|
cmds:
|
|
|
|
|
# Don't move this check into a `sh:` var: dotenv is visible in cmds but not
|
|
|
|
|
# during var evaluation, so the test would always see an empty value.
|
|
|
|
|
- cmd: |
|
|
|
|
|
if [ "{{.PROFILES}}" = "dev" ] && [ -z "${SAAS_DEV_PROJECT_REF:-}" ]; then
|
|
|
|
|
echo ">> SAAS_DEV_PROJECT_REF is not set."
|
|
|
|
|
echo ">> Testing a SaaS PR? Put its ref, DB password and publishable key in app/.env.saas.local."
|
|
|
|
|
echo ">> Wanted the shared v3 project? Use 'task backend:staging:saas' instead."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
- task: _run:saas
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT}}'
|
|
|
|
|
PROFILES: '{{.PROFILES}}'
|
|
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL}}'
|
|
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
|
|
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
|
2026-08-27 10:32:32 +00:00
|
|
|
APP_BASE_URL: '{{.APP_BASE_URL}}'
|
|
|
|
|
BASE_PATH: '{{.BASE_PATH}}'
|
2026-08-19 14:46:00 +00:00
|
|
|
|
|
|
|
|
staging:saas:
|
|
|
|
|
desc: "Start SaaS backend against the shared v3 staging project"
|
|
|
|
|
cmds:
|
|
|
|
|
- task: _run:saas
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT}}'
|
|
|
|
|
PROFILES: staging
|
|
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL}}'
|
|
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
|
|
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
|
2026-08-27 10:32:32 +00:00
|
|
|
APP_BASE_URL: '{{.APP_BASE_URL}}'
|
|
|
|
|
BASE_PATH: '{{.BASE_PATH}}'
|
|
|
|
|
|
|
|
|
|
dev:linked:
|
|
|
|
|
desc: "Self-hosted backend linked to a locally running SaaS backend (see task linked:*)"
|
|
|
|
|
ignore_error: true
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT | default "8080"}}'
|
|
|
|
|
SAAS_BASE_URL: '{{.SAAS_BASE_URL | default "http://localhost:8081"}}'
|
|
|
|
|
cmds:
|
|
|
|
|
- 'echo ">> self-hosted :{{.PORT}} linking to SaaS at {{.SAAS_BASE_URL}}"'
|
|
|
|
|
# The two backends run different STIRLING_FLAVOURs, which are different Gradle
|
|
|
|
|
# project graphs sharing one build/ tree. Waiting avoids overlapping builds; it
|
|
|
|
|
# does not make the sharing safe, so avoid rebuilding one while the other runs.
|
|
|
|
|
- cmd: |
|
|
|
|
|
n=0
|
|
|
|
|
while [ "$n" -lt 150 ]; do
|
|
|
|
|
if curl -s -m 2 "{{.SAAS_BASE_URL}}" >/dev/null 2>&1; then
|
|
|
|
|
echo ">> SaaS backend is up, starting self-hosted"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
{{if eq OS "windows"}}powershell -NoProfile -Command "Start-Sleep -Seconds 2"{{else}}sleep 2{{end}}
|
|
|
|
|
done
|
|
|
|
|
if [ "$n" -ge 150 ]; then
|
|
|
|
|
echo ">> SaaS backend never answered; starting anyway"
|
|
|
|
|
fi
|
|
|
|
|
- task: dev:proprietary
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT}}'
|
|
|
|
|
ACCOUNT_LINK_SAAS_BASE_URL: '{{.SAAS_BASE_URL}}'
|
2026-08-19 14:46:00 +00:00
|
|
|
|
|
|
|
|
_run:saas:
|
|
|
|
|
internal: true
|
2026-08-27 10:32:32 +00:00
|
|
|
# The frontend files are here only for RUN_SUBPATH, which the authorize URL needs.
|
|
|
|
|
# Last, because dotenv is set-if-absent: app/* still decides everything else.
|
|
|
|
|
dotenv:
|
|
|
|
|
- 'app/.env.saas.local'
|
|
|
|
|
- 'app/.env.saas'
|
|
|
|
|
- 'frontend/editor/.env.saas.local'
|
|
|
|
|
- 'frontend/editor/.env.saas'
|
2026-05-26 15:40:44 +01:00
|
|
|
ignore_error: true
|
|
|
|
|
vars:
|
|
|
|
|
PORT: '{{.PORT | default "8080"}}'
|
|
|
|
|
PROFILES: '{{.PROFILES | default "dev"}}'
|
2026-08-19 14:46:00 +00:00
|
|
|
# Built here rather than inline in the cmds below: the Windows line is an
|
|
|
|
|
# unquoted YAML scalar wrapping a cmd.exe string, so a nested {{if ne .X
|
|
|
|
|
# "none"}} needs escaped quotes that reach the Go template as literal
|
|
|
|
|
# backslashes and fail with `unexpected "\" in operand`.
|
|
|
|
|
PROFILE_ARGS: '{{if ne .PROFILES "none"}}--spring.profiles.include={{.PROFILES}}{{end}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL | default ""}}'
|
2026-06-18 11:08:50 +01:00
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}'
|
2026-08-27 10:32:32 +00:00
|
|
|
# Empty is the same as unset: the property defaults to empty and is blank-checked.
|
|
|
|
|
APP_BASE_URL: '{{.APP_BASE_URL | default ""}}'
|
|
|
|
|
# Relocates configs/pipeline/logs, for a second backend in the same directory.
|
|
|
|
|
# Empty is the same as unset: the reader blank-checks it.
|
|
|
|
|
BASE_PATH: '{{.BASE_PATH | default ""}}'
|
2026-05-26 15:40:44 +01:00
|
|
|
env:
|
|
|
|
|
SERVER_PORT: '{{.PORT}}'
|
|
|
|
|
STIRLING_FLAVOR: saas
|
2026-08-27 10:32:32 +00:00
|
|
|
STIRLING_BASE_PATH: '{{.BASE_PATH}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
AIENGINE_URL: '{{.AIENGINE_URL}}'
|
2026-06-18 11:08:50 +01:00
|
|
|
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
|
2026-06-11 13:20:01 +01:00
|
|
|
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
|
2026-08-27 10:32:32 +00:00
|
|
|
# Appends RUN_SUBPATH: the approval page is at <base>/link, so a subpath build
|
|
|
|
|
# serves it at <base>/app/link. An explicit value still wins.
|
|
|
|
|
SYSTEM_FRONTENDURL:
|
|
|
|
|
sh: |
|
|
|
|
|
if [ -n "${SYSTEM_FRONTENDURL:-}" ]; then
|
|
|
|
|
echo "${SYSTEM_FRONTENDURL}"
|
|
|
|
|
elif [ -n "{{.APP_BASE_URL}}" ] && [ -n "${RUN_SUBPATH:-}" ]; then
|
|
|
|
|
echo "{{.APP_BASE_URL}}/${RUN_SUBPATH}"
|
|
|
|
|
else
|
|
|
|
|
echo "{{.APP_BASE_URL}}"
|
|
|
|
|
fi
|
2026-05-26 15:40:44 +01:00
|
|
|
cmds:
|
2026-08-19 14:46:00 +00:00
|
|
|
# PROFILE_ARGS is empty when PROFILES=none, i.e. the bare `saas` profile
|
|
|
|
|
# against SAAS_DB_* (production).
|
|
|
|
|
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args=\"{{.PROFILE_ARGS}}\"{{end}}"
|
2026-05-26 15:40:44 +01:00
|
|
|
platforms: [windows]
|
2026-08-19 14:46:00 +00:00
|
|
|
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args='{{.PROFILE_ARGS}}'{{end}}
|
2026-05-26 15:40:44 +01:00
|
|
|
platforms: [linux, darwin]
|
|
|
|
|
|
2026-04-15 15:16:57 +01:00
|
|
|
build:
|
|
|
|
|
desc: "Full backend build"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat clean build"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew clean build
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
build:fast:
|
|
|
|
|
desc: "Build without tests"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat clean build -x test"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew clean build -x test
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
build:ci:
|
|
|
|
|
desc: "Build for CI (formatting checked separately)"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat build -PnoSpotless"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew build -PnoSpotless
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
test:
|
|
|
|
|
desc: "Run backend tests"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat test"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew test
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
2026-08-03 12:11:13 +01:00
|
|
|
test:force:
|
|
|
|
|
desc: "Run backend tests, ignoring cached results"
|
|
|
|
|
aliases: [test:no-cache]
|
|
|
|
|
cmds:
|
|
|
|
|
- cmd: cmd /c ".\gradlew.bat cleanTest test --no-build-cache"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew cleanTest test --no-build-cache
|
|
|
|
|
platforms: [linux, darwin]
|
|
|
|
|
|
2026-04-15 15:16:57 +01:00
|
|
|
format:
|
|
|
|
|
desc: "Auto-fix code formatting"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat spotlessApply"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew spotlessApply
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
format:check:
|
|
|
|
|
desc: "Check code formatting"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat spotlessCheck"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew spotlessCheck
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
fix:
|
|
|
|
|
desc: "Auto-fix backend"
|
|
|
|
|
cmds:
|
|
|
|
|
- task: format
|
|
|
|
|
|
|
|
|
|
swagger:
|
|
|
|
|
desc: "Generate OpenAPI docs"
|
2026-08-05 16:25:44 +02:00
|
|
|
run: once
|
2026-04-15 15:16:57 +01:00
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:copySwaggerDoc"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew :stirling-pdf:copySwaggerDoc
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-20 15:57:11 +01:00
|
|
|
sources:
|
|
|
|
|
- app/core/src/main/java/**/*.java
|
|
|
|
|
- app/proprietary/src/main/java/**/*.java
|
|
|
|
|
- app/common/src/main/java/**/*.java
|
|
|
|
|
generates:
|
|
|
|
|
- SwaggerDoc.json
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
check:
|
|
|
|
|
desc: "Backend quality gate"
|
|
|
|
|
cmds:
|
|
|
|
|
- task: format:check
|
|
|
|
|
- task: test
|
|
|
|
|
|
|
|
|
|
version:
|
|
|
|
|
desc: "Print project version"
|
|
|
|
|
silent: true
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat printVersion --quiet" | tail -1
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew printVersion --quiet | tail -1
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
licenses:check:
|
|
|
|
|
desc: "Check dependency licenses"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat checkLicense --no-parallel"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew checkLicense --no-parallel
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
licenses:generate:
|
|
|
|
|
desc: "Check and generate dependency license report"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat checkLicense generateLicenseReport --no-parallel"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew checkLicense generateLicenseReport --no-parallel
|
|
|
|
|
platforms: [linux, darwin]
|
2026-04-15 15:16:57 +01:00
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
desc: "Clean build artifacts"
|
|
|
|
|
cmds:
|
2026-05-20 13:57:41 +01:00
|
|
|
- cmd: cmd /c ".\gradlew.bat clean"
|
|
|
|
|
platforms: [windows]
|
|
|
|
|
- cmd: ./gradlew clean
|
|
|
|
|
platforms: [linux, darwin]
|