🐛 Check README order within licensing subgroups

This commit is contained in:
David Gasquez committed 2026-09-30 16:25:14 +02:00
1 parent 55461cbaee
commit 5a3f486c2d
4 files changed
+118 -6

No files matched your search

+9 -3
View File
@@ -8,7 +8,7 @@ if (!basePath || !currentPath) {
process.exit(2);
}
const SORTED_SECTION = "Community Extensions";
const SORTED_SECTION = "Extensions / Community Extensions";
const IGNORED_SECTIONS = new Set(["Contents"]);
function headingName(markdown) {
@@ -18,12 +18,18 @@ function headingName(markdown) {
function parseSections(path) {
const sections = new Map();
const headings = [];
let section = null;
for (const line of readFileSync(path, "utf8").split("\n")) {
const heading = line.match(/^#{2,3}\s+(.+?)\s*$/);
const heading = line.match(/^(#{2,6})\s+(.+?)\s*$/);
if (heading) {
section = headingName(heading[1]);
const level = heading[1].length;
while (headings.length > 0 && headings.at(-1).level >= level) {
headings.pop();
}
headings.push({ level, name: headingName(heading[2]) });
section = headings.map(({ name }) => name).join(" / ");
if (!sections.has(section)) {
sections.set(section, []);
}
+103
View File
@@ -0,0 +1,103 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
const entry = (name) => `- [${name}](https://example.com/${name}) - Description.`;
const communityHeading = "## Extensions\n### [Community Extensions](https://duckdb.org/community_extensions/)";
const subgroupBase = [
"## Tools Powered by DuckDB",
"### Business Intelligence & Dashboards",
"#### Free & Open Source",
entry("Pondview"),
"#### Free (Proprietary / Source-Available)",
entry("Proprietary"),
"### Fun & Demos",
"#### Free & Open Source",
entry("Demo"),
].join("\n");
const cases = [
{
name: "append to a flat section",
base: `## Resources\n${entry("Existing")}`,
current: `## Resources\n${entry("Existing")}\n${entry("New")}`,
valid: true,
},
{
name: "reject insertion before an existing flat entry",
base: `## Resources\n${entry("Existing")}`,
current: `## Resources\n${entry("New")}\n${entry("Existing")}`,
valid: false,
},
{
name: "append to a licensing subgroup before another subgroup",
base: subgroupBase,
current: subgroupBase.replace(entry("Pondview"), `${entry("Pondview")}\n${entry("DUX")}`),
valid: true,
},
{
name: "reject insertion within a subgroup despite matching entries in another category",
base: subgroupBase,
current: subgroupBase.replace(entry("Pondview"), `${entry("Demo")}\n${entry("Pondview")}`),
valid: false,
},
{
name: "move an entry to the bottom of its destination subgroup",
base: `${subgroupBase}\n${entry("DUX")}`,
current: subgroupBase.replace(entry("Pondview"), `${entry("Pondview")}\n${entry("DUX")}`),
valid: true,
},
{
name: "reset subgroup context at a top-level section",
base: `${subgroupBase}\n## Resources\n${entry("Existing")}`,
current: `${subgroupBase}\n${entry("NewDemo")}\n## Resources\n${entry("Existing")}\n${entry("NewResource")}`,
valid: true,
},
{
name: "allow updating an existing description in place",
base: `## Resources\n${entry("Existing")}\n${entry("Last")}`,
current: `## Resources\n${entry("Existing").replace("Description.", "Updated description.")}\n${entry("Last")}`,
valid: true,
},
{
name: "keep community extensions case-insensitively sorted",
base: `${communityHeading}\n${entry("`beta`")}`,
current: `${communityHeading}\n${entry("`Alpha`")}\n${entry("`beta`")}`,
valid: true,
},
{
name: "reject unsorted community extensions",
base: `${communityHeading}\n${entry("`beta`")}`,
current: `${communityHeading}\n${entry("`beta`")}\n${entry("`Alpha`")}`,
valid: false,
},
{
name: "ignore contents ordering",
base: `## Contents\n${entry("Existing")}`,
current: `## Contents\n${entry("New")}\n${entry("Existing")}`,
valid: true,
},
];
for (const { name, base, current, valid } of cases) {
test(name, () => {
const directory = mkdtempSync(join(tmpdir(), "readme-order-"));
try {
const basePath = join(directory, "base.md");
const currentPath = join(directory, "current.md");
writeFileSync(basePath, base);
writeFileSync(currentPath, current);
const result = spawnSync(process.execPath, [
new URL("./check-readme-order.mjs", import.meta.url).pathname,
basePath,
currentPath,
], { encoding: "utf8" });
assert.equal(result.status, valid ? 0 : 1, result.stderr || result.stdout);
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
}