diff --git a/.github/workflows/fern-docs.yml b/.github/workflows/fern-docs.yml index 65a3019b9c2a..49ad7ee340fb 100644 --- a/.github/workflows/fern-docs.yml +++ b/.github/workflows/fern-docs.yml @@ -284,37 +284,54 @@ jobs: - name: Propagate shared Reference nav to released versions run: | - # The Reference tab's General variant (Compatibility, Release - # Artifacts, Releases, Known Issues, Deprecations, Model EA Builds, - # Glossary) is SHARED across versions: release snapshots keep its nav - # paths on ../pages-dev/ so every version dropdown renders the + # The Reference tab's General group (Compatibility, Examples, Release + # Artifacts, Releases, Model EA Builds, Glossary, Releases + # machine-readable) is SHARED across versions: release snapshots keep + # its nav paths on ../pages-dev/ so every version dropdown renders the # always-current reference. When a page is added to the shared - # reference on main (e.g. a new release-notes page), copy the - # variant's nav block from dev.yml into each released version's yml - # so the new page appears in every dropdown, not just dev. Version - # snapshots cut before the reference rework have no such variant and - # are left untouched (the yq selection matches nothing). - yq '[.navigation[] | select(.tab == "reference") | .variants[] | select(.title == "General")][0]' \ - docs-checkout/fern/versions/dev.yml > /tmp/reference_general_variant.yml - - if [ "$(yq 'length' /tmp/reference_general_variant.yml)" = "0" ] || \ - [ "$(head -c4 /tmp/reference_general_variant.yml)" = "null" ]; then - echo "No shared Reference General variant in dev.yml; skipping propagation" - else - for vfile in docs-checkout/fern/versions/v*.yml; do - [ -e "$vfile" ] || continue - # Only rewrite files that actually carry the shared variant — - # yq -i normalizes whitespace, so touching pre-rework snapshots - # (which have no reference General variant) is pure churn. - if [ "$(yq '[.navigation[] | select(.tab == "reference") | .variants[] | select(.title == "General")] | length' "$vfile")" != "0" ]; then - yq -i '(.navigation[] | select(.tab == "reference") | .variants[] | select(.title == "General")) = load("/tmp/reference_general_variant.yml")' "$vfile" - echo "Synced shared Reference nav into $vfile" - else - echo "Skipped (no shared Reference variant): $vfile" - fi - done + # reference on main (e.g. a new release-notes page), copy those nav + # entries from dev.yml into each released version's yml so the new + # page appears in every dropdown, not just dev. + # + # The group is identified by CONTENT PATH (pages/reference/general/, + # rewritten to ../pages-dev/reference/general/ by the step above), not + # by a nav label. It used to key on `variants[] | select(.title == + # "General")`; #12410 deliberately flattened the Reference tab into + # folded sections with no variant selector, and the label-keyed + # selector then matched nothing for six days without failing anything. + # Directory layout is the durable signal — IA work renames labels. + yq '[.navigation[] | select(.tab == "reference") | .layout[] + | select([.. | select(has("path")) | .path] + | all_c(test("/reference/general/")))]' \ + docs-checkout/fern/versions/dev.yml > /tmp/reference_general_entries.yml + + if [ "$(yq 'length' /tmp/reference_general_entries.yml)" = "0" ]; then + # Hard failure, not a warning. The pre-merge composition check + # (pre-merge.yml -> simulate_docs_website.sh) asserts the same + # condition, so this only trips if something bypassed it. + echo "::error::No shared Reference pages found under reference/general/ in dev.yml. Shared Reference is disabled and every version will freeze its own reference copy. Update the path key in this workflow and in docs/fern/scripts/simulate_docs_website.sh." + exit 1 fi + for vfile in docs-checkout/fern/versions/v*.yml; do + [ -e "$vfile" ] || continue + # Only rewrite files that already carry shared entries. Versions cut + # before the reference rework have no reference tab at all + # (v1.3.0.yml is `tab: docs`), and yq -i normalizes whitespace, so + # touching them is pure churn. This stays inert until the first tag + # is cut after the tab restructure. + if [ "$(yq '[.navigation[]? | select(.tab? == "reference") | .layout[]? | select([.. | select(has("path")) | .path] | all_c(test("/pages-dev/reference/general/")))] | length' "$vfile")" != "0" ]; then + ENTRIES=/tmp/reference_general_entries.yml \ + yq -i '(.navigation[] | select(.tab == "reference") | .layout) |= + (load(strenv(ENTRIES)) + + [.[] | select([.. | select(has("path")) | .path] + | any_c(test("/pages-dev/reference/general/")) | not)])' "$vfile" + echo "Synced shared Reference nav into $vfile" + else + echo "Skipped (no shared Reference entries): $vfile" + fi + done + - name: Convert GitHub callouts to Fern format run: | echo "Converting GitHub-style callouts to Fern format in pages/..." @@ -379,11 +396,17 @@ jobs: # stylesheet in hosted builds, so previews of any fern/main.css change # render unstyled. The repo main.css already carries the NVIDIA # branding rules, so unthemed previews stay visually faithful. - if [ "${{ steps.ctx.outputs.is_main }}" = "true" ]; then - yq -i '.global-theme = "nvidia"' docs.yml - else - yq -i 'del(.global-theme)' docs.yml - fi + # Every hosted build gets the theme, previews included. The theme + # overrides the project css/js and the custom footer, so an unthemed + # preview renders under conditions that never ship -- it was faithful + # to `fern docs dev`, not to the live site, which is how unstyled Home, + # Community and blog pages reached production unnoticed. + # + # This does not change the source repo's docs.yml, which still omits + # global-theme so contributors can run `fern docs dev` without an + # nvidia-org FERN_TOKEN (#10073). The theme is injected only into the + # docs-checkout copy CI builds from. + yq -i '.global-theme = "nvidia"' docs.yml echo "Updated docs.yml:" cat docs.yml @@ -476,6 +499,21 @@ jobs: working-directory: docs-checkout/fern run: fern generate --docs + # The global theme overrides the project `css:`/`js:` and the custom + # `footer:`, so any component relying on main.css publishes unstyled. + # Previews are themed too now, so this should be caught before merge -- + # this is the backstop that proves it on the site people actually read. + # Retries cover CDN propagation. + # + # Runs on every main invocation, not only when has_changes: on a rerun + # the docs-website branch is already ahead of source-checkout, so + # has_changes flips false. The live pages still need re-verifying (a + # rerun is what a failed run turns into once triaged), so the check + # must not be gated on repository changes. + - name: Verify published pages carry their component CSS + if: steps.ctx.outputs.is_main == 'true' + run: python3 source-checkout/docs/fern/scripts/check_published_styles.py + ############################################################################# # VERSION RELEASE - Run on new version tags (vX.Y.Z) ############################################################################# @@ -601,22 +639,25 @@ jobs: run: | TAG="${{ steps.version.outputs.tag }}" - # The Reference tab's General variant is shared (always-current) - # across versions: its nav paths stay on ../pages-dev/ (see the - # version-config step), so drop exactly the files that variant - # references from the snapshot rather than freezing stale copies. - # Everything else under reference/ (observability pages, config - # references) belongs to versioned tabs and stays in the snapshot. - # If the selectors stop matching (tab or variant renamed), this and - # the version-config revert silently no-op and the reference quietly - # freezes per version again — warn loudly so the rename gets fixed. - if [ "$(yq '[.navigation[] | select(.tab == "reference") | .variants[] | select(.title == "General")] | length' docs-checkout/fern/versions/dev.yml)" = "0" ]; then - echo "::warning::No Reference General variant found in dev.yml — the shared-reference exclusion is a no-op and $TAG will freeze its own reference copy. If the tab or variant was renamed, update the yq selectors in this workflow." + # The Reference tab's General group is shared (always-current) across + # versions: its nav paths stay on ../pages-dev/ (see the + # version-config step), so drop those files from the snapshot rather + # than freezing stale copies. Everything else under reference/ + # (Kubernetes API, Components, Backends, Observability, NIXL Connect) + # is genuinely per-version and stays in the snapshot. + # + # The group is a directory, so this is a plain path operation — no nav + # traversal, which also means no yq assignment that could auto-create + # keys in a file it is only meant to read. + SHARED="docs-checkout/fern/pages-$TAG/reference/general" + if [ ! -d "$SHARED" ]; then + # Hard failure: silently skipping is what let shared Reference rot + # unnoticed for six days. The pre-merge composition check asserts + # the same invariant, so reaching here means something bypassed it. + echo "::error::$SHARED not found — the shared-reference exclusion would be a no-op and $TAG would freeze its own reference copy. If reference/general/ was renamed, update the path key in this workflow and in docs/fern/scripts/simulate_docs_website.sh." + exit 1 fi - yq '.navigation[] | select(.tab == "reference") | .variants[] | select(.title == "General") | .. | select(has("path")) | .path' \ - docs-checkout/fern/versions/dev.yml | sed 's|^\.\./pages-dev/||' | while read -r relpath; do - [ -n "$relpath" ] && rm -f "docs-checkout/fern/pages-$TAG/$relpath" - done + rm -rf "$SHARED" find "docs-checkout/fern/pages-$TAG/reference" -type d -empty -delete 2>/dev/null || true - name: Snapshot translation mirrors @@ -732,13 +773,25 @@ jobs: yq -i '(.. | select(has("path")).path) |= sub("^pages/home/index\.mdx$", "../index.mdx")' "$VERSION_FILE" yq -i '(.. | select(has("path")).path) |= sub("^pages/", "../pages-'"$TAG"'/")' "$VERSION_FILE" - # Revert the Reference tab's General variant to the shared source: - # it is always-current across versions (release metadata is - # cumulative — each page carries per-release sections), so its pages - # render from pages-dev in every version (paired with the snapshot - # drop above). The Kubernetes API and Components variants stay on the - # frozen snapshot (CRD fields and config flags are genuinely per-version). - yq -i "(.navigation[] | select(.tab == \"reference\") | .variants[] | select(.title == \"General\") | .. | select(has(\"path\")).path) |= sub(\"\.\./pages-$TAG/\", \"../pages-dev/\")" "$VERSION_FILE" + # Revert the Reference tab's General group to the shared source: it is + # always-current across versions (release metadata is cumulative — + # each page carries per-release sections), so its pages render from + # pages-dev in every version (paired with the snapshot drop above). + # Kubernetes API, Components, Backends, Observability and NIXL Connect + # stay on the frozen snapshot (CRD fields and config flags are + # genuinely per-version). + # + # Keyed on the content path, and applied as a text substitution rather + # than a yq assignment. A yq assignment whose left-hand side traverses + # a missing key auto-creates it: the previous `.variants[]` form + # injected an empty `variants: []` once #12410 removed the variants, + # which invalidates the whole navigation against Fern's schema. + perl -pi -e "s|path: \.\./pages-$TAG/reference/general/|path: ../pages-dev/reference/general/|g" "$VERSION_FILE" + + if ! grep -q "path: \.\./pages-dev/reference/general/" "$VERSION_FILE"; then + echo "::error::$VERSION_FILE has no shared reference/general/ paths after the revert; $TAG would freeze its own reference copy." + exit 1 + fi # Keep cross-navigation links within the selected documentation version. sed -i "s|href: /dynamo/dev/|href: /dynamo/$TAG/|g" "$VERSION_FILE" diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml index a3367403def2..3905d08852a8 100644 --- a/.github/workflows/pre-merge.yml +++ b/.github/workflows/pre-merge.yml @@ -47,7 +47,7 @@ jobs: pre-merge-status-check: runs-on: ubuntu-latest - needs: [changed-files, pre-commit, fern-check, fern-broken-links, operator, snapshot, rust-tests, rust-clippy] + needs: [changed-files, pre-commit, fern-check, docs-website-composition, fern-broken-links, operator, snapshot, rust-tests, rust-clippy] if: always() steps: - name: "Check all dependent jobs" @@ -83,6 +83,42 @@ jobs: working-directory: docs/fern run: fern check + docs-website-composition: + name: Docs Website Composition Check + needs: changed-files + if: needs.changed-files.outputs.docs == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + # The sync and release-version composition (rsync scopes, nav path + # transforms, the shared-Reference machinery) only executes on main pushes + # and tag cuts, so fern check pre-merge validates the source tree rather + # than the tree that actually publishes. This replays both jobs against + # docs-website and runs fern check on the composed result. + - name: Fetch docs-website + run: git fetch --no-tags --depth=1 origin docs-website:refs/remotes/origin/docs-website + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install Fern CLI + run: npm install -g fern-api + - name: Install yq + run: | + # Runner-owned bin dir: no sudo, and $GITHUB_PATH puts it ahead of + # any preinstalled yq for the steps that follow. + mkdir -p "$RUNNER_TEMP/bin" + wget -qO "$RUNNER_TEMP/bin/yq" https://github.com/mikefarah/yq/releases/download/v4.52.5/yq_linux_amd64 + chmod +x "$RUNNER_TEMP/bin/yq" + echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" + - name: Replay the docs-website composition + env: + DOCS_WEBSITE_REF: origin/docs-website + run: bash docs/fern/scripts/simulate_docs_website.sh + fern-broken-links: name: Fern Broken Links Check needs: changed-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 779645ba4835..0e91f761e025 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -100,6 +100,16 @@ repos: entry: python3 docs/fern/scripts/check_asset_paths.py --test language: system files: ^docs/fern/scripts/check_asset_paths\.py$ + - id: check-style-components + name: Check *Styles.tsx CSS template literals + entry: python3 docs/fern/scripts/check_style_components.py + language: system + files: ^docs/fern/(components/.*Styles\.tsx|scripts/check_style_components\.py)$ + - id: check-published-styles-selftest + name: Self-test the published-styles regex helpers + entry: python3 docs/fern/scripts/check_published_styles.py --test + language: system + files: ^docs/fern/scripts/check_published_styles\.py$ pass_filenames: false - id: pytest-marker-report name: Report pytest markers (static + inherited) diff --git a/docs/fern/scripts/check_published_styles.py b/docs/fern/scripts/check_published_styles.py new file mode 100755 index 000000000000..f917354b1f41 --- /dev/null +++ b/docs/fern/scripts/check_published_styles.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Assert the published site actually carries its page-level component CSS. + +Production sets `global-theme: nvidia`, which overrides the project `css:` and +`js:` entries and the custom `footer:` (Fern's docs.yml schema documents the +override). Components that deliver their CSS through a page-level