From a6e614bd7b2cb53ec65eacb189883580700fcca5 Mon Sep 17 00:00:00 2001 From: Meysam Parvizi Date: Fri, 9 Oct 2026 17:08:26 +0200 Subject: [PATCH] ci: add link checker, README convention check and PR templat --- .github/FUNDING.yml | 12 ------- .github/PULL_REQUEST_TEMPLATE.md | 14 ++++++++ .github/dependabot.yml | 6 ++++ .github/lychee.toml | 16 +++++++++ .github/scripts/check_readme.py | 47 +++++++++++++++++++++++++++ .github/workflows/link-check.yml | 44 +++++++++++++++++++++++++ .github/workflows/markdown-lint.yml | 33 +++++++++++++++++++ .github/workflows/static-analysis.yml | 32 ------------------ 8 files changed, 160 insertions(+), 44 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/lychee.toml create mode 100644 .github/scripts/check_readme.py create mode 100644 .github/workflows/link-check.yml create mode 100644 .github/workflows/markdown-lint.yml delete mode 100644 .github/workflows/static-analysis.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8b523c8..207a69d 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,13 +1 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: ['https://www.buymeacoffee.com/m3y54m'] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c398a40 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +# What this changes + + + +## Checklist + +Based on [CONTRIBUTING.md](../CONTRIBUTING.md). + +- [ ] Each entry uses `- [[đŸ‘ļ][💎] Title](url)` with the markers explained in the README +- [ ] The resource fits the section it is placed in +- [ ] The link opens and the content matches the title +- [ ] Free resources are preferred; a paid one is included only if clearly better than the free options +- [ ] No pile of entries from a single author or creator without a clear reason +- [ ] Advanced material is labeled as such, so beginners are not overwhelmed diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3a626c3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly diff --git a/.github/lychee.toml b/.github/lychee.toml new file mode 100644 index 0000000..c86b142 --- /dev/null +++ b/.github/lychee.toml @@ -0,0 +1,16 @@ +# Many sites in the README (Udemy, Medium, NXP, Microchip, ...) answer bots with 403/429 +# while working fine in a browser, so those codes count as alive. +accept = [200, 203, 206, 403, 429] + +# Amazon and LinkedIn return bot challenges or non-standard codes for every request. +exclude = [ + '^https?://(www\.)?amazon\.', + '^https?://a\.co/', + '^https?://(www\.)?linkedin\.com/', +] + +max_retries = 3 +retry_wait_time = 5 +timeout = 30 +max_concurrency = 8 +user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36" diff --git a/.github/scripts/check_readme.py b/.github/scripts/check_readme.py new file mode 100644 index 0000000..e6a198d --- /dev/null +++ b/.github/scripts/check_readme.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Check README.md resource entries against the conventions in CONTRIBUTING.md.""" +import re +import sys +from pathlib import Path + +README = Path(__file__).resolve().parents[2] / "README.md" + +# Type marker, then optional beginner (đŸ‘ļ), then optional invaluable (💎). +ENTRY = re.compile(r"^- \[(?:📘|đŸŽžī¸?|📝|🔗|🎧)đŸ‘ļ?💎? .+\]\(https?://\S+\)") +LIST_LINK = re.compile(r"^- \[") +URL = re.compile(r"\]\((https?://[^)\s]+(?:\([^)\s]*\))?[^)\s]*)\)") +BAD_CHARS = {"\ufffd": "U+FFFD replacement character", "\u200b": "zero-width space", + "\u200c": "zero-width non-joiner"} +HEADING = re.compile(r"^#{2,6} ") + + +def main() -> int: + errors = [] + section = "" + seen = {} + for number, line in enumerate(README.read_text(encoding="utf-8").splitlines(), 1): + for char, name in BAD_CHARS.items(): + if char in line: + errors.append(f"{number}: {name}") + if HEADING.match(line): + section = line + seen = {} + continue + if not LIST_LINK.match(line): + continue + if not ENTRY.match(line): + errors.append(f"{number}: entry does not match '- [[đŸ‘ļ][💎] Title](url)'") + continue + match = URL.search(line) + if match: + url = match.group(1) + if url in seen: + errors.append(f"{number}: duplicate URL in section, first used on line {seen[url]}") + seen[url] = number + for error in errors: + print(f"README.md:{error}") + return 1 if errors else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 0000000..76c33fc --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,44 @@ +name: Link check + +on: + schedule: + - cron: "0 6 * * 1" + pull_request: + paths: + - "**/*.md" + - ".github/lychee.toml" + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lychee: + name: Check links + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check links + id: lychee + uses: lycheeverse/lychee-action@v2 + with: + args: --config .github/lychee.toml --no-progress "**/*.md" + output: lychee/out.md + # Pull requests report broken links without blocking; scheduled runs fail and open an issue. + fail: ${{ github.event_name != 'pull_request' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Report broken links + if: github.event_name != 'pull_request' && steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@v5 + with: + title: Link check report + content-filepath: lychee/out.md + labels: broken-links diff --git a/.github/workflows/markdown-lint.yml b/.github/workflows/markdown-lint.yml new file mode 100644 index 0000000..f7ffc6a --- /dev/null +++ b/.github/workflows/markdown-lint.yml @@ -0,0 +1,33 @@ +name: Markdown lint + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + markdownlint: + name: Markdown lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: DavidAnson/markdownlint-cli2-action@v19 + with: + globs: "**/*.md" + + readme-conventions: + name: README conventions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check entry format and duplicates + run: python3 .github/scripts/check_readme.py diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml deleted file mode 100644 index cd62ea2..0000000 --- a/.github/workflows/static-analysis.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Static Analysis - -on: - push: - pull_request: - workflow_dispatch: - -jobs: - markdownlint: - name: Markdown Lint Check - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install markdownlint-cli - run: npm install -g markdownlint-cli - - - name: Run markdownlint - run: | - echo "🔍 Checking Markdown files..." - if find . -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*" | head -1 | grep -q .; then - find . -name "*.md" -not -path "./node_modules/*" -not -path "./.git/*" -print0 | xargs -0 markdownlint --config .markdownlint.json || markdownlint **/*.md - echo "✅ Markdown lint check completed successfully" - else - echo "â„šī¸ No Markdown files found to check" - fi \ No newline at end of file