set shell := ["bash", "-c"] # How many jobs the parallel gate runs at once. Lower this when another # worktree stack is active, or use 1 for a serial debugging run. jobs := env('JUST_JOBS', '4') default: @just --list # Full completion gate. Every check runs in one bounded pool, with likely # long Cypress groups scheduled first and shorter jobs filling free lanes. # Start the worktree stack before running it because Cypress needs Vite. test-all: just par "gate" {{ jobs }} \ frontend-cypress-account frontend-cypress-session \ frontend-cypress-sets backend-types-check \ frontend-cypress-scheduling frontend-build \ backend-test-feature frontend-cypress-today \ frontend-lint-check frontend-type-check backend-test-unit \ frontend-format-check cypress-spec-coverage # Run recipes concurrently, buffering each one's output to its own log. # Report jobs as they finish, then print every failed job's complete log. [private] par label max +targets: #!/usr/bin/env bash set -uo pipefail targets=({{ targets }}) logs=$(mktemp -d) declare -A target_of started_at failed=() next=0 running=0 echo "==> {{ label }} (${#targets[@]} jobs, up to {{ max }} at a time)" while (( next < ${#targets[@]} || running > 0 )); do while (( next < ${#targets[@]} && running < {{ max }} )); do target="${targets[next]}" just "$target" > "$logs/$target.log" 2>&1 & target_of[$!]="$target" started_at[$!]=$SECONDS next=$(( next + 1 )) running=$(( running + 1 )) done wait -n -p finished status=$? running=$(( running - 1 )) target="${target_of[$finished]}" elapsed=$(( SECONDS - started_at[$finished] )) if (( status == 0 )); then printf ' ok %-28s %4ds\n' "$target" "$elapsed" else printf ' FAIL %-28s %4ds\n' "$target" "$elapsed" failed+=("$target") fi done if (( ${#failed[@]} == 0 )); then rm -rf "$logs" exit 0 fi for target in "${failed[@]}"; do printf '\n--- %s ---\n' "$target" cat "$logs/$target.log" done printf '\n{{ label }} failed: %s\n' "${failed[*]}" printf 'logs kept in %s\n' "$logs" exit 1 # Backend backend-test *args: cd backend && php artisan test {{ args }} backend-test-unit *args: cd backend && php artisan test --testsuite=Unit {{ args }} backend-test-feature *args: cd backend && php artisan test --testsuite=Feature {{ args }} backend-types-check: cd backend && composer types:check fresh: cd backend && php artisan migrate:fresh --seed # Frontend frontend-format: cd frontend/website && npm run format frontend-format-check: cd frontend/website && npm run format:check frontend-lint: cd frontend/website && npm run lint frontend-lint-check: cd frontend/website && npm run lint:check frontend-type-check: cd frontend/website && npm run type-check frontend-build: cd frontend/website && npm run build-only frontend-cypress-run: cd frontend/website && npm run test:e2e # Each Cypress spec must belong to exactly one feature group. The complete # gate runs these groups concurrently; the whole-suite recipe above remains # available for focused iteration. frontend_specs_account := "confirm-email login signup" frontend_specs_session := "guest-auth session-auth" frontend_specs_sets := "set-layout sets-dashboard" frontend_specs_scheduling := "set-scheduling" frontend_specs_today := "today-assignments" frontend-cypress-account: just _cypress {{ frontend_specs_account }} frontend-cypress-session: just _cypress {{ frontend_specs_session }} frontend-cypress-sets: just _cypress {{ frontend_specs_sets }} frontend-cypress-scheduling: just _cypress {{ frontend_specs_scheduling }} frontend-cypress-today: just _cypress {{ frontend_specs_today }} [private] _cypress +names: cd frontend/website && specs=$(for name in {{ names }}; do \ printf 'cypress/e2e/%s.cy.ts,' "$name"; done) && \ npm run test:e2e -- --spec "${specs%,}" cypress-spec-coverage: just _spec-coverage {{ frontend_specs_account }} \ {{ frontend_specs_session }} {{ frontend_specs_sets }} \ {{ frontend_specs_scheduling }} {{ frontend_specs_today }} [private] _spec-coverage +names: #!/usr/bin/env bash set -uo pipefail cd frontend/website listed=$(printf '%s\n' {{ names }} | sort) on_disk=$(ls cypress/e2e/*.cy.ts | xargs -n1 basename \ | sed 's/\.cy\.ts$//' | sort) duplicated=$(echo "$listed" | uniq -d) unique=$(echo "$listed" | uniq) ungrouped=$(comm -13 <(echo "$unique") <(echo "$on_disk")) missing=$(comm -23 <(echo "$unique") <(echo "$on_disk")) status=0 if [ -n "$ungrouped" ]; then echo "in no group, so never runs in the gate:" printf ' %s\n' $ungrouped status=1 fi if [ -n "$missing" ]; then echo "listed in a group but not on disk:" printf ' %s\n' $missing status=1 fi if [ -n "$duplicated" ]; then echo "in more than one group, so runs twice:" printf ' %s\n' $duplicated status=1 fi exit $status