pediatric-ai-scribe-v3/Dockerfile
Daniel 759135f76f chore(ts): frontend build scaffold — esbuild + tsconfig.client + main.ts
Strangler-fig migration setup for the frontend. Legacy public/js/*.js
keeps working unchanged; new TypeScript code lives under client/ and
gets bundled into public/dist/ at image-build time.

Adds:
- esbuild@0.28 as a regular dep (so docker build can run the bundler
  without --include-dev). It's small; tsx already pulled in a pinned
  version transitively.
- scripts/build-client.mjs: bundles every top-level client/*.ts file
  into public/dist/<name>.js as ESM with sourcemap. --watch mode for
  dev. Sub-directories under client/ are bundled by their parent
  entry, not separately.
- tsconfig.client.json: browser target (lib: DOM), bundler module
  resolution, isolatedModules:true so esbuild + tsc agree.
- Two npm scripts: build:client, typecheck:client.
- Dockerfile: 'RUN node scripts/build-client.mjs' between COPY and CMD,
  so the prod image ships with the built bundle.
- .gitignore: public/dist/ — build output, never committed.
- public/index.html: <script type="module" src="/dist/main.js">
  appended after legacy script tags.
- client/main.ts: minimal entry point with a console.log marker
  (window.__clientBundle = true) so we can verify the bundle loaded.

Sacred files (per project memory) kept off the migration path:
- public/js/encounters.js (save/idempotency)
- voiceDictation.js, sickVisit.js recording paths (STT plumbing)
These need explicit per-file approval from Daniel before migration.

Verification:
- npm run typecheck → 0 errors (backend)
- npm run typecheck:client → 0 errors (frontend)
- npm run build:client → public/dist/main.js + .map in 3 ms
- 46/46 unit tests pass

Next session: migrate the first real frontend module (small + isolated,
likely extensions.js or memories.js) into client/ as ES module + TS.
2026-04-27 23:54:38 +02:00

66 lines
3.3 KiB
Docker

# ─── OpenBao CLI, copied from upstream image (multi-arch automatic) ───
# Update the tag here to adopt a newer OpenBao. Binary is statically linked,
# safe to drop into the Node alpine image as-is.
FROM openbao/openbao:2.5.3 AS bao-src
FROM node:20-alpine
WORKDIR /app
# ffmpeg: audio conversion for AWS Transcribe (WebM → PCM)
# curl: download Whisper models for browser-based transcription
# jq: JSON parsing for the entrypoint's OpenBao secret-fetch step
RUN apk add --no-cache ffmpeg curl jq
# Pull the bao CLI out of the upstream image — matches host arch because
# buildx pulls the right manifest-list variant per build.
COPY --from=bao-src /bin/bao /usr/local/bin/bao
RUN /usr/local/bin/bao version
COPY package.json ./
# argon2 compiles native code via node-gyp — needs python3/make/g++ at build time
RUN apk add --no-cache --virtual .build-deps python3 make g++ \
&& npm install --omit=dev \
&& apk del .build-deps
COPY . .
# Ensure the entrypoint is executable regardless of host file permissions
RUN chmod +x /app/docker-entrypoint.sh
# Build the TypeScript client bundle (client/*.ts → public/dist/*.js).
# esbuild is in regular deps so this works after npm install --omit=dev.
RUN node scripts/build-client.mjs
RUN mkdir -p /app/data/logs
# Download Browser Whisper (COMPLETE self-hosting - zero CDN dependencies)
# Library + Models all bundled and served from our server
RUN mkdir -p /app/public/models/Xenova/whisper-tiny.en/onnx && \
cd /app/public/models && \
echo "Downloading transformers.js library (worker-compatible build)..." && \
curl -sL -o transformers.min.js https://cdn.jsdelivr.net/npm/@xenova/transformers@2.0.0/dist/transformers.min.js && \
cd Xenova/whisper-tiny.en && \
echo "Downloading Whisper model files..." && \
curl -sL -o config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/config.json && \
curl -sL -o tokenizer.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/tokenizer.json && \
curl -sL -o preprocessor_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/preprocessor_config.json && \
curl -sL -o generation_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/generation_config.json && \
curl -sL -o onnx/encoder_model_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx && \
curl -sL -o onnx/decoder_model_merged_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/decoder_model_merged_quantized.onnx && \
echo "✅ Browser Whisper: 100% self-hosted (library: 760KB, models: 42MB)"
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
# Entrypoint wrapper handles optional OpenBao secret fetch before exec'ing CMD.
# See docker-entrypoint.sh for the logic — it is a no-op if OPENBAO_ADDR is
# unset, so legacy .env-only deployments continue to work unchanged.
ENTRYPOINT ["/app/docker-entrypoint.sh"]
# tsx instead of node so the runtime accepts .ts files transparently as
# the migration progresses. Currently server.js is still plain JS — tsx
# runs it identically. Direct path to the binary avoids npx startup cost.
CMD ["/app/node_modules/.bin/tsx", "server.js"]