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.
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.
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: yarn@1.22.22, but the Dockerfile uses npm (see the drift note below).
To build the AWS-dev flavor there is no npm alias — invoke the CLI directly:
Bundle budgets and output layout
Theproduction and aws-dev configurations enforce bundle budgets (angular.json:84-95):
Global styles (PrimeNG light-blue theme, Mapbox GL, FontAwesome, threebox) and scripts (
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)
1
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/.2
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.3
Run
Build and run:
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: yarn@1.22.22, 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.
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
1
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.2
(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.3
Add an npm script (optional)
Mirror the existing pattern, e.g.
"build:staging": "ng build --configuration=staging".4
Run Biome before committing
npm run biome:check:write — CI will auto-fix on the PR otherwise, adding a bot commit.
