Skip to content
Merged
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
108 changes: 108 additions & 0 deletions .ci/scripts/check-render-artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
#
# check-render-artifacts.sh
#
# Scan built Hugo HTML for forbidden render artifacts — patterns that
# should be impossible on a correctly built page. Their presence signals
# a rendering bug (whitespace leak in a render hook, broken shortcode
# template, etc.) that would otherwise ship to production as visibly
# broken content.
#
# The canonical example is influxdata/docs-v2#7079, where a whitespace
# leak in the `placeholders`/`callout` branch of render-codeblock.html
# caused Goldmark to HTML-escape highlighted code blocks into literal
# `<pre><code>&lt;div class=&quot;highlight&quot;…` fragments on every
# page that used either attribute. Every pattern in this script is a
# fingerprint of that class of bug.
#
# Usage:
# .ci/scripts/check-render-artifacts.sh [target]
#
# Arguments:
# target Directory or file to scan. Defaults to `public`.
#
# Exit codes:
# 0 No forbidden patterns found.
# 1 At least one forbidden pattern found. The script prints every
# offending file and the pattern that matched.
# 2 Target directory/file does not exist.
#
# Run this immediately after `npx hugo --quiet` in CI so that any
# regression fails the build before downstream jobs (Cypress, Vale,
# link-check) waste time.
#
# Known gaps / possible future additions:
#
# - `{{<` / `{{%` : Indicates an unrendered Hugo shortcode. Currently
# excluded because legitimate docs contain Helm and
# Go template examples that use `{{` syntax inside
# code fences, producing unavoidable false positives.
# Could be re-added as a scoped regex that requires
# the delimiters to appear outside `<code>` / `<pre>`.
#
# - `ZgotmplZ` : Hugo's context-escape sentinel. Currently excluded
# because a pre-existing bug emits it into
# `data-influxdb-urls="#ZgotmplZ"` on ~4600 pages.
# Re-add after that bug is fixed.

set -euo pipefail

TARGET="${1:-public}"

if [[ ! -e "$TARGET" ]]; then
echo "::error::check-render-artifacts: target '$TARGET' does not exist. Did Hugo build succeed?"
exit 2
fi

# Forbidden patterns, format: "PATTERN|DESCRIPTION"
#
# Every pattern below is HTML-escaped chroma output (`<div class="highlight">`,
# `<pre tabindex="0">`, `<span class="line">`). Goldmark only produces those
# escaped forms when it has interpreted legitimate chroma HTML as plain-text
# content — i.e. when a render hook or wrapper shortcode leaked whitespace and
# Goldmark re-wrapped the highlighted output as an indented code block.
PATTERNS=(
"&lt;div class=&quot;highlight&quot;|Goldmark re-wrapped highlighted code as an indented code block (see #7079). Likely cause: whitespace leak in a render hook (layouts/_default/_markup/render-*.html) or a wrapper shortcode template."
"&lt;pre tabindex=&quot;0&quot;|Escaped chroma <pre> wrapper — same class of bug as #7079."
"&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;cl&quot;|Escaped chroma line span — same class of bug as #7079."
)

failed=0
total_hits=0

for entry in "${PATTERNS[@]}"; do
pattern="${entry%%|*}"
description="${entry#*|}"

if [[ -d "$TARGET" ]]; then
hits=$(grep -rlF --include='*.html' "$pattern" "$TARGET" 2>/dev/null || true)
else
hits=$(grep -lF "$pattern" "$TARGET" 2>/dev/null || true)
fi

if [[ -n "$hits" ]]; then
count=$(printf '%s\n' "$hits" | wc -l | tr -d ' ')
total_hits=$((total_hits + count))
failed=1

echo "::error::Found forbidden render artifact in $count file(s): '$pattern'"
echo " Cause: $description"
echo " First 10 affected files:"
printf '%s\n' "$hits" | head -10 | sed 's/^/ /'
echo ""
fi
done

if [[ $failed -eq 0 ]]; then
echo "✅ check-render-artifacts: no forbidden patterns found in '$TARGET'."
exit 0
fi

echo "❌ check-render-artifacts: found $total_hits rendering artifact(s) across the built site."
echo ""
echo "These signatures appear only when a render hook or wrapper shortcode leaks"
echo "whitespace into its output and Goldmark re-wraps highlighted code as an"
echo "indented code block. The fix is almost always to add {{- ... -}} whitespace"
echo "trimming to every action tag in the offending template. See"
echo "influxdata/docs-v2#7079 for the canonical case and fix pattern."
exit 1
77 changes: 77 additions & 0 deletions .ci/scripts/check-render-hook-whitespace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
#
# check-render-hook-whitespace.sh
#
# Pre-commit lint for Hugo render hooks (layouts/_default/_markup/render-*.html).
#
# Enforces the invariant that every action tag inside a render hook must
# use `{{- ... -}}` whitespace trimming. Bare `{{ ... }}` actions leak
# their surrounding indent and trailing newline into the rendered output,
# which causes Goldmark to interpret the result as an indented code block
# and HTML-escape any leading HTML (see influxdata/docs-v2#7079 for the
# canonical failure mode).
#
# The only exception is template comments (`{{/* ... */}}`), which
# produce no output regardless of trimming.
#
# Usage:
# .ci/scripts/check-render-hook-whitespace.sh [file...]
#
# Typical invocation from lefthook pre-commit:
# glob: "layouts/_default/_markup/render-*.html"
# run: .ci/scripts/check-render-hook-whitespace.sh {staged_files}
#
# Exit codes:
# 0 All action tags in the provided files are whitespace-trimmed.
# 1 At least one bare `{{ ... }}` action was found.

set -euo pipefail

if [[ $# -eq 0 ]]; then
exit 0
fi

failed=0

for file in "$@"; do
[[ -f "$file" ]] || continue

# Match any line whose first non-whitespace `{{` is not followed by `-`
# (i.e. a bare opening action), AND whose matching `}}` is not preceded
# by `-` (bare closing action).
#
# Exclude template comments `{{/* ... */}}` since they produce no
# output and are unaffected by whitespace.
#
# Exclude lines where `{{` appears only inside a string literal (e.g.
# `print "...{{..."`), detected by requiring the action to start the
# line (after optional whitespace).
offenders=$(grep -nE '^\s*\{\{[^-/]' "$file" | grep -vE '\{\{/\*' || true)

# Also catch bare closing actions: `... }}` without the leading `-}}`.
# Only warn on lines that also start with an action (avoid false
# positives from string literals spanning multiple lines).
closing_offenders=$(grep -nE '[^-]\}\}\s*$' "$file" | grep -E '^\s*\{\{' | grep -vE '\{\{-' || true)

all_offenders=$(printf '%s\n%s\n' "$offenders" "$closing_offenders" | grep -v '^$' | sort -u || true)

if [[ -n "$all_offenders" ]]; then
failed=1
echo "❌ $file: found bare {{ ... }} action tag(s) in a render hook."
echo " Every action inside layouts/_default/_markup/render-*.html must"
echo " use whitespace-trimming delimiters ({{- ... -}}) to prevent"
echo " leaked whitespace from breaking Goldmark's HTML-block detection."
echo " See influxdata/docs-v2#7079 for the canonical failure."
echo ""
printf '%s\n' "$all_offenders" | sed 's/^/ /'
echo ""
fi
done

if [[ $failed -eq 1 ]]; then
echo "Fix: rewrite each offending action with {{- ... -}} trimming."
echo "Example: '{{ \$x := foo }}' → '{{- \$x := foo -}}'"
exit 1
fi

exit 0
101 changes: 101 additions & 0 deletions .github/workflows/pr-render-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Render Regression Check

# Guards against the class of rendering bug reported in
# influxdata/docs-v2#7079: whitespace leaks in Hugo render hooks or
# wrapper shortcode templates that cause Goldmark to HTML-escape
# highlighted code blocks on every affected page.
#
# Two layers:
#
# 1. check-artifacts: Site-wide grep of built HTML. Cheap, runs on
# every PR, catches regressions anywhere in the site even on pages
# nobody is thinking about. This is the backstop.
#
# 2. cypress-render: Cypress spec against content/example.md and a
# curated set of representative product pages. Runs only when a
# PR touches `layouts/` or `assets/` (i.e. the code paths that can
# cause this class of bug). Provides DOM-level verification that
# the pages actually look right, not just that they lack a string.

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
check-artifacts:
name: Scan built HTML for forbidden render artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6

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

- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: 0
run: yarn install --frozen-lockfile

- name: Build Hugo site
run: npx hugo --quiet

- name: Scan built HTML for render artifacts
run: .ci/scripts/check-render-artifacts.sh public

cypress-render:
name: Cypress render-regression spec
runs-on: ubuntu-latest
# Only run when a PR touches code that could regress rendering.
# Content-only PRs don't need this layer — the site-wide grep above
# already catches any page-level impact they could have.
if: |
contains(github.event.pull_request.labels.*.name, 'render-regression') ||
github.event.pull_request.draft == false
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Detect layout/asset changes
id: detect
run: |
# Only run Cypress when files under layouts/, assets/, or
# the render-regression spec itself have changed. Content-only
# PRs rely on the site-wide grep job above for coverage.
CHANGED=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }}/files" \
| jq -r '.[].filename')

echo "Changed files:"
echo "$CHANGED"

if echo "$CHANGED" | grep -qE '^(layouts/|assets/|cypress/e2e/content/render-regression\.cy\.js$|cypress/support/|\.ci/scripts/check-render-artifacts\.sh$|\.github/workflows/pr-render-check\.yml$)'; then
echo "should-run=true" >> $GITHUB_OUTPUT
echo "✅ Layout, asset, or render-check file changed — running Cypress"
else
echo "should-run=false" >> $GITHUB_OUTPUT
echo "ℹ️ No layout/asset/render-check file changed — skipping Cypress"
fi

- name: Setup Node.js
if: steps.detect.outputs.should-run == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'

- name: Install dependencies
if: steps.detect.outputs.should-run == 'true'
run: yarn install --frozen-lockfile

- name: Run render-regression spec
if: steps.detect.outputs.should-run == 'true'
run: |
node cypress/support/run-e2e-specs.js \
--spec "cypress/e2e/content/render-regression.cy.js" \
--no-mapping
105 changes: 105 additions & 0 deletions content/example.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Example post
description: This is just an example post to show the format of new 2.0 posts

Check notice on line 3 in content/example.md

View workflow job for this annotation

GitHub Actions / Vale style check

write-good.E-Prime

Try to avoid using 'is'.
weight: 1
related:
- /influxdb/v2/write-data/
Expand Down Expand Up @@ -1471,3 +1471,108 @@
## Ask AI Link

Can't access your InfluxDB instance? {{< ask-ai-link link-text="Ask InfluxData AI" query="What's my InfluxDB version?" >}} for help.

## Render-regression fixtures

The sections below exercise the code-block render hook combinations that
were broken in influxdata/docs-v2#7079. They exist to give the
`cypress/e2e/content/render-regression.cy.js` spec something to assert
against and to make this page a meaningful smoke test for any PR that
touches `layouts/_default/_markup/render-codeblock.html` or any wrapper
shortcode that contains fenced code. When adding coverage for a new
render-hook attribute combination, add a matching fixture here.

### Placeholder fence attribute

```sh { placeholders="DATABASE_NAME|AUTH_TOKEN" }
curl --request POST \
http://localhost:8181/api/v3/write_lp?db=DATABASE_NAME \
--header "Authorization: Bearer AUTH_TOKEN" \
--data-raw "home,room=Kitchen temp=22.1"
```

### Placeholder fence attribute with regex group

```sh { placeholders="DATABASE_(TOKEN|NAME)" }
influxctl write --token DATABASE_TOKEN --database DATABASE_NAME
```

### Callout fence attribute

```sh { callout="--host" }
influx query --host http://localhost:8086
```

### Callout fence attribute with explicit color

```sh { callout="--host" callout-color="magenta" }
influx query --host http://localhost:8086
```

### Placeholder and callout on the same fence

```sh { placeholders="DATABASE_NAME" callout="--host" callout-color="orange" }
influx query --host http://localhost:8086 --database DATABASE_NAME
```

### Placeholder fence inside a custom-timestamps wrapper

The `influxdb/custom-timestamps` wrapper was one of the shortcodes
affected by #7079 — it wraps code blocks that include timestamps so the
UI can rewrite them. This fixture exercises a placeholder code fence
inside that wrapper.

{{% influxdb/custom-timestamps %}}

```sh { placeholders="DATABASE_NAME|AUTH_TOKEN" }
influxdb3 write --token AUTH_TOKEN --database DATABASE_NAME \
'home,room=Kitchen temp=22.1 1641024000'
```

{{% /influxdb/custom-timestamps %}}

### Placeholder fence inside code-tab-content and custom-timestamps

The worst-case path: placeholders inside a code-tab-content section
inside a code-tabs wrapper inside an expand wrapper. This is the exact
shape that sample-data pages use.

{{< expand-wrapper >}}
{{% expand "Expand to see the tabbed, placeholder-enabled code block" %}}

{{< code-tabs-wrapper >}}
{{% code-tabs %}}
[influxdb3](#)
[v2 API](#)
{{% /code-tabs %}}
{{% code-tab-content %}}

{{% influxdb/custom-timestamps %}}

```sh { placeholders="DATABASE_NAME|AUTH_TOKEN" }
influxdb3 write --token AUTH_TOKEN --database DATABASE_NAME \
'home,room=Kitchen temp=22.1 1641024000'
```

{{% /influxdb/custom-timestamps %}}

{{% /code-tab-content %}}
{{% code-tab-content %}}

{{% influxdb/custom-timestamps %}}

```sh { placeholders="DATABASE_NAME|AUTH_TOKEN" }
curl --request POST \
http://localhost:8086/api/v2/write?bucket=DATABASE_NAME \
--header "Authorization: Bearer AUTH_TOKEN" \
--data-binary "home,room=Kitchen temp=22.1 1641024000"
```

{{% /influxdb/custom-timestamps %}}

{{% /code-tab-content %}}
{{< /code-tabs-wrapper >}}

{{% /expand %}}
{{< /expand-wrapper >}}

Loading
Loading