Skip to content
Open
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
3 changes: 2 additions & 1 deletion .github/workflows/check-csharp-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Check C# Examples
on:
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read
Expand Down Expand Up @@ -44,6 +45,6 @@ jobs:

- name: Run C# Example Validation
run: |
python scripts/check_csharp_examples.py \
python scripts/validators/check-csharp-examples.py \
--validator valkey-glide-csharp/dev/scripts/validate_examples.py \
--glide-dll valkey-glide-csharp/sources/Valkey.Glide/bin/Release/net8.0/Valkey.Glide.dll
60 changes: 60 additions & 0 deletions .github/workflows/check-node-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Check Node Examples

# Full-compilation validates the Node.js (TypeScript) code examples in the
# docs against the real @valkey/valkey-glide type definitions built from source.
# See scripts/validators/check-node-examples.py.

on:
pull_request:
branches: [main]
workflow_dispatch:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

workflow_dispatch isn't included in the corresponding C# workflow. Should we add it for consistency?


permissions:
contents: read

jobs:
check-node-examples:
runs-on: ubuntu-latest
steps:
- name: Checkout valkey-glide-docs
uses: actions/checkout@v4

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One of the security findings for the C# repo was that we should use commit hashes rather than version numbers, e.g.

uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5

(As I understand it, a commit hash cannot be changed, but a malicious actor who was able to gain control of a repo could update the tag to point to a new commit and so download different code).

Are you able to raise an issue for this on the docs repo? Should be a very easy issue to fix with AI.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Created #261


- name: Checkout valkey-glide
uses: actions/checkout@v4
with:
repository: valkey-io/valkey-glide
path: valkey-glide
Comment on lines +22 to +26

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would there be any advantages to doing a limited checkout of the few directories (e.g. node/, ffi/, and glide-core) that are actually needed for this job?

(The C# validator workflow doesn't checkout the valkey-glide submodule for same reason!)

@Aryex Aryex Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We could restrict it but I don't think it would net us much improvements.

The main bottleneck currently is during the Node client build phase. It has to build the Rust layer and Node, essentially building the full client. The C# checker only has to build the C# layer so it doesn't need valkey-glide submodule, and is a lot quicker.


- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable
with:
toolchain: stable
targets: x86_64-unknown-linux-gnu

- name: Install protoc
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3
with:
version: "25.1"
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev

- name: Build Node client
working-directory: valkey-glide/node
run: |
npm ci
npm run build:release
Comment on lines +48 to +52

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In the C# example, we are able to skip building the Rust core because it actually isn't needed to validate the examples syntax. Are there any similar optimizations that we could perform here? I know that building the Rust core can be quite slow. What about installing all of the node modules?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yup this is currently the biggest bottleneck here. Updating this to match C# would require a code change on Node side although I haven't looked at the scope of this.


- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Validate Node examples
run: python scripts/validators/check-node-examples.py --glide-index valkey-glide/node/build-ts/index.d.ts
1 change: 1 addition & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export default defineConfig({
{
label: "Connections",
items: [
"how-to/connections/circuit-breaker",
"how-to/connections/configure-lazy-connection",
"how-to/connections/limit-inflight-requests",
"how-to/connections/read-strategy",
Expand Down
46 changes: 46 additions & 0 deletions scripts/validators/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Example Validators

Compile the code examples in the docs against the real GLIDE client libraries, to catch broken syntax, wrong method names, and outdated APIs.

## How they work

1. Extract fenced code blocks (e.g. ` ```csharp `, ` ```typescript `) from the MDX docs.
2. Wrap each snippet into a compilable file, injecting common imports/client declarations.
3. Compile everything against a GLIDE client you've already built and point the script at.
4. Report any compiler errors per source file/line.

`_common.py` holds the shared extraction logic used by every language-specific validator.

## Usage

**C#**

You must build `Valkey.Glide.dll` yourself first — the script does not do this for you:

```bash
cd <path_to_valkey-glide-csharp> && dotnet build sources/Valkey.Glide/ --configuration Release /p:SkipCargo=true
```

Then run the validator:

```bash
python scripts/validators/check-csharp-examples.py \
--validator <path_to_valkey-glide-csharp>/dev/scripts/validate_examples.py \
--glide-dll <path_to_valkey-glide-csharp>/sources/Valkey.Glide/bin/Release/net8.0/Valkey.Glide.dll
```

**Node.js**

You must build the client yourself first — the script does not do this for you:

```bash
cd <path_to_valkey-glide>/node && npm ci && npm run build:release
```

Then run the validator:

```bash
python scripts/validators/check-node-examples.py --glide-index <path_to_valkey-glide>/node/build-ts/index.d.ts
```

Both scripts run automatically in CI on every PR (see `.github/workflows/check-csharp-examples.yml` and `.github/workflows/check-node-examples.yml`).
66 changes: 66 additions & 0 deletions scripts/validators/_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Shared helpers for the per-language example validators.

Every validator (C#, Node, and any future language) extracts fenced code
blocks from the MDX docs the same way — only the language tag(s) and the
files to skip differ. Centralizing that logic keeps future validators
consistent and makes it easy to fix bugs (e.g. in the extraction regex)
in one place.
"""

from __future__ import annotations

import os
import re

# Repository root is two levels up from this script's directory
# (scripts/validators/).
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DOCS_DIR = os.path.join(REPO_ROOT, "src", "content", "docs")


def extract_all(
languages: list[str],
*,
skip_patterns: list[str] | None = None,
) -> dict[str, str]:
"""Recursively walk the docs directory and extract fenced code blocks.

Args:
languages: Fence language tags to match, e.g. ``["csharp"]`` or
``["typescript", "ts", "javascript", "js"]``.
skip_patterns: Regexes matched against the path of each MDX file
(relative to the docs directory). Any file matching one of
these patterns is skipped entirely. For example, Node skips
migration guides (``r"^migration"``) and IAM guides
(``r"iam-"``) because they reference APIs outside GLIDE.

Returns:
A dict mapping ``"<repo_relative_path>:<line_number>"`` to the
extracted code string.
"""
fence_re = re.compile(
r"^\s*```(?:" + "|".join(languages) + r")\s*\n(.*?)^\s*```\s*$",
re.MULTILINE | re.DOTALL,
)
compiled_skips = [re.compile(p) for p in (skip_patterns or [])]

examples: dict[str, str] = {}
for root, _dirs, files in os.walk(DOCS_DIR):
for fname in sorted(files):
if not fname.endswith(".mdx"):
continue

filepath = os.path.join(root, fname)
rel_path = os.path.relpath(filepath, DOCS_DIR)
if any(skip.search(rel_path) for skip in compiled_skips):
continue

with open(filepath, encoding="utf-8") as fh:
content = fh.read()

for match in fence_re.finditer(content):
key_path = os.path.relpath(filepath, REPO_ROOT)
line_number = content[: match.start()].count("\n") + 1
examples[f"{key_path}:{line_number}"] = match.group(1)

return examples
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
4. Cleans up and propagates the exit code.

Usage:
python scripts/check_csharp_examples.py
python scripts/validators/check-csharp-examples.py
--validator <path_to_validate_examples.py>
--glide-dll <path_to_Valkey.Glide.dll>

Expand All @@ -19,46 +19,12 @@
import argparse
import json
import os
import re
import subprocess
import sys
import tempfile

# Repository root is one level up from this script's directory (scripts/).
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_DOCS_DIR = os.path.join(_REPO_ROOT, "src", "content", "docs")

# Matches a ```csharp ... ``` fenced code block, capturing the content.
# Allows leading whitespace before fences (common in MDX tab components).
_CSHARP_BLOCK_RE = re.compile(
r"^\s*```csharp\s*\n(.*?)^\s*```\s*$",
re.MULTILINE | re.DOTALL,
)


def extract_all(docs_dir: str) -> dict[str, str]:
"""Recursively walk docs_dir for .mdx files and extract C# blocks.

Returns a dict mapping "<repo_relative_path>:<line_number>" to the
code string.
"""
examples: dict[str, str] = {}

for root, _dirs, files in os.walk(docs_dir):
for fname in sorted(files):
if not fname.endswith(".mdx"):
continue

filepath = os.path.join(root, fname)
with open(filepath, encoding="utf-8") as fh:
content = fh.read()

for match in _CSHARP_BLOCK_RE.finditer(content):
key_path = os.path.relpath(filepath, _REPO_ROOT)
line_number = content[: match.start()].count("\n") + 1
examples[f"{key_path}:{line_number}"] = match.group(1)

return examples
from _common import DOCS_DIR as _DOCS_DIR
from _common import extract_all as _extract_all


def main() -> None:
Expand Down Expand Up @@ -103,7 +69,7 @@ def main() -> None:
sys.exit(1)

# Step 1: Extract examples from MDX files
examples = extract_all(_DOCS_DIR)
examples = _extract_all(["csharp"])
print(f"Extracted {len(examples)} C# code example(s).")

if not examples:
Expand Down
Loading
Loading