Every image, the standalone tarballs and the Lite zips are built with
source maps (`BULWARK_SOURCE_MAPS=1`). Turbopack builds give each chunk
a debug ID themselves; the webpack build in the Docker image gets them
from `sentry-cli sourcemaps inject`. `scripts/sourcemaps.mjs collect`
then moves every map out of the build before anything is packed, and
`scripts/sourcemaps.mjs check` fails the build when a `.map` file, an
inline map or a `sourceMappingURL` to a missing file would ship.
- Docker images: a `sourcemaps` stage holds only the maps; the amd64
job exports it (a cache hit) and uploads it, since both platforms
build identical chunks
- standalone tarballs, the Lite zips and the Lite image upload their
own maps and check what they ship
- `scripts/upload-sourcemaps.sh` uploads to `bulwark-instances` and
`bulwark-telemetry` (Bugsink keeps maps per project) and skips with a
notice when the `BUGSINK_AUTH_TOKEN` secret is missing
- `/api/metrics` in Prometheus text format, off until `METRICS_TOKEN`
or `METRICS_TOKEN_FILE` is set (404 before, 401 without the bearer
token): requests per route group with status class and latency,
sign-in outcomes, calls to Stalwart, WOPI, translation, the plugin
proxy and OAuth, browser errors, errors today, process memory,
event-loop lag, uptime and build info. Labels come from fixed lists.
Each replica serves its own.
- `/api/health/live` answers while the process runs;
`/api/health/ready` checks the config, the shared store, writable
data dirs and Stalwart's session resource. `/api/health` and
`/api/ready` are unchanged.
- the Docker image gets a HEALTHCHECK on the live endpoint
- adds prom-client, and the OpenTelemetry packages the tracing step
loads on demand
Every plugin iframe loaded /plugin-sandbox, a force-dynamic Next page.
Each frame then fetched, parsed and hydrated Next's client router on top
of React and the runtime, and the server rendered the page once per
frame, one after another when a dozen plugins started together.
Sandboxed frames have opaque origins, so the browser's code cache never
helps, and they run on the host page's main thread: with 12 plugins
the frames took 2.5-3 s to come up after the app had loaded.
The sandbox routes now return a small HTML page with the UI kit's
stylesheet and the runtime as an ES module, both built ahead of time:
- `scripts/build-plugin-sandbox.mjs` bundles the runtime with esbuild
and compiles `plugin-kit.css` with the app's Tailwind setup into
`public/plugin-sandbox-assets/`, with a manifest. Named Tabler imports
are pointed at the icons' own files, so the full icon set stays in a
chunk that only loads for an icon name the kit doesn't ship.
- `npm run build`, `npm run dev`, the Dockerfile and the perf-guard
workflow run it before `next build`.
- `lib/plugin-sandbox/sandbox-document.ts` writes the page; proxy.ts
still sets the sandbox CSP and the script carries its nonce.
- The assets are served from public/ with CORS and immutable caching.
A frame on an idle page is ready in 255 ms instead of 362 ms, eleven at
once in 1.0 s instead of 2.1 s.
The mock JMAP server behind `/api/dev-jmap` accepts any password and
shipped in every image, held back only by `DEV_MOCK_JMAP=true` at
runtime. It now also needs the flag at build time (inlined through
`next.config.ts`), so a build made without it cannot switch the mock on,
and the Docker image drops the route before building.
Add GitHub Actions workflow to automatically build and publish Docker
images to ghcr.io on releases. Supports amd64 and arm64 architectures.
Pre-built image available at ghcr.io/root-fr/jmap-webmail.