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
This commit is contained in:
@@ -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<x.y.z>`.
|
||||
- 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/<slug>` 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/<slug>/team/<code>` 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/<slug>/display`.
|
||||
- Live updates over a WebSocket at `/ws/<slug>`, 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.
|
||||
@@ -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).
|
||||
|
||||
+1
-1
@@ -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"}}
|
||||
{"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"}}
|
||||
+2
-1
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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 = '
|
||||
<main>
|
||||
${body}
|
||||
</main>
|
||||
<footer class="foot">Courtside · self-hosted tournament desk · <a href="https://gitea.cloudfreeiot.com/bkvargyas/courtside">source</a></footer>
|
||||
<footer class="foot">Courtside v${VERSION} · self-hosted tournament desk · <a href="https://gitea.cloudfreeiot.com/bkvargyas/courtside">source</a></footer>
|
||||
${state ? `<script id="state" type="application/json">${JSON.stringify(state).replace(/</g, '\\u003c')}</script>` : ''}
|
||||
${script ? `<script src="${esc(script)}" defer></script>` : ''}
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user