Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/link_check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: "Check documentation links"

on:
push:
branches:
- master
- "[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
force_recheck:
description: "Clear lychee cache and recheck all links from scratch"
type: boolean
default: false
pull_request: ~
schedule:
- cron: "0 6 * * *"

jobs:
link-check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.13]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install MkDocs dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Build documentation
run: mkdocs build --strict

- name: Clone versioned repositories for link remap
run: |
mkdir -p repositories
git clone --depth=1 --branch 4.6 https://github.com/ibexa/documentation-developer.git repositories/devdoc-4.6 &
git clone --depth=1 --branch 5.0 https://github.com/ibexa/documentation-developer.git repositories/devdoc-5.0 &
git clone --depth=1 --branch 4.6 https://github.com/ibexa/documentation-user.git repositories/userdoc-4.6 &
git clone --depth=1 --branch 5.0 https://github.com/ibexa/documentation-user.git repositories/userdoc-5.0 &
wait

- name: Build versioned repositories for link remap
run: |
for dir in repositories/devdoc-4.6 repositories/devdoc-5.0 repositories/userdoc-4.6 repositories/userdoc-5.0; do
(cd "$dir" && pip install -q -r requirements.txt && mkdocs build --quiet) &
done
wait

- name: Update remap paths for CI environment
run: |
sed -i "s|/Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker|$GITHUB_WORKSPACE|g" lychee.toml

- name: Restore lychee cache
if: ${{ !inputs.force_recheck }}
uses: actions/cache@v4
with:
path: .lycheecache
key: lychee-${{ github.ref_name }}-${{ hashFiles('lychee.toml') }}
restore-keys: |
lychee-${{ github.ref_name }}-
lychee-

- name: Check links
uses: lycheeverse/lychee-action@v2
with:
args: >-
--config lychee.toml
--cache
--cache-exclude-status "400.."
site
output: lychee-report.md
jobSummary: false
fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload link-check report
if: always()
uses: actions/upload-artifact@v4
with:
name: lychee-report
path: lychee-report.md
if-no-files-found: ignore
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ auth.json
yarn.lock
docs/css/*.map
.deptrac.cache
.lycheecache
/repositories/
4 changes: 2 additions & 2 deletions docs/js/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $(document).ready(function() {
const latestVersionNumber = '5.0';

// replace edit url
let branchName = 'master';
let branchName = '5.0';
const branchNameRegexp = /\/en\/([a-z0-9-_.]*)\//g.exec(document.location.href);
const eolVersions = window.eol_versions ?? [];

Expand All @@ -21,7 +21,7 @@ $(document).ready(function() {
}

if (!/^\d+\.\d+$/.test(branchName) && branchName !== 'latest') {
branchName = 'master';
branchName = '5.0';
}

// Insert version into header links
Expand Down
168 changes: 168 additions & 0 deletions lychee.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
############################# Display #############################

# Verbose program output
verbose = "error"

# Output format
format = "markdown"

# Path to report output file
output = "lychee-report.md"

# Don't show interactive progress bar while checking links.
no_progress = false

############################# Cache ###############################

# Enable link caching to avoid re-checking identical links across runs.
cache = true

# Discard cached results older than this duration.
max_cache_age = "1d"

############################# Runtime #############################

# Maximum number of allowed redirects. Set to 0 to fail on any redirect —
# a redirect usually signals moved or reorganised content that should be
# updated at the source.
max_redirects = 0

# Maximum number of allowed retries before a link is declared dead.
max_retries = 3

# Minimum wait time in seconds between retries of failed requests.
retry_wait_time = 2

# Maximum number of concurrent link checks across all hosts.
max_concurrency = 8

############################# Requests ############################

# Website timeout from connect to response finished (seconds).
timeout = 20

# Comma-separated list of accepted status codes for valid links.
# 429 = Too Many Requests (rate-limited, treat as valid).
accept = ["200", "202", "429"]

# Proceed for server connections considered insecure (invalid TLS).
insecure = false

# Check https/http and file:// (used by remap rules below).
# Relative and root-relative internal links are still skipped because they
# don't match any scheme — internal links are validated by mkdocs build --strict.
scheme = ["https", "http", "file"]

# Use HEAD requests — much faster than GET since no body is downloaded.
# Fragment checking requires GET, so include_fragments is disabled;
# internal anchor links are already validated by `mkdocs build --strict`.
method = "GET"

# Mimic a browser to avoid Cloudflare bot-detection (403) on sites like doc.ibexa.co.
user_agent = "Mozilla/5.0 (compatible; lychee link checker)"

# Do NOT check anchor fragments — requires full GET downloads for every URL.
include_fragments = true

# Do NOT check links inside <code> and <pre> blocks.
include_verbatim = false

############################# Exclusions ##########################

# Exclude URLs from checking (treated as regular expressions).
exclude = [
# LinkedIn blocks automated requests
"^https?://(www\\.)?linkedin\\.com",
# Localhost and loopback addresses
"^https?://localhost",
"^https?://127\\.0\\.0\\.1",
# Placeholder/example domains
"^https?://example\\.com",
# GitHub login/auth pages often rate-limit or redirect bots
"^https?://github\\.com/login",
# Known redirects (302) that are intentional
"^https://support\\.ibexa\\.co/",
"^https://redocly\\.com/redoc/",
"^https://updates\\.ibexa.co",
"^https://console\\.cloud\\.google\\.com",
# Versionless project root links (e.g. /projects/connect, /projects/userguide) appear in
# the MkDocs theme sidebar as cross-project navigation and are not real content links.
# The https form appears as absolute links; the file:// form appears after root_dir resolution
# of root-relative hrefs like /projects/connect in the built HTML.
"^https?://doc\\.ibexa\\.co/projects/[^/]+/?$",
"^file://.*?/site/projects/[^/]+/?$",
]

# Exclude these input paths from being scanned.
exclude_path = [
# Search index, assets and sitemap contain no meaningful external links
"site/search/search_index.json",
"site/assets",
"site/404.html",
"site/sitemap.xml",
"site/robots.txt",
"site/update_and_migration/migrate_to_ibexa_dxp",
"site/update_and_migration/from_1.x_2.x/",
]

# Check the specified file extensions
extensions = ["html"]

# Exclude all private IPs from checking.
exclude_all_private = true

############################# Local files #########################

# Required to resolve root-relative links (e.g. href="/") found in every page.
# Combined with scheme = ["https", "http"], the resulting file:// paths are
# silently skipped — no HTTP check, no error.
root_dir = "site"

############################# Remap ###############################

# Rewrite doc.ibexa.co links to locally-built MkDocs sites, avoiding HTTP
# requests to Cloudflare-protected hosts.
#
# Two patterns per version:
# 1. Trailing-slash pages → <version>/site/<path>/index.html
# 2. Direct .html files → <version>/site/<path>.html (PHP/REST API refs)
#
# The `repositories/` directory must be populated via:
# git clone --depth=1 --branch 4.6 https://github.com/ibexa/documentation-developer.git repositories/devdoc-4.6
# (and similarly for devdoc-5.0, userdoc-4.6, userdoc-5.0)
# Then build each with: python3 -m mkdocs build
remap = [
# devdoc 4.6 — three patterns in priority order:
# 1. direct .html files (PHP/REST API reference)
# 2. directory paths with trailing slash → index.html
# 3. directory paths without trailing slash → index.html
"https://doc\\.ibexa\\.co/en/4\\.6/(.+\\.html)$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-4.6/site/$1",
"https://doc\\.ibexa\\.co/en/4\\.6/(.+)/$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-4.6/site/$1/index.html",
"https://doc\\.ibexa\\.co/en/4\\.6/([^#]+[^/#])$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-4.6/site/$1/index.html",
# devdoc 5.0
"https://doc\\.ibexa\\.co/en/5\\.0/(.+\\.html)$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-5.0/site/$1",
"https://doc\\.ibexa\\.co/en/5\\.0/(.+)/$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-5.0/site/$1/index.html",
"https://doc\\.ibexa\\.co/en/5\\.0/([^#]+[^/#])$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/devdoc-5.0/site/$1/index.html",
# userdoc 4.6
"https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/(.+\\.html)$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-4.6/site/$1",
"https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/(.+)/$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-4.6/site/$1/index.html",
"https://doc\\.ibexa\\.co/projects/userguide/en/4\\.6/([^#]+[^/#])$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-4.6/site/$1/index.html",
# userdoc 5.0
"https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/(.+\\.html)$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-5.0/site/$1",
"https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/(.+)/$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-5.0/site/$1/index.html",
"https://doc\\.ibexa\\.co/projects/userguide/en/5\\.0/([^#]+[^/#])$ file:///Users/marek/Desktop/repos/mnocon/documentation-developer/link-checker/repositories/userdoc-5.0/site/$1/index.html",
]

############################# Hosts ###############################

# Global limit: at most 2 simultaneous requests to any single host.
host_concurrency = 2

# Global minimum interval between requests to the same host.
host_request_interval = "500ms"

# Stricter throttling for Cloudflare-protected hosts.
# [hosts] tables must come last in the file (TOML section scoping).
[hosts."doc.ibexa.co"]
concurrency = 1
request_interval = "1s"
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ INHERIT: plugins.yml

site_name: Developer Documentation
repo_url: https://github.com/ibexa/documentation-developer
edit_uri: edit/5.0/docs
site_url: https://doc.ibexa.co/en/latest/
copyright: "Copyright 1999-2026 Ibexa AS and others"
validation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% block content %}
<div class="md-header__source">
{% set href = 'https://github.com/ibexa/documentation-developer/tree/master/docs/api/php_api' %}
{% set href = 'https://github.com/ibexa/documentation-developer/tree/5.0/docs/api/php_api' %}
{% if node.file is not null %}
{% set path = node.file.path|split('/', 4) %}
{% set package = path|slice(1, 2)|join('/') %}
Expand Down
Loading