Releases: elfensky/helldivers.bot
Releases · elfensky/helldivers.bot
Release list
v0.65.0
Features
- War Narrative enrichment on
/archives. The Ministry chronicle now varies
its phrasing per season (deterministic, SSR-stable), and gains player
surge/collapse beats, offensive conquest milestones (a faction driven to its
homeworld's gates / the first homeworld to fall), and a "war by the numbers"
telemetry beat. Computed server-side, sogetCampaignand the rest of the app
are untouched; telemetry-backed beats appear only for seasons with telemetry.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.65.0
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.65.0
v0.60.0
Features
- Combat Telemetry on
/stats(#178). Three telemetry vizzes fed by the
sums already carried throughgetCrossSeasonStats: a reusableRatioTrendChart
drives Friendly Fire Index (accidentals/kills) and Accuracy Trend
(hits/shots) as cross-season%line charts, plus a Shots per Planet "big
number" StatCard.computeTelemetryStatsfilters to telemetry-bearing seasons
(live-polled wars; historical seasons predate collection) and narrows BigInt
sums to Number so the result is safe across the server→client boundary. The
section and each viz hide when no telemetry exists, so it grows as the worker
records more wars. - Player Engagement chart on
/archives(#275). A new lazy-loaded
per-faction scatter ofplayers_at_startvs. day-into-war, showing whether the
community rallied, declined, or surged for the final battles. Hidden when a
season has no event player data. - Closest Calls on
/archives(#273). The global archives stats now surface
the top-3 defend events that came nearest to holding (points/points_max
closest to 1.0 but short), each a danger-accented StatCard with region ·
faction · % held. Restricted to defend fails — the only events with a clean
margin signal — and hidden when a war had no genuine nail-biters.
Fixed
- Lockfile synced to
0.60.0—package-lock.jsonstill carried0.59.1. /statsCombat Telemetry charts deferred vianext/dynamic— added
RatioTrendChartLoaderso the Recharts bundle stays out of the initial page
JS, matching the/archiveschart loaders.metrics.ymlpinned back tolowlighter/metrics@v3.34— a CI group bump
had silently moved it to the unreleased@v4dev branch (not a release tag).buildEngagementSerieshardened and unit-tested — extracted to its own
module; the anchor fallback usesreduceinstead ofMath.min(...spread)(no
RangeErroron large event arrays), and the telemetry ratios guard missing
BigInt fields with?? 0n.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.60.0
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.60.0
v0.59.0
Features
- OpenAPI coverage for the
/v1endpoints (#438).GET /api/v1/h1/{status, stats,season,map}are now registered in the OpenAPI spec with query params,
typed response schemas, the{time,code,message,data}envelope, and the
401/404/429 + 304 responses — so/docs/apidocuments the real public API. - Rebroadcast action reconciliation (#438). The spec now declares all five
HD1-API actions;get_available_entitlements,get_leaderboards, and
get_usernamesreturn an explicit 501 Not Implemented (Demand-Driven
Compatibility) instead of a silent 404. Adds501to the error envelope.
Changed
- Public/internal API boundary.
/docs/api(and the OpenAPI spec) now
document the public surface only — the versioned/api/v1/h1/*reads,
/api/h1/rebroadcast, and the now-deprecated/api/h1/campaign. Internal
plumbing (/api/h1/live,/api/h1/update,/api/notifications/subscribe) is
excluded from the spec and noted as internal in prose. Deprecated endpoints
render a badge. - Cascade Failures leaderboard requires length ≥4.
findAllCascadesnow
defaultsminLengthto 4 (was 3) — length-3 runs are too common to be
noteworthy and made the board noisy.getCascadeLeaderboarddrops its explicit
override and uses the new default.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.59.0
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.59.0
v0.52.6
Security
- Resolve five transitive-dependency advisories via
npm audit fix(semver-compatible,
lockfile-only —package.jsonunchanged):undici(high, multiple CVEs),form-data
(high, CRLF injection),dompurify(moderate, XSS bypasses),js-yaml(moderate, DoS),
and@babel/core(low, arbitrary file read).npm auditnow reports 0 vulnerabilities.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.52.6
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.52.6
v0.52.2
Fixes
- Fix image-optimizer 500s (broken avatars/icons; 502s at the edge) caused by Sharp's native binary not loading on the Chainguard runtime. A latent bug unmasked by the 0.52.1
.next/cachepermission fix: with the cache now writable, the Next.js image optimizer finally runs far enough to load Sharp — and fails.Dockerfile.appbuilt onnode:24-alpine(musl) and stripped Sharp's glibc binaries, but the runtime imagecgr.dev/chainguard/nodeis glibc (Wolfi), so only@img/sharp-linuxmusl-x64shipped into a glibc runtime →Could not load the "sharp" module using the linux-x64 runtime→ every/_next/imagerequest 500s. Fixed by building on a glibc base (node:24Debian) and removing the musl-only strip, so Sharp's glibc binary (@img/sharp-linux-x64) is installed, traced into the standalone output, and loads at runtime. Verified by building the productionlinux/amd64image and confirming Sharp both loads and encodes a WebP. (Prisma was unaffected because it uses the@prisma/adapter-pgJS driver, not a native query engine.)
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.52.2
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.52.2
v0.52.1
Fixes
- Fix the
EACCES: permission denied, mkdir '/app/.next/cache'flood in the production container by chowning the runtime image by numeric uid instead of the unresolvable namenonroot. Root cause: the Chainguard runtime (cgr.dev/chainguard/node:latest) has nononrootentry in/etc/passwd— uid 65532 is namednode— so the runner-stageCOPY --from=builder --chown=nonroot:nonroot …lines silently fell back to root (0:0). That left/app/.nextroot-owned (mode 755), and since the container runs as uid 65532, the Next.js image optimizer's first remote-avatar optimization (mkdir('.next/cache/images', { recursive: true })innext/dist/server/lib/disk-lru-cache.external.js, triggered by Discord/GitHub/Google/Gravatar avatars) failed withEACCESand rejected on every subsequent cacheable image request.Dockerfile.appnow uses--chown=65532:65532(numeric IDs need no passwd lookup) on all three runner COPY lines, so the runtime user owns the standalone tree as intended and creates.next/cacheon demand. Verified by reproducing the exact production state (/app/.nextuid=0,mkdir FAILED:EACCES) and confirming the numeric-chown image yieldsuid=65532and a successful write. No application code or schema change; the image cache is ephemeral (Postgres holds all persistence). Requires a rebuilt image to deploy — production was onv0.47.7, which carries the same defect.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.52.1
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.52.1
v0.47.9
Documentation
timeago.jsadoption rationale documented in code (#360). Added a comment toformatTimeAgo.mjsexplaining thattimeago.jsis a deliberate dependency — it replaced a hand-rolledIntl.RelativeTimeFormathelper that lacked edge-case handling — and why a prior "save ~4.2KB by reverting to nativeIntl.RelativeTimeFormat" suggestion was rejected, so a future desloppify pass does not re-flag it. The optional dynamic-import sub-task was not pursued:formatTimeAgois a synchronous helper used by two components, and making it async to defer a ~4.2KB import would ripple through callers for a marginal gain.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.47.9
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.47.9
v0.47.4
Chores
postcsspinned to^8.5.10viapackage.json#overridesto remediate the GHSA-qx2v-qp2m-jg93 / CVE-2026-41305 advisory (PostCSS XSS via unescaped</style>in stringified CSS output, < 8.5.10). The vulnerable copy came in transitively vianext@16.2.6 → postcss@8.4.31; Tailwind 4's own@tailwindcss/postcssdependency already pulledpostcss@8.5.14, proving the 8.5.x line works in our build pipeline. Real-world exploit risk for this app is essentially zero (the vuln requires processing user-submitted CSS through PostCSS's stringifier and embedding the output in an HTML<style>tag — we author all our own CSS via Tailwind utilities and design tokens), but the override is a one-line hygiene fix that drops us from 1 moderate dependabot alert to 0. After install all three postcss consumers (Tailwind, Next, Vite) dedupe onto8.5.15. Verified end-to-end with the localdocker compose -f docker-compose.ci.yml up --buildstack.
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.47.4
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.47.4
v0.47.3
Bug fixes
- Second migrate-container crash from the v0.46.4 validator-protocol cleanup. The v0.46.4 "Validator protocol unified to raw-schema exports" change turned
isValidSeasonfrom(data) => rootSchema.safeParse(data)(callable wrapper) into a raw Zod schema (export const isValidSeason = rootSchema). The CHANGELOG noted that callers insrc/update/season.mjsandsrc/update/status.mjsplus tests were updated — but theprisma/seed/seed.mjs:51caller was missed. With v0.47.0 in production, the migrate container ran the seed and crashed withTypeError: isValidSeason is not a function. v0.47.1 fixed the prior@/sharedimport error in the same file but only verified the file LOADED under raw Node, not that downstream callers EXECUTED — so this latent bug surfaced only on the next docker-compose-up. Fix: changed the single seed call toisValidSeason.safeParse(seasonData)to match every other caller in the codebase. Verified end-to-end with a full localdocker compose -f docker-compose.ci.yml up --buildrun that brought the stack to healthy (the exact verification step the v0.47.1 hotfix should have done).
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.47.3
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.47.3
v0.47.1
Bug fixes
- Migrate container crashed at seed step on
@/sharedimport. The v0.46.x "enum migration" replaced inline string enums insrc/validators/isValidSeason.mjsandisValidStatus.mjswithimport { CAMPAIGN_STATUS, EVENT_STATUS } from '@/shared/enums/events.mjs'. The@/*alias is ajsconfig.jsonconstruct honored by Next.js and Vitest but not by raw Node — sonpm run buildandnpm run test:unitpassed locally while the production migrate container (which runsnode --experimental-strip-types prisma/seed/seed.mjsafterprisma migrate deploy) crashed withERR_MODULE_NOT_FOUND: Cannot find package '@/shared' imported from /app/src/validators/isValidSeason.mjs. Fix: switched both validator imports to relative paths (../shared/enums/events.mjs), added a clarifying comment on the seed-loaded file documenting the constraint, and updatedDockerfile.migrateto alsoCOPY src/shared/enums/events.mjsalongside the validator (the enums module has no further imports, so the copy chain terminates there).
Docker Images
docker pull ghcr.io/elfensky/helldiversbot:v0.47.1
docker pull ghcr.io/elfensky/helldiversbot-migrate:v0.47.1