From 2390f8753b7b6e75483e1855d020c64cf1281b13 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 26 Oct 2026 19:00:12 +0100 Subject: [PATCH] openvidu-dashboard: ./dashboard.sh dev runs the backend and frontend against openvidu-local-deployment dashboard-ce/tools/dev.sh checks the configuration and MongoDB, then starts both development servers, stopped together. The backend reads dashboard-ce/backend/dev.env (local deployment defaults), under a git-ignored .env and the environment; DASHBOARD_DEV_PORT and DASHBOARD_DEV_BACKEND_PORT move the servers. The demo shares the process helpers, now in dev-lib.sh, and runs every server by directory so Ctrl+C prints no pnpm errors. --- openvidu-dashboard/.dockerignore | 4 + openvidu-dashboard/AGENTS.md | 3 +- openvidu-dashboard/README.md | 19 ++- .../dashboard-ce/backend/AGENTS.md | 4 + .../dashboard-ce/backend/dev.env | 7 ++ .../dashboard-ce/backend/package.json | 5 +- .../dashboard-ce/backend/scripts/dev-check.ts | 6 + .../backend/scripts/dev-preload.ts | 4 + .../dashboard-ce/backend/scripts/dev.ts | 110 ++++++++++++++++++ .../backend/src/config/config-file.ts | 7 +- .../backend/test/unit/dev-env.test.ts | 46 ++++++++ .../dashboard-ce/backend/tsconfig.json | 2 +- .../dashboard-ce/tools/demo-lib.sh | 80 +++---------- openvidu-dashboard/dashboard-ce/tools/demo.sh | 4 +- .../dashboard-ce/tools/dev-lib.sh | 80 +++++++++++++ openvidu-dashboard/dashboard-ce/tools/dev.sh | 23 ++++ openvidu-dashboard/dashboard.sh | 10 +- openvidu-dashboard/package.json | 1 - 18 files changed, 336 insertions(+), 79 deletions(-) create mode 100644 openvidu-dashboard/dashboard-ce/backend/dev.env create mode 100644 openvidu-dashboard/dashboard-ce/backend/scripts/dev-check.ts create mode 100644 openvidu-dashboard/dashboard-ce/backend/scripts/dev-preload.ts create mode 100644 openvidu-dashboard/dashboard-ce/backend/scripts/dev.ts create mode 100644 openvidu-dashboard/dashboard-ce/backend/test/unit/dev-env.test.ts create mode 100644 openvidu-dashboard/dashboard-ce/tools/dev-lib.sh create mode 100755 openvidu-dashboard/dashboard-ce/tools/dev.sh diff --git a/openvidu-dashboard/.dockerignore b/openvidu-dashboard/.dockerignore index f1c47d3d0..cd438f516 100644 --- a/openvidu-dashboard/.dockerignore +++ b/openvidu-dashboard/.dockerignore @@ -14,3 +14,7 @@ dashboard-ce/tools # Their manifests keep the workspace consistent with the lockfile. !dashboard-ce/tools/*/package.json PLAN.md +# Development settings, personal ones included, never reach the image. +**/.env +**/.env.* +**/dev.env diff --git a/openvidu-dashboard/AGENTS.md b/openvidu-dashboard/AGENTS.md index 22e1d065c..a8a38ad40 100644 --- a/openvidu-dashboard/AGENTS.md +++ b/openvidu-dashboard/AGENTS.md @@ -49,7 +49,8 @@ sources through the `source` export condition, so type checking, linting and tes Everything runs through `./dashboard.sh` (what CI calls; `help` lists every command, any accepts `--skip-install`): `check` (lint, format check, typecheck, spec validation, unit tests), `test-integration` (Docker), `e2e`, -`contract-check `, `build-image ce`, `smoke-image ce`, and `demo ce`: a local demo on generated data +`contract-check `, `build-image ce`, `smoke-image ce`, `dev` (backend and frontend development +servers against openvidu-local-deployment; README "Development") and `demo ce`: a local demo on generated data (http://localhost:4200, `admin` / `admin`; MongoDB container `openvidu-dashboard-demo-mongo` on port 27018; `DASHBOARD_DEMO_KEEP_DATA=true` reuses the data of a running container). diff --git a/openvidu-dashboard/README.md b/openvidu-dashboard/README.md index 4f05f5144..ba7938903 100644 --- a/openvidu-dashboard/README.md +++ b/openvidu-dashboard/README.md @@ -36,7 +36,7 @@ All tasks run through `./dashboard.sh`: ```bash ./dashboard.sh install # install dependencies ./dashboard.sh check # lint + format check + type check + spec validation + unit tests -./dashboard.sh dev # frontend dev server, proxying /api to a backend on port 5000 +./dashboard.sh dev # backend and frontend dev servers against openvidu-local-deployment ./dashboard.sh demo ce # local demo on generated data: http://localhost:4200, admin / admin ./dashboard.sh test-integration # integration tests against MongoDB (Docker required) ./dashboard.sh e2e # build the application and run the Playwright tests @@ -46,6 +46,23 @@ All tasks run through `./dashboard.sh`: ./dashboard.sh help # list every command ``` +## Development + +`./dashboard.sh dev` runs the backend and frontend development servers against the MongoDB of an +[openvidu-local-deployment](https://github.com/OpenVidu/openvidu-local-deployment) running on this machine, both +reloading on changes: http://localhost:4200, `admin` / `admin`. Ctrl+C stops both. Without a deployment, +`./dashboard.sh demo ce` runs the same servers on generated data instead. + +- Before starting, it checks the configuration and that MongoDB answers, and says what to do when it does not. +- The backend reads `dashboard-ce/backend/dev.env`, the defaults for the local deployment. A git-ignored + `dashboard-ce/backend/.env` overrides them, and the environment overrides both, for example + `DATABASE_URL` for another MongoDB or `LOG_LEVEL=debug`. The deployment's replica set advertises its member + as `mongo:27017`, which only Docker resolves, so a URL from the host needs `directConnection=true`. +- `DASHBOARD_DEV_PORT` (default 4200) and `DASHBOARD_DEV_BACKEND_PORT` (default 5000) move the servers, for + example off port 5000 where macOS runs AirPlay. +- The deployment needs a 3.10 OpenVidu server: data appears once it writes the analytics database, and an + older writer schema stops the check. + ## Container image `openvidu/openvidu-dashboard`, built from `dashboard-ce/docker/Dockerfile` on `node:24-trixie-slim` diff --git a/openvidu-dashboard/dashboard-ce/backend/AGENTS.md b/openvidu-dashboard/dashboard-ce/backend/AGENTS.md index 2f33ecac7..0cc4b85c9 100644 --- a/openvidu-dashboard/dashboard-ce/backend/AGENTS.md +++ b/openvidu-dashboard/dashboard-ce/backend/AGENTS.md @@ -45,6 +45,10 @@ Behaviour is specified in `openspec/specs/*-api`, `api-conventions`, `auth`, `st `entityIdKind()` from typings. - Configuration variables are new `DASHBOARD_*`, or `DATABASE_*` for database connection settings; legacy names keep their 3.9 meaning (`src/config/variables.ts`, unit-tested). The README lists them all. +- `scripts/dev.ts` (exported as `@openvidu-dashboard/backend/dev`) serves `./dashboard.sh dev`: `loadDevEnv()` (the + environment, then `.env`, then `dev.env`; `scripts/dev-preload.ts` for `tsx watch --import`) and + `checkDevConfig()` (`scripts/dev-check.ts`). `dev.env` holds the openvidu-local-deployment defaults; never give + `src/` defaults for required variables, since a deployment must fail when they are missing. ## Rules diff --git a/openvidu-dashboard/dashboard-ce/backend/dev.env b/openvidu-dashboard/dashboard-ce/backend/dev.env new file mode 100644 index 000000000..c4dac6e63 --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/backend/dev.env @@ -0,0 +1,7 @@ +# Defaults of ./dashboard.sh dev, for openvidu-local-deployment running on this machine. Override any of them in +# .env next to this file (git-ignored) or in the environment. +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +# The deployment's replica set advertises its member as mongo:27017, a name only Docker resolves. +DATABASE_URL=mongodb://mongoadmin:mongoadmin@localhost:27017/?directConnection=true +DASHBOARD_BASE_PATH=/ diff --git a/openvidu-dashboard/dashboard-ce/backend/package.json b/openvidu-dashboard/dashboard-ce/backend/package.json index 7a6ba4171..9c4eedca1 100644 --- a/openvidu-dashboard/dashboard-ce/backend/package.json +++ b/openvidu-dashboard/dashboard-ce/backend/package.json @@ -13,14 +13,15 @@ "default": "./dist/index.js" }, "./testing": "./test/support/index.ts", - "./testing/global-mongo": "./test/support/global-mongo.ts" + "./testing/global-mongo": "./test/support/global-mongo.ts", + "./dev": "./scripts/dev.ts" }, "files": [ "dist" ], "scripts": { "build": "tsc -p tsconfig.build.json", - "dev": "tsx watch --conditions=source src/server.ts", + "dev": "tsx watch --conditions=source --import ./scripts/dev-preload.ts src/server.ts", "typecheck": "tsc --noEmit -p tsconfig.json", "test": "vitest run --project unit", "test:integration": "vitest run --project integration --project integration-plans", diff --git a/openvidu-dashboard/dashboard-ce/backend/scripts/dev-check.ts b/openvidu-dashboard/dashboard-ce/backend/scripts/dev-check.ts new file mode 100644 index 000000000..20815c986 --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/backend/scripts/dev-check.ts @@ -0,0 +1,6 @@ +/** Run by `./dashboard.sh dev` before it starts the development servers. */ +import { checkDevConfig } from './dev.js'; + +if (!(await checkDevConfig())) { + process.exitCode = 1; +} diff --git a/openvidu-dashboard/dashboard-ce/backend/scripts/dev-preload.ts b/openvidu-dashboard/dashboard-ce/backend/scripts/dev-preload.ts new file mode 100644 index 000000000..f41ed9873 --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/backend/scripts/dev-preload.ts @@ -0,0 +1,4 @@ +/** Preloaded by the backend development scripts: the environment, then `.env` (git-ignored), then `dev.env`. */ +import { DEV_ENV_FILE, loadDevEnv } from './dev.js'; + +loadDevEnv(['.env', DEV_ENV_FILE]); diff --git a/openvidu-dashboard/dashboard-ce/backend/scripts/dev.ts b/openvidu-dashboard/dashboard-ce/backend/scripts/dev.ts new file mode 100644 index 000000000..caa492a24 --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/backend/scripts/dev.ts @@ -0,0 +1,110 @@ +/** + * Configuration of `./dashboard.sh dev`, exported as `@openvidu-dashboard/backend/dev` for the development scripts of + * extending workspaces: the variable files of the backend development server and the check that runs before it. + */ +import { fileURLToPath } from 'node:url'; + +import { MongoClient } from 'mongodb'; + +import { WriterSchemaStatus } from '@openvidu-dashboard/typings'; + +import { applyEnvFile, loadConfigFile } from '../src/config/config-file.js'; +import { type DashboardConfig, parseConfig } from '../src/config/variables.js'; +import { mongoClientOptions, resolveMongoUrl } from '../src/mongo/mongo-client.js'; +import { olderSchemaMessage, readSchemaStatus } from '../src/schema/schema-status.js'; + +/** The defaults for openvidu-local-deployment running on this machine. */ +export const DEV_ENV_FILE = fileURLToPath(new URL('../dev.env', import.meta.url)); + +const MONGO_HINT = + 'Start openvidu-local-deployment, set DATABASE_URL in the .env of the backend, or run ./dashboard.sh demo for ' + + 'generated data.'; + +/** + * Loads the files that exist, in order, into `env`: a variable keeps the first value it gets, so the environment wins + * over every file and each file over the ones after it. + * + * @throws Error when a file cannot be read. + */ +export function loadDevEnv(files: readonly string[], env: NodeJS.ProcessEnv = process.env): void { + for (const file of files) { + const result = applyEnvFile(file, env); + + if (result.kind === 'unreadable') { + throw new Error(`Cannot read ${file}: ${result.reason}`); + } + } +} + +/** + * Checks the configuration in `env` and that its MongoDB answers with a schema this dashboard supports, printing the + * problem or what the backend will use. + * + * @returns Whether the backend can start. + */ +export async function checkDevConfig(env: NodeJS.ProcessEnv = process.env): Promise { + const problem = await findProblem(env); + + if (problem !== undefined) { + process.stderr.write(`${problem}\n`); + } + + return problem === undefined; +} + +async function findProblem(env: NodeJS.ProcessEnv): Promise { + const file = loadConfigFile(env); + + if (file.kind === 'unreadable') { + return `Cannot read ${file.path} (DASHBOARD_CONFIG_PATH): ${file.reason}`; + } + + const parsed = parseConfig(env); + + if (parsed.kind === 'mongo-disabled') { + return 'MONGO_ENABLED is false: the dashboard stops at startup'; + } + + if (parsed.kind === 'invalid') { + return parsed.errors.map((error) => `Invalid configuration: ${error}`).join('\n'); + } + + return findMongoProblem(parsed.config); +} + +async function findMongoProblem(config: DashboardConfig): Promise { + const url = resolveMongoUrl(config.database); + + if (url.kind === 'missing') { + return `Define DATABASE_URL, or ${url.variables.join(', ')}`; + } + + const redacted = url.url.replace(/\/\/([^:@/]+):[^@/]*@/, '//$1:***@'); + let client: MongoClient; + + try { + client = new MongoClient(url.url, { ...mongoClientOptions(config.database), serverSelectionTimeoutMS: 3_000 }); + } catch (error) { + return `Cannot use the MongoDB settings: ${errorMessage(error)}`; + } + + try { + const schema = await readSchemaStatus(client.db(config.database.name)); + + if (schema.status === WriterSchemaStatus.OLDER && schema.writer !== undefined) { + return `${olderSchemaMessage(schema.writer)} (pull the latest images of the deployment)`; + } + } catch (error) { + return `MongoDB does not answer at ${redacted}: ${errorMessage(error)}\n${MONGO_HINT}`; + } finally { + await client.close(); + } + + process.stdout.write(`User ${config.admin.username}; MongoDB ${redacted}, database ${config.database.name}\n`); + + return undefined; +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/openvidu-dashboard/dashboard-ce/backend/src/config/config-file.ts b/openvidu-dashboard/dashboard-ce/backend/src/config/config-file.ts index 3299bb714..d12ccb6a9 100644 --- a/openvidu-dashboard/dashboard-ce/backend/src/config/config-file.ts +++ b/openvidu-dashboard/dashboard-ce/backend/src/config/config-file.ts @@ -14,10 +14,11 @@ export type ConfigFileResult = export function loadConfigFile(env: NodeJS.ProcessEnv): ConfigFileResult { const path = env.DASHBOARD_CONFIG_PATH?.trim(); - if (!path) { - return { kind: 'none' }; - } + return path ? applyEnvFile(path, env) : { kind: 'none' }; +} +/** Loads the `KEY=value` lines of `path` into `env`; variables already set in `env` keep their value. */ +export function applyEnvFile(path: string, env: NodeJS.ProcessEnv): Exclude { if (!existsSync(path)) { return { kind: 'missing', path }; } diff --git a/openvidu-dashboard/dashboard-ce/backend/test/unit/dev-env.test.ts b/openvidu-dashboard/dashboard-ce/backend/test/unit/dev-env.test.ts new file mode 100644 index 000000000..0ca60d12e --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/backend/test/unit/dev-env.test.ts @@ -0,0 +1,46 @@ +import { mkdtempSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { parseConfig } from '../../src/config/variables.js'; +import { DEV_ENV_FILE, loadDevEnv } from '../../scripts/dev.js'; + +describe('development variables', () => { + function file(content: string): string { + const path = join(mkdtempSync(join(tmpdir(), 'dashboard-dev-')), '.env'); + writeFileSync(path, content); + + return path; + } + + it('keeps the environment over the first file and that file over the next one', () => { + const env: NodeJS.ProcessEnv = { ADMIN_PASSWORD: 'from-env' }; + + loadDevEnv( + [file('ADMIN_PASSWORD=from-local\nLOG_LEVEL=debug\n'), file('LOG_LEVEL=warn\nSERVER_PORT=6000\n')], + env + ); + expect(env).toEqual({ ADMIN_PASSWORD: 'from-env', LOG_LEVEL: 'debug', SERVER_PORT: '6000' }); + }); + + it('skips a file that does not exist', () => { + const env: NodeJS.ProcessEnv = {}; + + loadDevEnv(['/nonexistent/.env'], env); + expect(env).toEqual({}); + }); + + it('defaults to a valid configuration for the local deployment', () => { + const env: NodeJS.ProcessEnv = {}; + + loadDevEnv([DEV_ENV_FILE], env); + + const parsed = parseConfig(env); + + expect(parsed.kind).toBe('config'); + expect(parsed.kind === 'config' && parsed.config.admin.username).toBe('admin'); + expect(env.DATABASE_URL).toBe('mongodb://mongoadmin:mongoadmin@localhost:27017/?directConnection=true'); + }); +}); diff --git a/openvidu-dashboard/dashboard-ce/backend/tsconfig.json b/openvidu-dashboard/dashboard-ce/backend/tsconfig.json index 327d69a74..478925583 100644 --- a/openvidu-dashboard/dashboard-ce/backend/tsconfig.json +++ b/openvidu-dashboard/dashboard-ce/backend/tsconfig.json @@ -5,5 +5,5 @@ "resolveJsonModule": true, "types": ["node"] }, - "include": ["src", "test", "vitest.config.ts"] + "include": ["src", "test", "scripts", "vitest.config.ts"] } diff --git a/openvidu-dashboard/dashboard-ce/tools/demo-lib.sh b/openvidu-dashboard/dashboard-ce/tools/demo-lib.sh index 9e781fa70..ff0a64bc7 100644 --- a/openvidu-dashboard/dashboard-ce/tools/demo-lib.sh +++ b/openvidu-dashboard/dashboard-ce/tools/demo-lib.sh @@ -1,60 +1,20 @@ #!/usr/bin/env bash # Shared parts of the local demos, sourced by dashboard-ce/tools/demo.sh and by the demos of workspaces that extend # these packages: a MongoDB replica set (a container, or DASHBOARD_DEMO_MONGO_URI), 30 days of generated data kept live, -# and the backend and frontend development servers. The caller sets DATABASE and STATS_MODE and runs from the root of -# its workspace. +# and the backend and frontend development servers of dev-lib.sh. The caller sets DATABASE and STATS_MODE and runs +# from the root of its workspace. # DASHBOARD_DEMO_MONGO_URI MongoDB to use instead of the container # DASHBOARD_DEMO_MONGO_PORT host port of the container (default 27018) # DASHBOARD_DEMO_KEEP_DATA true reuses the demo database instead of generating it again +TOOLS_DIR="$(dirname "${BASH_SOURCE[0]}")" + +# shellcheck source=dashboard-ce/tools/dev-lib.sh +source "$TOOLS_DIR/dev-lib.sh" + MONGO_IMAGE="${DASHBOARD_TEST_MONGO_IMAGE:-openvidu/mongodb:8.0.29-r1}" MONGO_CONTAINER=openvidu-dashboard-demo-mongo MONGO_PORT="${DASHBOARD_DEMO_MONGO_PORT:-27018}" -# The frontend development servers proxy /api to port 5000 (proxy.conf.json); another backend gets its own proxy file. -DEFAULT_BACKEND_PORT=5000 -WORK_DIR="$(mktemp -d)" -PIDS=() - -kill_tree() { - local child - - for child in $(pgrep -P "$1" || true); do - kill_tree "$child" - done - - kill "$1" 2>/dev/null || true -} - -cleanup() { - for pid in "${PIDS[@]}"; do - kill_tree "$pid" - done - - rm -rf "$WORK_DIR" -} -trap cleanup EXIT - -# require_free_ports ...: exits when one is in use. -require_free_ports() { - local port - - for port in "$@"; do - if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then - echo "Port $port is in use" >&2 - exit 1 - fi - done -} - -wait_for() { - for _ in $(seq 1 120); do - curl -sf -o /dev/null "$1" && return 0 - sleep 1 - done - - echo "$1 did not answer" >&2 - exit 1 -} start_mongo() { if [ "$(docker inspect -f '{{.State.Running}}' "$MONGO_CONTAINER" 2>/dev/null)" != true ]; then @@ -92,34 +52,22 @@ prepare_data() { if [ "${DASHBOARD_DEMO_KEEP_DATA:-false}" != true ]; then echo "Generating $DATABASE (scenario S, 30 days, $STATS_MODE stats)" - pnpm --silent --filter @openvidu-dashboard/data-generator run generate --uri "$MONGO_URI" --db "$DATABASE" \ + pnpm --silent --dir "$TOOLS_DIR/data-generator" run generate --uri "$MONGO_URI" --db "$DATABASE" \ --scenario S --days 30 --stats-mode "$STATS_MODE" --drop --five-minute-rollups --node-rollups --room-sums \ --active-window 60 --min-active-rooms 10 | tail -2 fi - pnpm --silent --filter @openvidu-dashboard/data-generator run keep-alive --uri "$MONGO_URI" --db "$DATABASE" & + pnpm --silent --dir "$TOOLS_DIR/data-generator" run keep-alive --uri "$MONGO_URI" --db "$DATABASE" & PIDS+=("$!") } -# start_backend [VAR=value...]: the backend development server on the demo database, in the background. +# start_backend [VAR=value...]: the backend development server on the demo database, in +# the background. start_backend() { - local package="$1" + local directory="$1" local port="$2" shift 2 - env SERVER_PORT="$port" DASHBOARD_BASE_PATH=/ ADMIN_USERNAME=admin ADMIN_PASSWORD=admin DATABASE_URL="$MONGO_URI" \ - DATABASE_NAME="$DATABASE" "$@" pnpm --silent --filter "$package" run dev & - PIDS+=("$!") -} - -# start_frontend : the frontend development server proxying /api to that backend. -start_frontend() { - local proxy=() - - if [ "$3" != "$DEFAULT_BACKEND_PORT" ]; then - printf '{ "/api": { "target": "http://localhost:%s", "secure": false } }\n' "$3" >"$WORK_DIR/proxy-$2.json" - proxy=(--proxy-config "$WORK_DIR/proxy-$2.json") - fi - - pnpm --silent --filter "$1" run dev --port "$2" "${proxy[@]}" + start_dev_backend "$directory" "$port" DASHBOARD_BASE_PATH=/ ADMIN_USERNAME=admin ADMIN_PASSWORD=admin \ + DATABASE_URL="$MONGO_URI" DATABASE_NAME="$DATABASE" "$@" } diff --git a/openvidu-dashboard/dashboard-ce/tools/demo.sh b/openvidu-dashboard/dashboard-ce/tools/demo.sh index 03f1fc2fa..d163707a2 100755 --- a/openvidu-dashboard/dashboard-ce/tools/demo.sh +++ b/openvidu-dashboard/dashboard-ce/tools/demo.sh @@ -22,7 +22,7 @@ source dashboard-ce/tools/demo-lib.sh require_free_ports "$DEFAULT_BACKEND_PORT" "$FRONTEND_PORT" prepare_data -start_backend @openvidu-dashboard/backend "$DEFAULT_BACKEND_PORT" +start_backend dashboard-ce/backend "$DEFAULT_BACKEND_PORT" wait_for "http://127.0.0.1:$DEFAULT_BACKEND_PORT/api/healthcheck" echo "OpenVidu Dashboard ce demo: http://localhost:$FRONTEND_PORT (admin / admin)" -start_frontend @openvidu-dashboard/frontend "$FRONTEND_PORT" "$DEFAULT_BACKEND_PORT" +start_frontend dashboard-ce/frontend "$FRONTEND_PORT" "$DEFAULT_BACKEND_PORT" diff --git a/openvidu-dashboard/dashboard-ce/tools/dev-lib.sh b/openvidu-dashboard/dashboard-ce/tools/dev-lib.sh new file mode 100644 index 000000000..425989ca5 --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/tools/dev-lib.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Development servers of the local runs, sourced by dev.sh and demo-lib.sh here and by the scripts of workspaces that +# extend these packages: the backend and frontend development servers in one session, stopped together. The caller +# runs from the root of its workspace. + +# The frontend development servers proxy /api to port 5000 (proxy.conf.json); another backend gets its own proxy file. +DEFAULT_BACKEND_PORT=5000 +WORK_DIR="$(mktemp -d)" +PIDS=() + +kill_tree() { + local child + + for child in $(pgrep -P "$1" || true); do + kill_tree "$child" + done + + kill "$1" 2>/dev/null || true +} + +cleanup() { + for pid in "${PIDS[@]}"; do + kill_tree "$pid" + done + + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +# require_free_ports ...: exits when one is in use. +require_free_ports() { + local port + + for port in "$@"; do + if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then + echo "Port $port is in use" >&2 + exit 1 + fi + done +} + +wait_for() { + for _ in $(seq 1 120); do + curl -sf -o /dev/null "$1" && return 0 + sleep 1 + done + + echo "$1 did not answer" >&2 + exit 1 +} + +# check_dev_backend : exits when the backend there cannot start, with its scripts/dev-check.ts +# and the variables of its scripts/dev-preload.ts. +check_dev_backend() { + pnpm --silent --dir "$1" exec tsx --conditions=source --import ./scripts/dev-preload.ts scripts/dev-check.ts +} + +# start_dev_backend [VAR=value...]: the backend development server in the background. The +# variables win over the files of its scripts/dev-preload.ts. +start_dev_backend() { + local directory="$1" + local port="$2" + + shift 2 + env SERVER_PORT="$port" "$@" pnpm --silent --dir "$directory" run dev & + PIDS+=("$!") +} + +# start_frontend : the frontend development server proxying /api to that +# backend. +start_frontend() { + local proxy=() + + if [ "$3" != "$DEFAULT_BACKEND_PORT" ]; then + printf '{ "/api": { "target": "http://localhost:%s", "secure": false } }\n' "$3" >"$WORK_DIR/proxy-$2.json" + proxy=(--proxy-config "$WORK_DIR/proxy-$2.json") + fi + + pnpm --silent --dir "$1" run dev --port "$2" "${proxy[@]}" +} diff --git a/openvidu-dashboard/dashboard-ce/tools/dev.sh b/openvidu-dashboard/dashboard-ce/tools/dev.sh new file mode 100755 index 000000000..aa8e2f23d --- /dev/null +++ b/openvidu-dashboard/dashboard-ce/tools/dev.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Local development against openvidu-local-deployment (see dev-lib.sh): the backend and frontend development servers, +# both reloading on changes. The backend reads dashboard-ce/backend/dev.env, overridden by dashboard-ce/backend/.env +# (git-ignored) and by the environment. +# Usage: dashboard-ce/tools/dev.sh +# DASHBOARD_DEV_PORT port of the frontend development server (default 4200) +# DASHBOARD_DEV_BACKEND_PORT port of the backend development server (default 5000) +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/../.." + +FRONTEND_PORT="${DASHBOARD_DEV_PORT:-4200}" +BACKEND_PORT="${DASHBOARD_DEV_BACKEND_PORT:-5000}" + +# shellcheck source=dashboard-ce/tools/dev-lib.sh +source dashboard-ce/tools/dev-lib.sh + +require_free_ports "$BACKEND_PORT" "$FRONTEND_PORT" +check_dev_backend dashboard-ce/backend +start_dev_backend dashboard-ce/backend "$BACKEND_PORT" +wait_for "http://127.0.0.1:$BACKEND_PORT/api/healthcheck" +echo "OpenVidu Dashboard: http://localhost:$FRONTEND_PORT" +start_frontend dashboard-ce/frontend "$FRONTEND_PORT" "$BACKEND_PORT" diff --git a/openvidu-dashboard/dashboard.sh b/openvidu-dashboard/dashboard.sh index 3bdc82b72..6def60641 100755 --- a/openvidu-dashboard/dashboard.sh +++ b/openvidu-dashboard/dashboard.sh @@ -35,7 +35,9 @@ Commands: format-check Prettier --check typecheck tsc --noEmit in every package that defines it build Build every package in dependency order - dev Frontend dev server on http://localhost:4200, proxying /api to the backend on port 5000 + dev Backend and frontend dev servers against openvidu-local-deployment: http://localhost:4200, backend + on port 5000 (DASHBOARD_DEV_PORT, DASHBOARD_DEV_BACKEND_PORT); defaults in + dashboard-ce/backend/dev.env, overridden by dashboard-ce/backend/.env and the environment demo [ce] Local demo on generated data: MongoDB (a container, or DASHBOARD_DEMO_MONGO_URI), 30 days of data kept live, backend and frontend dev servers on http://localhost:4200 (admin / admin) test Unit tests (Vitest) of every package that defines them (same as test-unit) @@ -141,7 +143,11 @@ case "$COMMAND" in format-check) run format:check ;; typecheck) run typecheck ;; build) run build ;; - dev) run dev ;; + dev) + require_pnpm + ensure_installed + dashboard-ce/tools/dev.sh + ;; demo) require_ce require_pnpm diff --git a/openvidu-dashboard/package.json b/openvidu-dashboard/package.json index efa447fc1..533d7a582 100644 --- a/openvidu-dashboard/package.json +++ b/openvidu-dashboard/package.json @@ -14,7 +14,6 @@ "format:check": "prettier --check .", "typecheck": "pnpm -r run typecheck", "build": "pnpm -r run build", - "dev": "pnpm --filter @openvidu-dashboard/frontend run dev", "test:unit": "pnpm -r --if-present run test", "test:integration": "pnpm -r --if-present run test:integration", "e2e": "pnpm --filter \"@openvidu-dashboard/e2e^...\" run build && pnpm --filter @openvidu-dashboard/e2e run e2e",