Skip to content

Commit 49f24fa

Browse files
authored
Have Task choose free ports for dev servers (#6145)
1 parent 12a00fe commit 49f24fa

9 files changed

Lines changed: 216 additions & 114 deletions

File tree

.taskfiles/backend.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ tasks:
44
dev:
55
desc: "Start backend dev server"
66
ignore_error: true
7+
vars:
8+
PORT: '{{.PORT | default "8080"}}'
9+
AIENGINE_URL: '{{.AIENGINE_URL | default ""}}'
10+
env:
11+
SERVER_PORT: '{{.PORT}}'
712
cmds:
8-
- cmd: cmd /c gradlew.bat :stirling-pdf:bootRun
13+
- cmd: '{{if .AIENGINE_URL}}set "AIENGINE_URL={{.AIENGINE_URL}}" && set "AIENGINE_ENABLED=true" && {{end}}cmd /c gradlew.bat :stirling-pdf:bootRun'
914
platforms: [windows]
10-
- cmd: ./gradlew :stirling-pdf:bootRun
15+
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
1116
platforms: [linux, darwin]
1217

1318
build:

.taskfiles/desktop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ vars:
66
tasks:
77
prepare:
88
desc: "Prepare desktop build dependencies"
9-
deps: [jlink, ":frontend:prepare:desktop", provisioner]
9+
deps:
10+
- jlink
11+
- task: ":frontend:prepare"
12+
vars: { MODE: desktop }
13+
- provisioner
1014

1115
provisioner:
1216
desc: "Build installer provisioner"

.taskfiles/engine.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,24 @@ tasks:
2828
deps: [prepare]
2929
ignore_error: true
3030
dir: src
31+
vars:
32+
PORT: '{{.PORT | default "5001"}}'
3133
env:
3234
PYTHONUNBUFFERED: "1"
3335
cmds:
34-
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port 5001
36+
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}}
3537

3638
dev:
3739
desc: "Start engine dev server with hot reload"
3840
deps: [prepare]
3941
ignore_error: true
4042
dir: src
43+
vars:
44+
PORT: '{{.PORT | default "5001"}}'
4145
env:
4246
PYTHONUNBUFFERED: "1"
4347
cmds:
44-
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port 5001 --reload
48+
- uv run uvicorn stirling.api.app:app --host 0.0.0.0 --port {{.PORT}} --reload
4549

4650
lint:
4751
desc: "Run linting"

.taskfiles/frontend.yml

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,107 +15,97 @@ tasks:
1515
CI: '{{ .CI | default "false" }}'
1616

1717
prepare:env:
18-
desc: "Generate .env from example if missing"
19-
run: once
20-
deps: [install]
21-
cmds:
22-
- npx tsx scripts/setup-env.ts
23-
sources:
24-
- scripts/setup-env.ts
25-
generates:
26-
- .env.local
27-
28-
prepare:env:saas:
29-
desc: "Generate .env and .env.saas from examples if missing"
30-
run: once
31-
deps: [install]
32-
cmds:
33-
- npx tsx scripts/setup-env.ts --saas
34-
sources:
35-
- scripts/setup-env.ts
36-
generates:
37-
- .env.local
38-
- .env.saas.local
39-
40-
prepare:env:desktop:
41-
desc: "Generate .env and .env.desktop from examples if missing"
42-
run: once
18+
internal: true
19+
run: when_changed
4320
deps: [install]
21+
vars:
22+
MODE: '{{.MODE | default ""}}'
4423
cmds:
45-
- npx tsx scripts/setup-env.ts --desktop
24+
- npx tsx scripts/setup-env.ts{{if .MODE}} --{{.MODE}}{{end}}
4625
sources:
4726
- scripts/setup-env.ts
4827
generates:
4928
- .env.local
50-
- .env.desktop.local
29+
- .env{{if .MODE}}.{{.MODE}}{{end}}.local
5130

5231
prepare:icons:
53-
desc: "Generate icon bundle from source references"
32+
internal: true
5433
run: once
5534
deps: [install]
5635
cmds:
5736
- node scripts/generate-icons.js
5837

5938
prepare:
6039
desc: "Set up dev environment"
61-
run: once
62-
deps: [prepare:env, prepare:icons]
63-
64-
prepare:saas:
65-
desc: "Prepare for SaaS mode"
66-
run: once
67-
deps: [prepare:env:saas, prepare:icons]
68-
69-
prepare:desktop:
70-
desc: "Prepare for desktop mode"
71-
run: once
72-
deps: [prepare:env:desktop, prepare:icons]
40+
run: when_changed
41+
vars:
42+
MODE: '{{.MODE | default ""}}'
43+
deps:
44+
- task: prepare:env
45+
vars: { MODE: '{{.MODE}}' }
46+
- prepare:icons
7347

7448
# ============================================================
7549
# Development
7650
# ============================================================
7751

52+
dev:_run:
53+
internal: true
54+
ignore_error: true
55+
vars:
56+
MODE: '{{.MODE}}'
57+
PORT: '{{.PORT | default "5173"}}'
58+
BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}'
59+
OPEN: '{{.OPEN | default ""}}'
60+
env:
61+
BACKEND_URL: '{{.BACKEND_URL}}'
62+
cmds:
63+
- npx vite --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
64+
7865
dev:
7966
desc: "Start frontend dev server"
80-
deps: [prepare]
81-
ignore_error: true
8267
cmds:
83-
- npx vite
68+
- task: dev:proprietary
69+
vars: { PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
8470

8571
dev:core:
8672
desc: "Start frontend dev server in core mode"
8773
deps: [prepare]
88-
ignore_error: true
8974
cmds:
90-
- npx vite --mode core
75+
- task: dev:_run
76+
vars: { MODE: core, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
9177

9278
dev:proprietary:
9379
desc: "Start frontend dev server in proprietary mode"
9480
deps: [prepare]
95-
ignore_error: true
9681
cmds:
97-
- npx vite --mode proprietary
82+
- task: dev:_run
83+
vars: { MODE: proprietary, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
9884

9985
dev:saas:
10086
desc: "Start frontend dev server in SaaS mode"
101-
deps: [prepare:saas]
102-
ignore_error: true
87+
deps:
88+
- task: prepare
89+
vars: { MODE: saas }
10390
cmds:
104-
- npx vite --mode saas
91+
- task: dev:_run
92+
vars: { MODE: saas, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
10593

10694
dev:desktop:
10795
desc: "Start frontend dev server in desktop mode"
108-
deps: [prepare:desktop]
109-
ignore_error: true
96+
deps:
97+
- task: prepare
98+
vars: { MODE: desktop }
11099
cmds:
111-
- npx vite --mode desktop
100+
- task: dev:_run
101+
vars: { MODE: desktop, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
112102

113103
dev:prototypes:
114104
desc: "Start frontend dev server in prototypes mode"
115105
deps: [prepare]
116-
ignore_error: true
117106
cmds:
118-
- npx vite --mode prototypes
107+
- task: dev:_run
108+
vars: { MODE: prototypes, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
119109

120110
# ============================================================
121111
# Build
@@ -141,13 +131,17 @@ tasks:
141131

142132
build:saas:
143133
desc: "Build for SaaS mode"
144-
deps: [prepare:saas]
134+
deps:
135+
- task: prepare
136+
vars: { MODE: saas }
145137
cmds:
146138
- npx vite build --mode saas
147139

148140
build:desktop:
149141
desc: "Build for desktop mode"
150-
deps: [prepare:desktop]
142+
deps:
143+
- task: prepare
144+
vars: { MODE: desktop }
151145
cmds:
152146
- npx vite build --mode desktop
153147

@@ -211,13 +205,17 @@ tasks:
211205

212206
typecheck:saas:
213207
desc: "Typecheck SaaS build variant"
214-
deps: [prepare:saas]
208+
deps:
209+
- task: prepare
210+
vars: { MODE: saas }
215211
cmds:
216212
- npx tsc --noEmit --project src/saas/tsconfig.json
217213

218214
typecheck:desktop:
219215
desc: "Typecheck desktop build variant"
220-
deps: [prepare:desktop]
216+
deps:
217+
- task: prepare
218+
vars: { MODE: desktop }
221219
cmds:
222220
- npx tsc --noEmit --project src/desktop/tsconfig.json
223221

AGENTS.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This file provides guidance to AI Agents when working with code in this reposito
66

77
This project uses [Task](https://taskfile.dev/) as a unified command runner. All build, dev, test, lint, and docker commands can be run from the repo root via `task <command>`. Run `task --list` to see all available commands.
88

9+
Task `desc:` fields should describe **what** the task does, not **how** it does it. Keep them generic and stable: don't reference implementation details like aliases, internal helpers, mode flags, or which other task delegates to which. The description is for users picking a command from `task --list`, not a changelog of refactors.
10+
911
### Quick Reference
1012
- `task install` — install all dependencies
1113
- `task dev` — start backend + frontend concurrently
@@ -143,7 +145,7 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
143145
- These files are committed to Git and must not contain private keys
144146
- Local overrides (API keys, machine-specific settings) go in uncommitted sibling `.env.local` / `.env.saas.local` / `.env.desktop.local` files — Vite automatically layers them on top
145147
- Never use `|| 'hardcoded-fallback'` inline — put defaults in the committed env files
146-
- `task frontend:prepare` / `prepare:saas` / `prepare:desktop` create empty `.local` override files on first run
148+
- `task frontend:prepare` creates empty `.local` override files on first run; pass `MODE=saas` or `MODE=desktop` to also create the mode-specific `.local` file
147149
- Prepare runs automatically as a dependency of all `dev*`, `build*`, and `desktop*` tasks
148150
- See `frontend/README.md#environment-variables` for full documentation
149151

Taskfile.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ version: '3'
22

33
output: prefixed
44

5+
vars:
6+
FIND_FREE_PORT_PS: powershell -NoProfile -File scripts\find-free-port.ps1 -Preferred
7+
FIND_FREE_PORT_SH: bash scripts/find-free-port.sh
8+
59
includes:
610
backend:
711
taskfile: .taskfiles/backend.yml
@@ -35,17 +39,43 @@ tasks:
3539
# ============================================================
3640

3741
dev:
38-
desc: "Start backend + frontend concurrently"
42+
desc: "Start backend + frontend concurrently on free ports"
43+
vars:
44+
PORTS:
45+
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}'
46+
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
47+
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
3948
deps:
40-
- backend:dev
41-
- frontend:dev
49+
- task: backend:dev
50+
vars:
51+
PORT: '{{.BACKEND_PORT}}'
52+
- task: frontend:dev
53+
vars:
54+
PORT: '{{.FRONTEND_PORT}}'
55+
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
56+
OPEN: "true"
4257

4358
dev:all:
44-
desc: "Start backend + frontend + engine concurrently"
59+
desc: "Start backend + frontend + engine concurrently on free ports"
60+
vars:
61+
PORTS:
62+
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173,5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
63+
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
64+
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
65+
ENGINE_PORT: '{{index (splitList "\n" .PORTS) 2}}'
4566
deps:
46-
- backend:dev
47-
- frontend:dev:prototypes
48-
- engine:dev
67+
- task: engine:dev
68+
vars:
69+
PORT: '{{.ENGINE_PORT}}'
70+
- task: backend:dev
71+
vars:
72+
PORT: '{{.BACKEND_PORT}}'
73+
AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}'
74+
- task: frontend:dev:prototypes
75+
vars:
76+
PORT: '{{.FRONTEND_PORT}}'
77+
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
78+
OPEN: "true"
4979

5080
# ============================================================
5181
# Build

0 commit comments

Comments
 (0)