skygridmap) compiled with the esbuild browser builder into a flat dist/ bundle, then packaged into an nginx container. There is no runtime configuration: the environment (which Gateway URL, which Janus/WS-proxy endpoints, Stripe key, prod-mode flag) is chosen at build time by swapping src/environments/environment.ts for one of five variants via Angular file replacements. Pick the wrong build configuration and the SPA silently points at the wrong backend.
This page covers how those environments map to build configurations, the container image, and the CI/deploy pipeline. For what the app does with these values at runtime, see App Shell & Bootstrap, Frontend ↔ Gateway Integration, and App State & Video.
Environments and build configurations
Every build configuration inangular.json is defined for the single project dashboard. The production configuration is the default (defaultConfiguration: "production" at angular.json:154), so a bare ng build / npm run build produces a production artifact.
| Configuration | Env file (replaces environment.ts) | url (Gateway REST) | production | Optimized | Notes |
|---|---|---|---|---|---|
production (default build) | environment.prod.ts | https://prod.skyhub.ai:5000/api/v1 | true | yes, outputHashing: all | Budgets enforced; live Stripe key |
aws-dev | environment.aws-dev.ts | https://dev.skyhub.ai:5000/api/v1 | true | yes, outputHashing: all | Budgets enforced; no Stripe |
development (default serve) | environment.ts (unchanged) | http://localhost:5000/api/v1 | false | no, sourcemaps + named chunks | ng serve default |
local | environment.local.ts | http://localhost:5000/api/v1 | false | no, sourcemaps | serviceWorker: false; all endpoints localhost |
e2e | environment.e2e.ts | https://prod.skyhub.ai:5000/api/v1 | true | inherits base | Backs Playwright’s webServer |
Environment variable reference
All five files export the same object shape. The base object lives insrc/environments/environment.ts; the per-environment overrides differ only in the fields below.
| Key | Purpose | Prod value | Local value |
|---|---|---|---|
url | Gateway REST base (/api/v1) | https://prod.skyhub.ai:5000/api/v1 | http://localhost:5000/api/v1 |
production | Angular prod-mode flag (enableProdMode()) | true | false |
janusGatewayUrl | Janus WebSocket for WebRTC video | wss://prod.skyhub.ai:8188 | ws://localhost:8188 |
janusIceServers | STUN/ICE servers | ['stun:stun.l.google.com:19302'] | same |
ws_proxy | Gamepad WebSocket proxy (redispad) | wss://prod.skyhub.ai:7070 | ws://localhost:7070 |
assetsUrl | S3 base for drone assets | https://skyhub-prod-assets.s3.eu-central-1.amazonaws.com/drone | same (all envs) |
mapbox.accessToken | Mapbox GL public token | committed pk.eyJ1... (same all envs) | same |
stripePublishableKey | Stripe billing UI key | pk_live_xxx (present only in environment.ts + environment.prod.ts) | absent |
enableIsaacSim | Isaac Sim feature flag | false (all envs) | false |
sseDebounceTime | Stream-update debounce (ms) | 1000 | 1000 |
httpSessionExpiryTime | Client session expiry (minutes) | 8 | 8 |
DEFAULT_LAT / DEFAULT_LNG | Initial map center (Plovdiv, BG) | 42.1354 / 24.7453 | same |
aws-dev is the outlier that routes the gamepad proxy through Cloud Map service discovery: ws_proxy: 'wss://ws_proxy.skyhub-dev.internal:7070' (src/environments/environment.aws-dev.ts:15). The janusGatewayUrl, ws_proxy, and url all encode the jumphost/domain the browser must reach — see VPC, Jumphost & nginx Routing, Janus SFU, and WebSocket Gamepad Proxy.
npm scripts
package.json requires Node >= 24 and pins packageManager: [email protected], but the Dockerfile uses npm (see the drift note below).
| Script | Command | What it does |
|---|---|---|
npm start | ng serve | Dev server, development config (localhost:5000), port 4200 |
npm run start:local | ng serve --configuration=local --host 0.0.0.0 --open | All-localhost endpoints, SW disabled, LAN-accessible |
npm run start:e2e | ng serve --configuration=e2e --host 0.0.0.0 | Serves the e2e config; used as Playwright’s webServer |
npm run build | ng build | Production build (default config) → flat dist/ |
npm run build:local | ng build --configuration=local | Unoptimized, SW-off build |
npm run watch | ng build --watch --configuration development | Rebuild on change |
npm test | jest | Unit tests (jsdom, serial) |
npm run test:coverage | jest --coverage --coverageProvider=v8 | Coverage → coverage/ |
npm run playwright[:ui|:headed|:debug|:report] | playwright test ... | E2E run / inspect |
npm run biome:lint / biome:check / biome:check:write | biome ... | Lint / format-check / auto-fix (CI gate) |
npm run format / format:check | prettier ... | Prettier over src/** (local only) |
Bundle budgets and output layout
Theproduction and aws-dev configurations enforce bundle budgets (angular.json:84-95):
| Budget type | Warning | Error |
|---|---|---|
initial | 2 MB | 2.5 MB |
anyComponentStyle | 50 KB | 80 KB |
adapter.js, janus.js) are declared in angular.json:68-78. CommonJS deps that would otherwise warn (mapbox, lodash, nanoid, etc.) are whitelisted in allowedCommonJsDependencies. Target browsers come from .browserslistrc (last 1 Chrome/FF, last 2 Edge/Safari/iOS, Firefox ESR, no IE11).
Container image (two-stage, nginx on 8001)
Stage 1 — build (node:latest)
Dockerfile:4-16 runs npm install then npm run build with no arguments, so it produces the production configuration (prod.skyhub.ai). Output lands in the flat dist/.Stage 2 — serve (nginx:latest)
Dockerfile:22-27 copies nginx/nginx.conf to /etc/nginx/nginx.conf and dist/ to /usr/share/nginx/html. The SPA is served with a fallback so deep links resolve to index.html.nginx/nginx.conf) is minimal:
nginx/nginx.conf
Gotcha: production.conf is dead config
Gotcha: production.conf is dead config
A second nginx server block,
production.conf, exists at the repo root with a different SPA fallback (try_files $uri $uri/ /index.html?$args). It is never referenced by the Dockerfile — only nginx/nginx.conf is baked in. Ignore production.conf or delete it; the two divergent fallback rules are a trap for future editors.Gotcha: Docker build drifts from CI
Gotcha: Docker build drifts from CI
The Dockerfile uses
npm install on node:latest (unpinned), while the repo declares packageManager: [email protected], engines.node >= 24, and CI pins node 24.11.1 and installs with yarn install (--frozen-lockfile in the Playwright/Biome workflows). A local docker build can resolve a different dependency tree than CI (no lockfile enforcement, npm vs yarn, floating base image).Gotcha: no .dockerignore
Gotcha: no .dockerignore
There is no
.dockerignore, so COPY . /usr/local/app/ pulls node_modules, dist, .git, coverage, and playwright-report into the build context and the stage-1 layer, bloating build time and image size.The service worker (built but unregistered)
angular.json:79-80 sets serviceWorker: true with ngswConfigPath: ngsw-config.json, so every non-local build emits ngsw-worker.js and ngsw.json into dist/. ngsw-config.json defines two asset groups: app (prefetch: index.html, CSS, JS, manifest) and assets (lazy, prefetch update mode, excluding the demo/, demo_mission_history_assets/, and theme directories).
Testing
- Jest (unit)
- Playwright (e2e)
- Biome (lint/format)
jest.config.json uses preset: jest-preset-angular on a jsdom environment, runs serially (maxWorkers: 1) for deterministic results, and collects coverage (json, lcov, text, clover). Native/WebGL deps are mocked via moduleNameMapper so tests don’t touch a real GPU:jest.config.json
setup.jest.ts) mocks mapboxgl, DragEvent, window.CSS/getComputedStyle. Removing or renaming the mocks under src/app/services/__mocks__ silently breaks the suite.Stale docs: the dashboard repo’s own
README.md and CLAUDE.md still describe E2E as Cypress (npm run cypress:open), and .nycrc targets coverage/cypress. No Cypress config or scripts exist — the project uses Playwright. Ignore those references.Continuous integration
Three GitHub Actions workflows all run on push/PR todevelopment and main on Node 24.11.1, installing deps with yarn.
| Workflow | Steps | Artifacts |
|---|---|---|
jest_unit_tests.yml | yarn install → npm test → npm run test:coverage | coverage/ |
playwright.yml | yarn install --frozen-lockfile → playwright install chromium --with-deps → npm run playwright | playwright-report/, test-results/ |
biome.yml | biome-check job: yarn biome:lint + yarn biome:check. biome-auto-fix job (PR only): biome:check:write, then commits fixes back to the PR branch and comments | — |
Branch naming is inconsistent across the repo: CI triggers on
development, but .coderabbit.yaml lists develop/main/master as base branches. Production releases build from the development branch (see below).Production release (AWS CodeBuild)
Production releases are not driven from this repo’s workflows. They run out-of-repo on an AWS CodeBuild project namedskyhub-prod-frontend-build-and-deploy in eu-central-1, building from the development branch on Node 24.11.1. The buildspec lives in the CodeBuild project, not the repo.
slack/notify_start.js (posts :rocket: Started) and slack/notify_finish.js (posts SUCCESS/FAILURE from CODEBUILD_BUILD_SUCCEEDING). Both read CodeBuild env vars: SLACK_WEBHOOK_URL, PROJECT_NAME, COMMIT_HASH, TAG, CODEBUILD_BUILD_ID, CODEBUILD_SOURCE_VERSION, AWS_REGION, CODEBUILD_LOG_PATH.
For how the built static bundle is served and fronted in production, and the parallel Gateway build pipeline, see CI/CD: CodeBuild, ECR & Frontend Deploy and Gateway Build, Docker & Runtime.
Adding a new environment or endpoint
Add the field to all five environment files
Add the key to
environment.ts (the base/default) and to environment.prod.ts, environment.aws-dev.ts, environment.local.ts, environment.e2e.ts. TypeScript’s structural typing means an object missing a field a component reads will fail to compile only where it’s used — keep the shape uniform.(New environment only) add a build configuration
Add a
configurations.<name> block in angular.json with a fileReplacements entry pointing environment.ts → your new file, plus budgets/hashing to match production if it’s an optimized build. Add a matching serve browserTarget if it needs a dev server.Add an npm script (optional)
Mirror the existing pattern, e.g.
"build:staging": "ng build --configuration=staging".
