Skip to content

Restore bin/docs-lint and bin/install-hooks#38

Merged
chasecmiller merged 2 commits into
mainfrom
feature/seo-meta-rendering
May 1, 2026
Merged

Restore bin/docs-lint and bin/install-hooks#38
chasecmiller merged 2 commits into
mainfrom
feature/seo-meta-rendering

Conversation

@chasecmiller
Copy link
Copy Markdown
Contributor

@chasecmiller chasecmiller commented May 1, 2026

The .github/workflows/docs-lint.yaml job runs bin/docs-lint on every PR touching docs/, but the script wasn't tracked -- it lived in a stash. Restoring it.

The script enforces the docs-site rules:

  1. No legacy /docs/... links, no ./ or ../ relative links
  2. Filenames match ^[a-z0-9][a-z0-9-]*.md$ (except _index.md)
  3. Every directory under docs/ has _index.md
  4. Every .md file starts with YAML frontmatter
  5. Every page has a title; non-root pages have a weight
  6. Every /documentation/{pkg}/{ver}/{slug} link resolves on disk

Also fixed two relative ../advanced/livewire-widgets.md links in api-reference/widget-contract.md (introduced in the dual-render docs commit) -- replaced with absolute /documentation/layup/v1/... links to match the established convention.

Restored bin/install-hooks alongside since it installs the pre-commit hook that runs bin/docs-lint locally for the same trigger condition the CI workflow uses.

bin/docs-lint passes: 32 files clean.

Summary by CodeRabbit

  • New Features

    • Added a documentation linting tool that validates markdown files for proper structure, naming conventions, frontmatter requirements, and link integrity across documentation pages.
    • Added a git pre-commit hook installer to automatically run documentation validation before commits.
  • Documentation

    • Updated API reference documentation to use versioned absolute links instead of relative paths.

The .github/workflows/docs-lint.yaml job runs `bin/docs-lint` on every
PR touching docs/, but the script wasn't tracked -- it lived in a
stash. Restoring it.

The script enforces the docs-site rules:
  1. No legacy /docs/... links, no ./ or ../ relative links
  2. Filenames match ^[a-z0-9][a-z0-9\-]*\.md$ (except _index.md)
  3. Every directory under docs/ has _index.md
  4. Every .md file starts with YAML frontmatter
  5. Every page has a title; non-root pages have a weight
  6. Every /documentation/{pkg}/{ver}/{slug} link resolves on disk

Also fixed two relative ../advanced/livewire-widgets.md links in
api-reference/widget-contract.md (introduced in the dual-render
docs commit) -- replaced with absolute /documentation/layup/v1/...
links to match the established convention.

Restored bin/install-hooks alongside since it installs the pre-commit
hook that runs bin/docs-lint locally for the same trigger condition
the CI workflow uses.

bin/docs-lint passes: 32 files clean.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Warning

Rate limit exceeded

@chasecmiller has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 31 minutes and 50 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85a34421-6500-4712-93eb-04c72fd76ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 22b767a and 4218b36.

📒 Files selected for processing (1)
  • bin/docs-lint
📝 Walkthrough

Walkthrough

The PR introduces a documentation linting system consisting of a new PHP script (bin/docs-lint) that validates markdown files within docs/ directories for proper structure, frontmatter metadata, and link integrity, paired with a bash installer (bin/install-hooks) that configures a git pre-commit hook to automatically run the linter. A documentation link is also updated to use absolute versioning.

Changes

Cohort / File(s) Summary
Documentation Linting Script
bin/docs-lint
New PHP executable that validates markdown files: enforces _index.md presence in subdirectories, kebab-case filenames, YAML frontmatter with required title and conditional weight fields, and scans/resolves link targets (rejecting legacy /docs/... and relative .//../ patterns, resolving /documentation/ links to on-disk files).
Git Hook Installer
bin/install-hooks
New bash executable that installs a pre-commit hook that triggers bin/docs-lint whenever docs/ or bin/docs-lint files are staged for commit.
Documentation Link Updates
docs/api-reference/widget-contract.md
Replaces relative links to livewire-widgets documentation with absolute versioned links using /documentation/layup/v1/ prefix.

Possibly Related PRs

Poem

🐰 A linter hops through docs with care,
Checking markdown everywhere—
From frontmatter to links so fine,
Pre-commit hooks keep all aligned! ✨


🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Restore bin/docs-lint and bin/install-hooks' directly and specifically summarizes the main changes: restoring two missing executable scripts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/seo-meta-rendering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 31 minutes and 50 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/docs-lint`:
- Around line 74-77: The code calls file_get_contents($path) into $content and
then uses str_starts_with on it, which can throw if file_get_contents returned
false; update the logic around file_get_contents/$content (and references to
$path/$rel) to first detect unreadable files (e.g., if $content === false) and
push a file-level error into $errors[] (like "{$rel}: unreadable file") and skip
the YAML frontmatter checks; only call str_starts_with when $content is a string
to avoid the TypeError.
- Line 83: $isVersionRoot comparison mixes normalized $path with an unnormalized
$docsRoot, causing false negatives on Windows; normalize both sides before
comparing (e.g., run str_replace(DIRECTORY_SEPARATOR, '/', ...) on $docsRoot or
build the expected index path using DIRECTORY_SEPARATOR) so $isVersionRoot =
(normalized($path) === normalized("{$docsRoot}/_index.md")); update the check
that uses $isVersionRoot to use the normalized comparison and ensure any
trailing slashes are handled consistently.
- Around line 200-208: The code constructs $slug from $slugParts and then checks
$candidates with is_file(), but $slugParts can contain traversal segments like
".." allowing paths outside the docs root; update the link-resolution logic in
bin/docs-lint to sanitize/validate $slugParts before joining (reject or remove
"." and ".." and empty segments) or resolve the final candidate path (e.g. with
a realpath-equivalent) and ensure it is inside $docsRoot before calling
is_file(); reference the variables $slugParts, $slug, $candidates and the
is_file() check and enforce that resolved paths start with $docsRoot to prevent
traversal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c5af2da-58da-42b9-8506-28232dcd8527

📥 Commits

Reviewing files that changed from the base of the PR and between 7368122 and 22b767a.

📒 Files selected for processing (3)
  • bin/docs-lint
  • bin/install-hooks
  • docs/api-reference/widget-contract.md

Comment thread bin/docs-lint Outdated
Comment thread bin/docs-lint Outdated
Comment thread bin/docs-lint Outdated
- Path traversal in /documentation/{pkg}/{ver}/... slug resolution.
  parse_url() yields raw URL segments; '..' could reach files outside
  $docsRoot via is_file(), silently validating a broken link. Reject
  '.' / '..' / empty segments before building the candidate path, and
  add a defence-in-depth realpath check that the resolved file is
  actually inside $docsRoot.
- Windows path comparison for $isVersionRoot. The LHS was normalized
  (DIRECTORY_SEPARATOR -> '/') but the RHS embedded the un-normalized
  $docsRoot, so the comparison never matched on Windows -- the version
  root would be falsely flagged as missing `weight`. Normalize both
  sides via $normalizedDocsRoot once, then compare normalized paths.
- file_get_contents() returning false. Under declare(strict_types=1),
  passing false to str_starts_with() throws TypeError, crashing the
  linter on any unreadable file (permission/race). Detect false
  explicitly and emit a per-file 'unreadable file' error instead.

Verified all three behaviours with throwaway fixtures:
  - traversal link -> 'broken link' error (not silent acceptance)
  - chmod 000 file -> 'unreadable file' error (not TypeError)
  - clean docs/ tree -> still passes (32 files)
@chasecmiller chasecmiller merged commit 84b2b4f into main May 1, 2026
2 checks passed
@chasecmiller chasecmiller deleted the feature/seo-meta-rendering branch May 1, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant