jevstrudel.git / website / src / jev / releases.mjs
1// Release notes: RELEASES.md at the repo root, newest first, one `## `
2// heading per deploy:
3//
4//   ## 2026-09-24.2 — Jev arranges the whole song
5//   - a line a listener would care about
6//
7// The heading's first word is the release's version. The site embeds its
8// own version (ReplPage.astro, meta jev-release) and publishes every note
9// (/release-notes.json). The release notes modal lists every release up to
10// the one it is about, each a heading that expands: when a newer deploy is
11// waiting, the ones newer than the page's own are expanded; after a return
12// visit, the ones not seen yet; opened from the header's version, the newest. The deploy refuses to publish
13// a site whose newest note is already live (tools/deploy/).
14export function parseReleases(text) {
15  const releases = [];
16  for (const block of text.split(/^## /m).slice(1)) {
17    const [heading, ...lines] = block.split('\n');
18    const [, version, title] = heading.match(/^(\S+)\s*(?:[—-]\s*(.*))?$/) ?? [];
19    if (!version) continue;
20    const notes = lines
21      .filter((l) => /^\s*- /.test(l))
22      .map((l) => l.replace(/^\s*- /, '').trim())
23      .filter(Boolean);
24    releases.push({ version, title: title?.trim() ?? '', notes });
25  }
26  return releases;
27}
28
29// The releases a page on `version` has not seen, newest first; all but the
30// newest are dropped when the page's version is unknown.
31export function newerThan(releases, version) {
32  const i = releases.findIndex((r) => r.version === version);
33  return i === -1 ? releases.slice(0, 1) : releases.slice(0, i);
34}
35
36// What the release notes modal lists: every release up to and including
37// `upTo` (all of them when it is unknown), newest first, each with `open`
38// when it is one of `open`'s (or, if none of those is listed, the newest).
39export function releaseList(releases, upTo, open) {
40  const from = releases.findIndex((r) => r.version === upTo);
41  const listed = from === -1 ? releases : releases.slice(from);
42  const opened = new Set(open.map((r) => r.version));
43  if (!listed.some((r) => opened.has(r.version)) && listed[0]) opened.add(listed[0].version);
44  return listed.map((r) => ({ ...r, open: opened.has(r.version) }));
45}