Improve README contribution checks (#327)

* Stabilize README link checks

* Enforce README entry ordering

* Ignore CI-blocked lib.rs links
This commit is contained in:
David Gasquez authored and GitHub committed 2026-08-08 11:03:46 +02:00
1 parent 16b7d79974
commit 009e09359b
6 files changed
+177 -62

No files matched your search

-15
View File
@@ -1,15 +0,0 @@
[output]
log=html
[checking]
sslverify=0
[filtering]
checkextern=1
ignorewarnings=http-redirected
ignore=
https://harlequin\.sh.*
https://.*medium\.com.*
https://ibis-project\.org.*
https://enrich\.sh.*
https://streams\.dbconvert\.com.*
+16
View File
@@ -0,0 +1,16 @@
no_progress = true
max_retries = 3
retry_wait_time = 2
# These sites reject automated link checks even though the pages are available.
exclude = [
'^https://harlequin\.sh',
'^https://.*medium\.com',
'^https://ibis-project\.org',
'^https://lib\.rs',
'^https://enrich\.sh',
'^https://streams\.dbconvert\.com',
'^https://communities\.sas\.com',
'^https://www\.packtpub\.com',
'^https://www\.transfermarkt\.co\.uk',
]
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
const [basePath, currentPath] = process.argv.slice(2);
if (!basePath || !currentPath) {
console.error("Usage: check-readme-order.mjs BASE_README CURRENT_README");
process.exit(2);
}
const SORTED_SECTION = "Community Extensions";
const IGNORED_SECTIONS = new Set(["Contents"]);
function headingName(markdown) {
const linkedHeading = markdown.match(/^\[([^\]]+)\]\([^)]+\)$/);
return linkedHeading ? linkedHeading[1] : markdown;
}
function parseSections(path) {
const sections = new Map();
let section = null;
for (const line of readFileSync(path, "utf8").split("\n")) {
const heading = line.match(/^#{2,3}\s+(.+?)\s*$/);
if (heading) {
section = headingName(heading[1]);
if (!sections.has(section)) {
sections.set(section, []);
}
continue;
}
if (!section || !line.startsWith("- [")) {
continue;
}
const item = line.match(/^- \[(.*?)\]\(([^)\s]+)(?:\s+[^)]*)?\)/);
if (!item) {
continue;
}
const label = item[1].replaceAll("`", "").trim();
sections.get(section).push({
sortKey: label.toLowerCase(),
url: item[2],
});
}
return sections;
}
const baseSections = parseSections(basePath);
const currentSections = parseSections(currentPath);
let orderingIsValid = true;
const sortedEntries = currentSections.get(SORTED_SECTION) ?? [];
for (let index = 1; index < sortedEntries.length; index += 1) {
if (sortedEntries[index - 1].sortKey > sortedEntries[index].sortKey) {
orderingIsValid = false;
}
}
for (const [section, currentEntries] of currentSections) {
if (section === SORTED_SECTION || IGNORED_SECTIONS.has(section)) {
continue;
}
const baseEntries = baseSections.get(section) ?? [];
if (baseEntries.length === 0) {
continue;
}
const isExisting = (entry) =>
baseEntries.some(
(baseEntry) =>
baseEntry.url === entry.url || baseEntry.sortKey === entry.sortKey,
);
let foundNewEntry = false;
for (const entry of currentEntries) {
const entryAlreadyExists = isExisting(entry);
if (entryAlreadyExists && foundNewEntry) {
orderingIsValid = false;
} else if (!entryAlreadyExists) {
foundNewEntry = true;
}
}
}
if (!orderingIsValid) {
const message = "Thanks for contributing! Please move your new entry to follow the ordering rules in CONTRIBUTING.md, then update this pull request.";
console.error(`::error file=${currentPath},title=README entry order::${message}`);
process.exit(1);
}
console.log("README entries follow the required ordering.");
+40 -25
View File
@@ -1,31 +1,46 @@
name: Check links in the README
name: Check the README
on: [push, pull_request]
on:
push:
branches: [main]
paths:
- README.md
- .github/lychee.toml
- .github/scripts/check-readme-order.mjs
- .github/workflows/linkchecker.yml
pull_request:
paths:
- README.md
- .github/lychee.toml
- .github/scripts/check-readme-order.mjs
- .github/workflows/linkchecker.yml
workflow_dispatch:
permissions:
contents: read
concurrency:
group: link-check-${{ github.ref }}
cancel-in-progress: true
jobs:
LinkChecker:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check entry order
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
git fetch --no-tags --depth=1 origin "$BASE_REF"
git show FETCH_HEAD:README.md > "$RUNNER_TEMP/README.base.md"
node .github/scripts/check-readme-order.mjs "$RUNNER_TEMP/README.base.md" README.md
- name: Check links
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
fetch-depth: 0
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: pandoc
- name: Convert Markdown to HTML format
run: pandoc README.md --standalone --output README.html
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: '3.9'
- run: pip install linkchecker --user
- name: Link Checker
run: linkchecker README.html --config .github/linkchecker/linkchecker.conf --file-output html/utf-8/report.html
- run: cat report.html >> ${GITHUB_STEP_SUMMARY}
if: success() || failure()
args: --config .github/lychee.toml README.md
lycheeVersion: v0.24.2