Skip to content

Commit b6bb605

Browse files
author
github-actions
committed
Sync docs from infrahub repo
1 parent 0841a70 commit b6bb605

4 files changed

Lines changed: 287 additions & 0 deletions

File tree

docs/plugins/release-notes-data.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Build-time plugin: scans release-notes frontmatter into global plugin data.
3+
* Registered in docusaurus.config.ts; consumed via
4+
* usePluginData("release-notes-data") in <ReleaseFeed/>.
5+
*/
6+
const fs = require("fs");
7+
const path = require("path");
8+
9+
// Minimal parser for the flat `key: value` frontmatter used by release files
10+
// (see scripts/backfill-release-frontmatter.mjs); gray-matter is not a
11+
// dependency of this site.
12+
function parseFrontmatter(src) {
13+
const m = src.match(/^---\n([\s\S]*?)\n---\n/);
14+
if (!m) return {};
15+
const data = {};
16+
for (const line of m[1].split("\n")) {
17+
const kv = line.match(/^([A-Za-z_]+):\s*(.*)$/);
18+
if (!kv) continue;
19+
let value = kv[2].trim();
20+
if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
21+
if (value === "true") value = true;
22+
else if (value === "false") value = false;
23+
data[kv[1]] = value;
24+
}
25+
return data;
26+
}
27+
28+
module.exports = function releaseNotesData() {
29+
return {
30+
name: "release-notes-data",
31+
async contentLoaded({ actions }) {
32+
const dir = path.join(__dirname, "..", "docs", "release-notes", "infrahub");
33+
const releases = fs
34+
.readdirSync(dir)
35+
.map((file) => {
36+
const m = file.match(/^release-(\d+)_(\d+)(?:_(\d+))?\.mdx$/);
37+
if (!m) return null;
38+
const data = parseFrontmatter(fs.readFileSync(path.join(dir, file), "utf8"));
39+
return {
40+
version: m[3] === undefined ? `${m[1]}.${m[2]}` : `${m[1]}.${m[2]}.${m[3]}`,
41+
line: `${m[1]}.${m[2]}`,
42+
major: +m[1],
43+
minor: +m[2],
44+
patch: m[3] === undefined ? null : +m[3],
45+
date: data.release_date ?? null, // ISO string
46+
type: data.release_type ?? "patch", // minor | patch | security
47+
description: data.description ?? "",
48+
breaking: data.breaking === true,
49+
permalink: `/release-notes/infrahub/${file.replace(/\.mdx$/, "")}`,
50+
};
51+
})
52+
.filter(Boolean)
53+
.sort((a, b) => b.major - a.major || b.minor - a.minor || (b.patch ?? 0) - (a.patch ?? 0));
54+
actions.setGlobalData({ releases });
55+
},
56+
};
57+
};

docs/sidebar-releases.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Generates the grouped Infrahub release-notes sidebar.
3+
* Globs docs/release-notes/infrahub/release-*.mdx, parses versions from
4+
* filenames, and returns one collapsed category per minor line (newest line
5+
* first, releases inside a line in chronological ascending order).
6+
*/
7+
import * as fs from 'fs';
8+
import * as path from 'path';
9+
import type { SidebarItemConfig } from '@docusaurus/plugin-content-docs/src/sidebars/types';
10+
11+
const RELEASE_DIR = path.join(__dirname, 'docs', 'release-notes', 'infrahub');
12+
const RELEASE_RE = /^release-(\d+)_(\d+)(?:_(\d+))?\.mdx$/;
13+
14+
type Release = { major: number; minor: number; patch: number | null; id: string };
15+
16+
function readReleases(): Release[] {
17+
return fs
18+
.readdirSync(RELEASE_DIR)
19+
.map((f) => {
20+
const m = f.match(RELEASE_RE);
21+
if (!m) return null;
22+
return {
23+
major: Number(m[1]),
24+
minor: Number(m[2]),
25+
patch: m[3] === undefined ? null : Number(m[3]),
26+
id: `release-notes/infrahub/${f.replace(/\.mdx$/, '')}`,
27+
};
28+
})
29+
.filter((r): r is Release => r !== null);
30+
}
31+
32+
/** One collapsed category per minor line ("1.10 release", "1.9 release", …), newest first. */
33+
export function generateInfrahubReleaseSidebar(): SidebarItemConfig[] {
34+
const byLine = new Map<string, Release[]>();
35+
for (const r of readReleases()) {
36+
const line = `${r.major}.${r.minor}`;
37+
(byLine.get(line) ?? byLine.set(line, []).get(line)!).push(r);
38+
}
39+
40+
const lines = [...byLine.keys()].sort((a, b) => {
41+
const [am, an] = a.split('.').map(Number);
42+
const [bm, bn] = b.split('.').map(Number);
43+
return bm - am || bn - an; // newest line first
44+
});
45+
46+
return lines.map((line) => {
47+
const releases = byLine
48+
.get(line)!
49+
.sort((a, b) => (a.patch ?? 0) - (b.patch ?? 0)); // chronological ascending
50+
51+
// Legacy single-file lines (release-0_6.mdx …): plain doc link, no category.
52+
if (releases.length === 1 && releases[0].patch === null) {
53+
return { type: 'doc', id: releases[0].id, label: `${line} release` } as SidebarItemConfig;
54+
}
55+
56+
const base = releases.find((r) => r.patch === 0 || r.patch === null);
57+
return {
58+
type: 'category',
59+
label: `${line} release`,
60+
collapsed: true,
61+
collapsible: true,
62+
// Clicking the line opens its X.Y.0 release (the narrative feature release).
63+
...(base ? { link: { type: 'doc', id: base.id } } : {}),
64+
items: releases.map((r) => ({
65+
type: 'doc' as const,
66+
id: r.id,
67+
label: r.patch === null ? line : `${line}.${r.patch}`,
68+
})),
69+
} as SidebarItemConfig;
70+
});
71+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* <ReleaseFeed/> — release feed for /release-notes/infrahub.
3+
* Full detail for the newest `detailedLines` release lines, compact rows for
4+
* earlier ones. Reads global data from plugins/release-notes-data.js.
5+
*/
6+
import React from "react";
7+
import Link from "@docusaurus/Link";
8+
import { usePluginData } from "@docusaurus/useGlobalData";
9+
import styles from "./styles.module.css";
10+
11+
type Release = {
12+
version: string;
13+
line: string;
14+
major: number;
15+
minor: number;
16+
patch: number | null;
17+
date: string | null;
18+
type: "minor" | "patch" | "security";
19+
description: string;
20+
breaking: boolean;
21+
permalink: string;
22+
};
23+
24+
const fmtDate = (iso: string | null, month: "long" | "short" = "long") =>
25+
iso
26+
? new Date(`${iso}T00:00:00`).toLocaleDateString("en-US", { year: "numeric", month, day: "numeric" })
27+
: "";
28+
29+
/** Fully-expanded group: X.Y.0 hero first, then updates in chronological (ascending) order. */
30+
function LineGroup({ line, releases }: { line: string; releases: Release[] }) {
31+
const base = releases.find((r) => r.patch === 0 || r.patch === null) ?? releases[0];
32+
const updates = releases.filter((r) => r !== base).sort((a, b) => (a.patch ?? 0) - (b.patch ?? 0));
33+
return (
34+
<section className={styles.group}>
35+
<div className={styles.groupHead}>
36+
<h2>{line}</h2>
37+
<span className={styles.featureBadge}>Feature release</span>
38+
</div>
39+
<Link to={base.permalink} className={styles.heroRow}>
40+
<div className={styles.rowHead}>
41+
<span className={styles.heroVersion}>{base.version}</span>
42+
{base.breaking && <span className={styles.breakingChip}>Breaking changes</span>}
43+
<span className={styles.date}>{fmtDate(base.date)}</span>
44+
</div>
45+
{base.description && <p className={styles.heroSummary}>{base.description}</p>}
46+
</Link>
47+
<div className={styles.updates}>
48+
{updates.map((r) => (
49+
<Link key={r.version} to={r.permalink} className={styles.updateRow}>
50+
<span className={styles.bullet} />
51+
<div className={styles.rowHead}>
52+
<span className={styles.updateVersion}>{r.version}</span>
53+
{r.breaking && <span className={styles.breakingChip}>Breaking</span>}
54+
<span className={styles.date}>{fmtDate(r.date)}</span>
55+
</div>
56+
{r.description && <p className={styles.summary}>{r.description}</p>}
57+
</Link>
58+
))}
59+
</div>
60+
</section>
61+
);
62+
}
63+
64+
/** Compact row for older lines: version · description · date. */
65+
function EarlierRow({ base }: { base: Release }) {
66+
return (
67+
<Link to={base.permalink} className={styles.earlierRow}>
68+
<span className={styles.earlierVersion}>{base.line}</span>
69+
<span className={styles.earlierSummary}>{base.description}</span>
70+
<span className={styles.date}>{fmtDate(base.date, "short")}</span>
71+
</Link>
72+
);
73+
}
74+
75+
/** Newest `detailedLines` version lines fully expanded; the rest as compact rows. */
76+
export default function ReleaseFeed({ detailedLines = 2 }: { detailedLines?: number }) {
77+
const { releases } = usePluginData("release-notes-data") as { releases: Release[] };
78+
const lines: string[] = [];
79+
for (const r of releases) if (!lines.includes(r.line)) lines.push(r.line); // newest line first
80+
const detailed = lines.slice(0, detailedLines);
81+
const earlier = lines.slice(detailedLines);
82+
return (
83+
<div className={styles.feed}>
84+
<h1 className={styles.title}>Infrahub release notes</h1>
85+
{detailed.map((line) => (
86+
<LineGroup key={line} line={line} releases={releases.filter((r) => r.line === line)} />
87+
))}
88+
<section className={styles.group}>
89+
<div className={styles.earlierHead}>
90+
<h2>Earlier releases</h2>
91+
</div>
92+
{earlier.map((line) => {
93+
const rs = releases.filter((r) => r.line === line);
94+
const base = rs.find((r) => r.patch === 0 || r.patch === null) ?? rs[rs.length - 1];
95+
return <EarlierRow key={line} base={base} />;
96+
})}
97+
</section>
98+
</div>
99+
);
100+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/* ReleaseFeed — matches the approved release-notes redesign.
2+
Keyed to Infima vars where they exist; local vars carry the two values the
3+
design fixes explicitly in dark mode (hover overlay, muted text). */
4+
.feed {
5+
--rn-hover: var(--ifm-hover-overlay);
6+
--rn-muted: #525860;
7+
max-width: 760px;
8+
}
9+
[data-theme='dark'] .feed {
10+
--rn-hover: #10333d;
11+
--rn-muted: #a2a8ae;
12+
}
13+
14+
.title { font-size: 1.6rem; font-weight: 700; line-height: 1.25; margin: 0 0 0.5rem; }
15+
16+
.group { margin-top: 2.75rem; }
17+
.groupHead { display: flex; align-items: baseline; gap: 0.75rem; }
18+
.groupHead h2 { font-size: 1.5rem; font-weight: 800; margin: 0; }
19+
20+
.featureBadge { background: #e6f4f9; color: var(--ifm-color-primary-darkest); font-weight: 600;
21+
font-size: 0.72rem; padding: 0.15rem 0.55rem; border-radius: 999px; white-space: nowrap; }
22+
[data-theme='dark'] .featureBadge { background: #0c3844; color: #7fd9f2; }
23+
24+
.breakingChip { border: 1px solid #a3452f; color: #a3452f; font-weight: 600; font-size: 0.72rem;
25+
padding: 0.1rem 0.5rem; border-radius: 999px; white-space: nowrap; }
26+
[data-theme='dark'] .breakingChip { border-color: #e89380; color: #e89380; }
27+
28+
.rowHead { display: flex; align-items: baseline; gap: 0.65rem; flex-wrap: wrap; }
29+
.date { font-size: 0.82rem; color: var(--rn-muted); }
30+
31+
/* Feature release row — fully clickable, quiet hover, no border/shadow */
32+
.heroRow { display: block; margin-top: 0.6rem; border-radius: 0.5rem; padding: 0.7rem 0.5rem;
33+
color: inherit; text-decoration: none; }
34+
.heroRow:hover { background: var(--rn-hover); color: inherit; text-decoration: none; }
35+
.heroVersion { font-size: 1.25rem; font-weight: 700; }
36+
.heroSummary { margin: 0.6rem 0 0; font-size: 0.95rem; line-height: 1.6; text-wrap: pretty; }
37+
38+
/* Updates — indented list, chronological, whole row clickable, hanging bullet */
39+
.updates { margin: 0.25rem 0 0 0.5rem; padding-left: 1rem; display: flex; flex-direction: column; }
40+
.updateRow { display: block; position: relative; padding: 0.7rem 0.5rem; margin: 0 -0.5rem;
41+
border-radius: 0.375rem; color: inherit; text-decoration: none; }
42+
.updateRow:hover { background: var(--rn-hover); color: inherit; text-decoration: none; }
43+
.bullet { position: absolute; left: -1rem; top: 1.32rem; width: 5px; height: 5px;
44+
border-radius: 50%; background: var(--rn-muted); opacity: 0.55; }
45+
.updateVersion { font-weight: 600; font-size: 0.95rem; }
46+
.summary { margin: 0.25rem 0 0; font-size: 0.95rem; color: var(--rn-muted);
47+
line-height: 1.6; max-width: 660px; }
48+
49+
/* Earlier releases — compact rows: version · description · date */
50+
.earlierHead { border-bottom: 1px solid var(--border-color, var(--ifm-color-emphasis-300));
51+
padding-bottom: 0.6rem; margin-top: 3.25rem; }
52+
.earlierHead h2 { font-size: 1.25rem; font-weight: 700; margin: 0; }
53+
.earlierRow { display: grid; grid-template-columns: 64px 1fr auto; gap: 0.9rem;
54+
align-items: baseline; padding: 0.7rem 0.5rem; border-radius: 0.375rem;
55+
color: inherit; text-decoration: none; }
56+
.earlierRow:hover { background: var(--rn-hover); color: inherit; text-decoration: none; }
57+
.earlierVersion { font-weight: 700; font-size: 1rem; }
58+
.earlierSummary { font-size: 0.95rem; color: var(--rn-muted); }
59+
.earlierRow .date { white-space: nowrap; }

0 commit comments

Comments
 (0)