Repository navigation
Expand file tree
/
Copy pathtools
More file actions
executable file
·499 lines (445 loc) · 14.5 KB
/
Copy pathtools
File metadata and controls
executable file
·499 lines (445 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#!/usr/bin/env bash
# If you are used to a Makefile, this is where you do the work instead
set -e
THISSCRIPT=$(basename $0)
PROJECT_ROOT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# colors
GREEN=$'\e[0;32m'
NC=$'\e[0m'
# Modify for the help message
usage() {
log_status "Usage"
echo "${THISSCRIPT} command"
echo ""
echo "Commands:"
echo ""
echo " setup Install all dependencies (Python with uv, Go modules)"
echo " test Run all tests (Python + Go + Web)"
echo " test-runnerlib Run Python tests only"
echo " test-coordinator Run Go tests only"
echo " test-web Run webapp Go tests, unit and integration"
echo " test-web-integration Run webapp integration tests only (needs a container runtime)"
echo " test-ui Run web UI (SolidJS) tests only"
echo " run-api Start coordinator API locally"
echo " run-worker Start worker locally"
echo " build Build Go binary (builds the UI first)"
echo " build-ui Build the web UI into the webapp binary's embed directory"
echo " docker-build Build Docker images"
echo " clean Clean generated files"
echo " dev Start full local development stack"
echo " updatedb Run migrations manually (normally runs on app startup)"
echo " check-deps Check for required tools"
echo " test-e2e Run end-to-end tests"
echo " test-e2e-quick Run quick E2E tests"
echo " test-run-local-paths Run run-local path contract smoke test"
echo " deadcode Report unreachable Go functions, including test reachability"
echo ""
exit 0
}
check_deps(){
log_status "Checking dependencies"
local missing_deps=0
# Check Python
if ! command -v python3 2>&1 >/dev/null; then
echo "❌ Python 3.13+ is required but not found"
missing_deps=1
else
python_version=$(python3 --version | cut -d' ' -f2)
echo "✅ Python ${python_version} found"
fi
# Check Go
if ! command -v go 2>&1 >/dev/null; then
echo "❌ Go 1.23+ is required but not found"
missing_deps=1
else
go_version=$(go version | cut -d' ' -f3)
echo "✅ Go ${go_version} found"
fi
# Check container runtime. Prefer containerd/nerdctl when usable, but
# Docker is still supported for local development.
if container_cmd >/dev/null 2>&1; then
echo "✅ Container runtime found: $(container_cmd)"
else
echo "❌ A usable container runtime is required: nerdctl/containerd or docker"
missing_deps=1
fi
# Check uv
if ! command -v uv 2>&1 >/dev/null; then
echo "⚠️ uv is not installed, will install it"
else
uv_version=$(uv --version | cut -d' ' -f2)
echo "✅ uv ${uv_version} found"
fi
if [ $missing_deps -eq 1 ]; then
echo ""
echo "Please install missing dependencies before continuing"
exit 1
fi
}
setup(){
log_status "Setting up development environment"
check_deps
# Install uv if not present
if ! command -v uv 2>&1 >/dev/null; then
log_status "Installing uv"
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.cargo/bin:$PATH"
fi
# Set up Python environment with uv
log_status "Setting up Python environment"
cd "${PROJECT_ROOT_DIR}/runnerlib"
uv sync
cd "${PROJECT_ROOT_DIR}"
# Install Go dependencies
log_status "Installing Go dependencies"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go mod download
cd "${PROJECT_ROOT_DIR}/webapp"
go mod download
cd "${PROJECT_ROOT_DIR}"
echo ""
echo "✅ Setup complete!"
}
updatedb(){
# Note: Migrations now run automatically on app startup.
# This command is only needed for running migrations manually (e.g., local dev without docker).
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go run . migrate
cd "${PROJECT_ROOT_DIR}"
}
log_status() {
echo "${GREEN}--------------------------------- ${1} ---------------------------------${NC}"
}
container_cmd(){
if command -v nerdctl >/dev/null 2>&1 && nerdctl ps >/dev/null 2>&1; then
echo "nerdctl"
return 0
fi
if command -v docker >/dev/null 2>&1 && docker version >/dev/null 2>&1; then
echo "docker"
return 0
fi
return 1
}
compose_cmd(){
if command -v nerdctl >/dev/null 2>&1 && nerdctl ps >/dev/null 2>&1 && nerdctl compose version >/dev/null 2>&1; then
echo "nerdctl compose"
return 0
fi
if command -v docker >/dev/null 2>&1 && docker version >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
echo "docker compose"
return 0
fi
if command -v docker-compose >/dev/null 2>&1 && docker-compose version >/dev/null 2>&1; then
echo "docker-compose"
return 0
fi
return 1
}
test(){
log_status "Running all tests"
test_runnerlib
test_coordinator
test_web
test_ui
echo "✅ All tests passed!"
}
test_runnerlib(){
log_status "Running Python tests"
cd "${PROJECT_ROOT_DIR}/runnerlib"
uv run pytest -v
cd "${PROJECT_ROOT_DIR}"
}
test_coordinator(){
log_status "Running Go tests"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go test -v ./...
cd "${PROJECT_ROOT_DIR}"
}
test_web(){
log_status "Running webapp tests"
cd "${PROJECT_ROOT_DIR}/webapp"
go test -v -short ./internal/...
cd "${PROJECT_ROOT_DIR}"
test_web_integration
}
# The webapp integration tests are SELF-CONTAINED: testcontainers starts its own
# PostgreSQL, builds the coordinator, runs migrations, and boots both servers.
# They need a container runtime and about five seconds, and nothing else.
#
# They run by default because they catch a class of bug the unit tests
# structurally cannot. The in-memory fakes evaluate visibility in Go; only the
# real database evaluates the SQL predicate. An anonymous caller binding "" into
# a uuid comparison passed every unit test and raised SQLSTATE 22P02 against
# PostgreSQL -- see visibilityArgs.
test_web_integration(){
# testcontainers-go speaks the DOCKER API specifically. container_cmd is the
# wrong check here: it prefers nerdctl, so on a machine where nerdctl works
# and the Docker daemon does not, it reports a runtime and these tests then
# fail rather than skipping. Check what testcontainers actually needs.
if ! docker version >/dev/null 2>&1; then
echo "⚠️ Skipping webapp integration tests: no reachable Docker API."
echo " These start their own PostgreSQL through testcontainers-go, which"
echo " talks to the Docker API and does not use nerdctl."
echo " Start Docker, or point DOCKER_HOST at a Docker-compatible endpoint"
echo " (a rootless socket, or 'nerdctl' with a Docker-API shim)."
return 0
fi
log_status "Running webapp integration tests"
cd "${PROJECT_ROOT_DIR}/webapp"
go test ./test/
cd "${PROJECT_ROOT_DIR}"
}
run_api(){
log_status "Starting coordinator API"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go run ./cmd/api
}
run_worker(){
log_status "Starting worker"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go run ./cmd/worker
}
# Node for the web UI.
#
# Locally that is whatever is on PATH -- catalyst-tools provides one (see the
# repository CLAUDE.md). CI has no Node in the image and fetches a pinned one
# with webi instead (see .reactorcide/plugins/plugin_ci_jobs.py). Both are fine;
# what is not fine is a bare "npm: command not found" with no hint.
require_npm(){
if command -v npm >/dev/null 2>&1; then
return 0
fi
echo "❌ npm is not on PATH, and the web UI needs it."
echo
echo " Use the shared toolchain:"
echo " source \"\${CATALYST_TOOLS:-\$HOME/.local/catalyst-tools}/env.sh\""
echo
echo " Or fetch a pinned Node the way CI does:"
echo " curl -sS https://webi.sh/webi | sh"
echo " ~/.local/bin/webi node@22.23.2"
echo " export PATH=\"\$HOME/.local/opt/node/bin:\$PATH\""
return 1
}
# The SPA is compiled INTO the webapp binary (webapp/internal/embedui), so it
# has to be built before the Go build or the binary ships a placeholder page.
build_ui(){
require_npm || exit 1
log_status "Building web UI"
cd "${PROJECT_ROOT_DIR}/webapp/ui"
if [[ ! -d node_modules ]]; then
log_status "Installing web UI dependencies"
npm ci 2>/dev/null || npm install
fi
npm run build
cd "${PROJECT_ROOT_DIR}"
# vite empties the output directory, which takes .gitkeep with it. That file
# is what gives `go:embed all:dist` something to embed on a fresh clone.
touch "${PROJECT_ROOT_DIR}/webapp/internal/embedui/dist/.gitkeep"
echo "✅ Web UI built into webapp/internal/embedui/dist/"
}
test_ui(){
require_npm || exit 1
log_status "Running web UI tests"
cd "${PROJECT_ROOT_DIR}/webapp/ui"
if [[ ! -d node_modules ]]; then
npm ci 2>/dev/null || npm install
fi
npm run typecheck
npm run test
cd "${PROJECT_ROOT_DIR}"
}
build(){
build_ui
log_status "Building Go binaries"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go build -o ../bin/coordinator-api ./cmd/api
go build -o ../bin/worker ./cmd/worker
cd "${PROJECT_ROOT_DIR}/webapp"
go build -o ../bin/reactorcide-web .
cd "${PROJECT_ROOT_DIR}"
echo "✅ Binaries built in ./bin/"
}
docker_build(){
log_status "Building Docker images"
local runtime
runtime="$(container_cmd)" || {
echo "❌ No usable container runtime found. Tried nerdctl first, then docker."
exit 1
}
echo "Using container runtime: ${runtime}"
# Build runnerbase image
${runtime} build -t reactorcide/runnerbase:latest -f runnerlib/Dockerfile.runner runnerlib/
# Build Go binary for coordinator and worker
log_status "Building Go binary"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o reactorcide .
cp reactorcide "${PROJECT_ROOT_DIR}/deployment/"
cd "${PROJECT_ROOT_DIR}"
# Build coordinator image
${runtime} build -t reactorcide/coordinator:latest -f deployment/Dockerfile.coordinator deployment/
# Build worker image
${runtime} build -t reactorcide/worker:latest -f deployment/Dockerfile.worker deployment/
# Cleanup coordinator/worker binary
rm -f "${PROJECT_ROOT_DIR}/deployment/reactorcide"
# Build webapp binary. The UI is embedded, so it must exist first.
build_ui
log_status "Building webapp binary"
cd "${PROJECT_ROOT_DIR}/webapp"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o reactorcide-web .
cp reactorcide-web "${PROJECT_ROOT_DIR}/deployment/"
cd "${PROJECT_ROOT_DIR}"
# Build web image
${runtime} build -t reactorcide/web:latest -f deployment/Dockerfile.web deployment/
# Cleanup
rm -f "${PROJECT_ROOT_DIR}/deployment/reactorcide-web"
echo "✅ Docker images built:"
echo " - reactorcide/runnerbase:latest"
echo " - reactorcide/coordinator:latest"
echo " - reactorcide/worker:latest"
echo " - reactorcide/web:latest"
}
clean(){
log_status "Cleaning generated files"
rm -rf "${PROJECT_ROOT_DIR}"/bin/
rm -rf "${PROJECT_ROOT_DIR}"/runnerlib/.pytest_cache
rm -rf "${PROJECT_ROOT_DIR}"/runnerlib/__pycache__
rm -rf "${PROJECT_ROOT_DIR}"/runnerlib/src/__pycache__
rm -rf "${PROJECT_ROOT_DIR}"/runnerlib/tests/__pycache__
find "${PROJECT_ROOT_DIR}"/ -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true
echo "✅ Cleaned"
}
dev(){
log_status "Starting local development stack"
if [ ! -f "docker-compose.yml" ]; then
echo "❌ docker-compose.yml not found"
echo "Please create it first or run from the project root"
exit 1
fi
local compose
compose="$(compose_cmd)" || {
echo "❌ No usable compose command found. Tried nerdctl compose, docker compose, then docker-compose."
exit 1
}
echo "Using compose command: ${compose}"
${compose} up -d
echo "✅ Development stack started"
echo "Run '${compose} logs -f' to see logs"
}
test_e2e(){
log_status "Running end-to-end tests"
# Check if test script exists
if [ ! -f "${PROJECT_ROOT_DIR}/examples/run_e2e_tests.sh" ]; then
echo "❌ E2E test script not found"
exit 1
fi
# Run E2E tests
bash "${PROJECT_ROOT_DIR}/examples/run_e2e_tests.sh" "$@"
}
test_e2e_quick(){
log_status "Running quick E2E tests"
# Check if test script exists
if [ ! -f "${PROJECT_ROOT_DIR}/examples/e2e_test.sh" ]; then
echo "❌ Quick E2E test script not found"
exit 1
fi
# Run quick tests
bash "${PROJECT_ROOT_DIR}/examples/e2e_test.sh" quick
}
test_run_local_paths(){
log_status "Running run-local path contract test"
bash "${PROJECT_ROOT_DIR}/scripts/test-run-local-paths.sh" "$@"
}
deadcode(){
log_status "Checking for unreachable Go functions"
cd "${PROJECT_ROOT_DIR}/coordinator_api"
go run golang.org/x/tools/cmd/deadcode@v0.49.0 -test ./...
cd "${PROJECT_ROOT_DIR}"
}
# This should be last in the script, all other functions are named beforehand.
case "$1" in
"setup")
shift
setup "$@"
;;
"test")
shift
test "$@"
;;
"test-runnerlib")
shift
test_runnerlib "$@"
;;
"test-coordinator")
shift
test_coordinator "$@"
;;
"test-web")
shift
test_web "$@"
;;
"test-web-integration")
shift
test_web_integration "$@"
;;
"test-ui")
shift
test_ui "$@"
;;
"run-api")
shift
run_api "$@"
;;
"run-worker")
shift
run_worker "$@"
;;
"build")
shift
build "$@"
;;
"build-ui")
shift
build_ui "$@"
;;
"docker-build")
shift
docker_build "$@"
;;
"clean")
shift
clean "$@"
;;
"dev")
shift
dev "$@"
;;
"updatedb")
shift
updatedb "$@"
;;
"check-deps")
shift
check_deps "$@"
;;
"test-e2e")
shift
test_e2e "$@"
;;
"test-e2e-quick")
shift
test_e2e_quick "$@"
;;
"test-run-local-paths")
shift
test_run_local_paths "$@"
;;
"deadcode")
shift
deadcode "$@"
;;
*)
usage
;;
esac
exit 0