diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad5b429..2cb769f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,17 +75,18 @@ jobs: fail-on-broken: 'true' # Informational only, and expected to report findings: the fixture holds - # a deliberately unreachable host and three deliberate redirects. It runs - # with fail-on-broken so that the PR-comment path is exercised, and with - # continue-on-error so that neither those findings nor an outage at one of - # the redirect targets can gate CI. Do not read its result as an - # assertion -- the assertions are the deterministic steps below. + # a deliberately unreachable host and three deliberate redirects. + # + # fail-on-broken is false so that no PR comment is posted. Its findings + # are true by construction, so a comment about them says the same thing + # on every run and therefore says nothing. The check-own-docs job below + # exercises the comment path against links that can actually rot. - name: Live smoke scan (informational, does not gate) continue-on-error: true uses: ./ with: html-path: 'tests/broken-links.html' - fail-on-broken: 'true' + fail-on-broken: 'false' timeout: 30 # A real 200 over real HTTP was the one thing only a public site could @@ -327,4 +328,68 @@ jobs: - name: Test action on empty directory uses: ./ with: - html-path: 'empty-html' \ No newline at end of file + html-path: 'empty-html' + + # Dogfooding, and the only place a PR comment is worth posting. The links + # here are real ones in our own documentation, so a finding names a link we + # publish that came back broken. When the docs are clean there are no + # findings, broken-links-found stays false and no comment is posted at all + # -- which is the point. A comment that appears on every run carries no + # information. + # + # This action reads HTML, so the Markdown is rendered first. Rendering also + # does the filtering for free: the placeholder URLs in the usage examples + # live inside fenced code blocks and never become anchors, so only genuine + # links are checked. + check-own-docs: + runs-on: ubuntu-latest + name: Check links in our own docs + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Render the Markdown docs to HTML + run: | + python3 -m pip install --quiet markdown + mkdir -p docs-html + python3 - <<'PY' + import markdown, pathlib + + out = pathlib.Path('docs-html') + sources = sorted(pathlib.Path('.').glob('*.md')) + [pathlib.Path('tests/README.md')] + for src in sources: + html = markdown.markdown(src.read_text(encoding='utf-8'), + extensions=['fenced_code', 'tables']) + dest = out / (src.as_posix().replace('/', '__') + '.html') + dest.write_text('' + html + '', encoding='utf-8') + print('rendered', src) + PY + + # continue-on-error because an outage at one of the linked sites is not a + # reason to fail this repository's build. The PR comment is the signal. + # + # Status 0 is silenced. From a runner a transport failure cannot be told + # apart from a host that is simply down or throttling this IP range, and + # neither keepachangelog.com nor semver.org is on the legitimate_domains + # allowance, so a blip at either would otherwise post a comment about a + # link that is fine. A real 404 -- the class that found the dead + # Marketplace badge -- is unaffected. The cost is that a domain which + # disappears outright goes unreported here. + - name: Check the links in the rendered docs + id: docs + continue-on-error: true + uses: ./ + with: + html-path: 'docs-html' + fail-on-broken: 'true' + silent-codes: '0,403,503' + timeout: 20 + + - name: Report + run: | + echo "broken: ${{ steps.docs.outputs.broken-link-count }}" + echo "redirects: ${{ steps.docs.outputs.redirect-count }}" + if [ "${{ steps.docs.outputs.broken-links-found }}" = "true" ]; then + echo "::warning::A link in our own documentation came back broken -- see the PR comment" + fi \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 42872f3..de4cbaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- A `check-own-docs` CI job that renders this repository's Markdown and checks the links in it. It is the only job that posts a link-check comment on a pull request, and it posts one only when a link we publish is reported broken. It runs with `silent-codes: '0,403,503'`, so a transient timeout or DNS failure stays quiet and the comment is reserved for a real status such as a 404 + ### Changed - CI serves its own fixtures from `127.0.0.1`, so no gating step depends on the public internet any more. A clean page, a followed redirect, a reported error status and that status silenced by `silent-codes` are all asserted end to end against responses under our control. The scans of `good-links.html` and `broken-links.html` remain, as informational smoke that cannot turn CI red +- The fixture scans no longer post a link-check comment on pull requests. Their findings are true by construction, so the comment reported the same fixture on every run and carried no information + +### Fixed +- The README linked a GitHub Marketplace badge to `https://github.com/marketplace/actions/ai-link-checker`, which returns 404 — the action was never published there. Found by the new `check-own-docs` job on its first run ## [1.1.0] - 2026-08-03 diff --git a/README.md b/README.md index 72bde66..1a446c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # AI-Powered Link Checker Action [![CI](https://github.com/QuantEcon/action-link-checker/actions/workflows/ci.yml/badge.svg)](https://github.com/QuantEcon/action-link-checker/actions/workflows/ci.yml) -[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-AI%20Link%20Checker-blue.svg?colorA=24292e&colorB=0366d6&style=flat&longCache=true&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAM6wAADOsB5dZE0gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAERSURBVCiRhZG/SsMxFEafKoEMFhyrdsFt6FYHNycunTo0Q4LgEhcnW4PgYgchGjoYiQ6ON2ARpK9nCxjUjuIFP+B+h3O/xmE2yVxPOJGkC3RgJ8qA3bQn7SiTKCQdC4J8HDW0v85CZaUHNzxhQcHdJvjZwM4mXaKJ4BdDMKxIsYoim1Smk2X6HPUdCnU5gO5D9POqvayBzY8nwoJJ+G9h9vGB0U8h8dNPgGLKlv1n6cJgAjjfY9lv1CVKq5f3oUAe5dJz9n3RkBhGA1ouJ/hT5a4c8yQQYSdF8vhN5gT1igMgZ9nJgzUqm9E1V+8rbYQhptmEURKA=)](https://github.com/marketplace/actions/ai-link-checker) A sophisticated GitHub Action that validates web links in HTML files with AI-powered suggestions for improvements. Goes beyond traditional link checkers by providing intelligent recommendations and handling modern web challenges. diff --git a/tests/README.md b/tests/README.md index 53ac1ae..af2cf26 100644 --- a/tests/README.md +++ b/tests/README.md @@ -60,6 +60,16 @@ The HTML fixtures are scanned by the `test-action` job, and both Python test fil The `ignore-patterns` assertions in CI use a fixture generated by the workflow rather than one kept here, so that the expected counts stay exact as fixtures are added to this directory. +## The one place a PR comment is posted + +None of the fixtures here post a link-check comment on a pull request, and that is deliberate. Their findings are true by construction — `broken-links.html` contains a host that can never resolve, so a comment about it says the same thing on every run and therefore says nothing. + +The `check-own-docs` job is the exception. It renders this repository's own Markdown to HTML and checks the links in it, so a finding names a link we publish that came back broken. When the docs are clean there are no findings, `broken-links-found` stays `false`, and no comment is posted at all. + +It runs with `silent-codes: '0,403,503'`. From a runner a status 0 cannot be told apart from a host that is momentarily down or throttling the IP range, and neither `keepachangelog.com` nor `semver.org` is on the built-in `legitimate_domains` allowance, so without that setting a blip at either would post a comment about a link that is fine. A real status such as a 404 is unaffected. The trade is that a domain which disappears outright is not reported here. + +Rendering does the filtering for free: the placeholder URLs in the usage examples sit inside fenced code blocks and never become anchors, so only genuine links are checked. It found its first real bug immediately — a README badge pointing at a GitHub Marketplace listing that was never published. + ## Running Tests Locally `action.yml` is a composite action, not an executable — run the checker directly instead. It takes one HTML file and prints a JSON report: