From 6a3f966758b85024b2496d7e3d1414c46f8411dd Mon Sep 17 00:00:00 2001 From: Brian Vargyas Date: Thu, 3 Sep 2026 17:31:21 -0400 Subject: [PATCH] Release 0.2.0: changelog, version reporting, push-to-deploy - CHANGELOG.md following Keep a Changelog, covering 0.1.0 and 0.2.0 - server/version.js reads the version from package.json; /healthz returns it and the footer renders it, so a deployment always states what it is serving - bump package.json 0.1.0 -> 0.2.0 --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++ README.md | 5 +++++ package.json | 2 +- server/routes.js | 3 ++- server/version.js | 7 ++++++ server/views/layout.js | 3 ++- 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 server/version.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9cd5e89 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +All notable changes to Courtside are recorded here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +The running version is reported at `GET /healthz` and shown in the page footer, +so you can always tell what a deployment is actually serving. + +## [Unreleased] + +## [0.2.0] - 2026-09-03 + +First deployed release. Courtside now runs as a managed stack rather than a +`docker compose up` on someone's laptop. + +### Added +- `docker-compose.komodo.yml` — deployment compose for a host that already sits + behind a shared Cloudflare Tunnel. Publishes a host port instead of bundling a + `cloudflared` sidecar, so there is one tunnel to operate rather than one per app. +- Version reporting: `server/version.js` reads the version from `package.json`; + `GET /healthz` returns it as `version`, and the footer renders `Courtside v`. +- This changelog. +- Push-to-deploy: a push to `main` triggers a rebuild and redeploy of the stack. + +### Notes +- The original `docker-compose.yml` (app + its own `cloudflared`) is unchanged and + still the right choice for a standalone install, as documented in `docs/DEPLOY.md`. +- No database migration. State is JSON documents in SQLite created with + `CREATE TABLE IF NOT EXISTS`; upgrading is a container replacement. + +## [0.1.0] - 2026-09-03 + +Initial MVP. + +### Added +- Tournament engine with no I/O (`engine/`): round-robin pools, single and double + elimination with bracket reset, pools-then-bracket, configurable rally scoring + (points, win-by, cap, best-of), standings with tiebreaks, and a court engine that + assigns matches as courts free up and maintains an "up next" queue with ETAs. +- One-QR-code public flow: `/t/` is the sign-up form during check-in, then the + roster, then the live board, then the results page — no re-scanning. +- Per-team pages at `/t//team/` with on-deck and up-now alerts. +- Organizer desk at `/admin`: create tournaments, manage teams, generate pools and + brackets, score matches, correct results, withdraw teams, pause and swap courts, + broadcast messages, print QR codes. +- TV display mode at `/t//display`. +- Live updates over a WebSocket at `/ws/`, with a state re-fetch as a fallback. +- Docker image and compose files, deploy/architecture/organizer/API docs, + engine unit tests, an HTTP smoke test, and a full-day simulator. diff --git a/README.md b/README.md index 50085fd..0b1a457 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,11 @@ ADMIN_PASSWORD=test PORT=3123 npm start & BASE=http://localhost:3123 ADMIN_PASS Architecture and the reasoning behind it: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Endpoints: [docs/API.md](docs/API.md). +## Releases + +Versions and what changed in each: [CHANGELOG.md](CHANGELOG.md). The running +version is in the page footer and at `GET /healthz`. + ## License MIT. See [LICENSE](LICENSE). diff --git a/package.json b/package.json index 1cc1e97..814807d 100644 --- a/package.json +++ b/package.json @@ -1 +1 @@ -{"name":"courtside","type":"module","version":"0.1.0","scripts":{"test":"node --test test/*.test.js","sim":"node simulate.js","start":"node --disable-warning=ExperimentalWarning server/index.js","dev":"node --disable-warning=ExperimentalWarning --watch server/index.js","demo":"node --disable-warning=ExperimentalWarning scripts/seed-demo.js","smoke":"bash test/smoke.sh"},"description":"Self-hosted volleyball tournament desk: one QR code from check-in to live scores","main":"server/index.js","directories":{"test":"test"},"keywords":[],"author":"","license":"MIT","dependencies":{"qrcode":"^1.5.4","ws":"^8.21.3"},"engines":{"node":">=22.13"},"repository":{"type":"git","url":"https://gitea.cloudfreeiot.com/bkvargyas/courtside.git"}} \ No newline at end of file +{"name":"courtside","type":"module","version":"0.2.0","scripts":{"test":"node --test test/*.test.js","sim":"node simulate.js","start":"node --disable-warning=ExperimentalWarning server/index.js","dev":"node --disable-warning=ExperimentalWarning --watch server/index.js","demo":"node --disable-warning=ExperimentalWarning scripts/seed-demo.js","smoke":"bash test/smoke.sh"},"description":"Self-hosted volleyball tournament desk: one QR code from check-in to live scores","main":"server/index.js","directories":{"test":"test"},"keywords":[],"author":"","license":"MIT","dependencies":{"qrcode":"^1.5.4","ws":"^8.21.3"},"engines":{"node":">=22.13"},"repository":{"type":"git","url":"https://gitea.cloudfreeiot.com/bkvargyas/courtside.git"}} \ No newline at end of file diff --git a/server/routes.js b/server/routes.js index 90ca9f9..c50fd63 100644 --- a/server/routes.js +++ b/server/routes.js @@ -1,6 +1,7 @@ import QRCode from 'qrcode'; import { Router, HttpError, readBody, send, esc } from './http.js'; import { publicState } from './public-state.js'; +import { VERSION } from './version.js'; import * as pub from './views/public.js'; import * as adm from './views/admin.js'; @@ -12,7 +13,7 @@ export function buildRouter({ registry, auth, store }) { // ---------- public ---------- r.get('/', (req, res) => send.html(res, pub.homePage(registry.list()))); - r.get('/healthz', (req, res) => send.json(res, { ok: true, tournaments: registry.items.size, uptime: process.uptime() })); + r.get('/healthz', (req, res) => send.json(res, { ok: true, version: VERSION, tournaments: registry.items.size, uptime: process.uptime() })); r.get('/t/:slug', (req, res, { slug }) => { const it = item(slug); diff --git a/server/version.js b/server/version.js new file mode 100644 index 0000000..0b64dab --- /dev/null +++ b/server/version.js @@ -0,0 +1,7 @@ +// Single source of truth for the running version: package.json, read once at start. +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const here = dirname(fileURLToPath(import.meta.url)); +export const VERSION = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')).version; diff --git a/server/views/layout.js b/server/views/layout.js index 993c075..1294f31 100644 --- a/server/views/layout.js +++ b/server/views/layout.js @@ -1,3 +1,4 @@ +import { VERSION } from '../version.js'; import { esc } from '../http.js'; export function layout({ title, body, state = null, script = null, bodyClass = '', admin = null, nav = '' }) { @@ -22,7 +23,7 @@ export function layout({ title, body, state = null, script = null, bodyClass = '
${body}
-
Courtside · self-hosted tournament desk · source
+
Courtside v${VERSION} · self-hosted tournament desk · source
${state ? `` : ''} ${script ? `` : ''}