diff --git a/.github/workflows/vale-check.yml b/.github/workflows/vale-check.yml
index b15dc41d..09f62951 100644
--- a/.github/workflows/vale-check.yml
+++ b/.github/workflows/vale-check.yml
@@ -1,6 +1,9 @@
name: Lint Docs
-on: [push, pull_request]
+on:
+ pull_request:
+ push:
+ branches: [main]
jobs:
lint:
@@ -8,13 +11,22 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install Tools
+ env:
+ # Keep in step with the version developers run locally -- older Vale
+ # reports Microsoft.Headings false positives this repo can't reproduce.
+ VALE_VERSION: 3.15.2
run: |
- # Install Vale
- wget https://github.com/errata-ai/vale/releases/download/v3.9.0/vale_3.9.0_Linux_64-bit.tar.gz
- tar -xvzf vale_3.9.0_Linux_64-bit.tar.gz
- sudo mv vale /usr/local/bin/
+ # Install Vale. Extract outside the repo: the tarball ships its own
+ # README.md/LICENSE, which otherwise clobber ours and get linted.
+ tmp="$(mktemp -d)"
+ wget -qO "$tmp/vale.tar.gz" \
+ "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/vale_${VALE_VERSION}_Linux_64-bit.tar.gz"
+ tar -xzf "$tmp/vale.tar.gz" -C "$tmp" vale
+ sudo mv "$tmp/vale" /usr/local/bin/
+ vale -v
# Install Markdownlint
npm install -g markdownlint-cli
+ markdownlint --version
- name: Run Quality Check
run: |
chmod +x docs-linter/lint.sh
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 01b638cc..375a7c9b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,9 +2,9 @@
This guide covers the development workflow for making changes to the NetFoundry Docusaurus theme and shared components.
-## Repository Structure
+## Repository structure
-```
+```text
docusaurus-shared/
├── packages/
│ └── docusaurus-theme/ # @netfoundry/docusaurus-theme
@@ -36,7 +36,7 @@ docusaurus-shared/
└── CONTRIBUTING.md # You are here
```
-## The Theme Package
+## The theme package
| Package | npm | Purpose |
|---------|-----|---------|
@@ -44,14 +44,14 @@ docusaurus-shared/
---
-## Development Workflow
+## Development workflow
### Prerequisites
- Node.js 18+
- Yarn package manager
-### Initial Setup
+### Initial setup
```bash
git clone https://github.com/netfoundry/docusaurus-shared.git
@@ -59,7 +59,7 @@ cd docusaurus-shared
yarn install
```
-### Quick Commands (from repo root)
+### Quick commands (from repo root)
```bash
yarn dev # Start test-site dev server
@@ -69,9 +69,9 @@ yarn test # Run theme tests
---
-## Making Changes to the Theme
+## Making changes to the theme
-### 1. Configure Local Development
+### 1. Configure local development
The test-site is pre-configured to use the local theme. Check `test-site/docusaurus.config.ts`:
@@ -89,7 +89,7 @@ export default {
};
```
-### 2. Start Development Server
+### 2. Start development server
```bash
yarn dev
@@ -99,7 +99,7 @@ cd test-site && yarn start
Open http://localhost:3000. Changes to theme files hot-reload automatically.
-### 3. Make Your Changes
+### 3. Make your changes
Common file locations:
@@ -115,7 +115,7 @@ Common file locations:
| Remark plugins | `packages/docusaurus-theme/src/docusaurus-plugins/` |
| Theme config types | `packages/docusaurus-theme/src/options.ts` |
-### 4. Test Your Changes
+### 4. Test your changes
```bash
# Development server (hot reload)
@@ -126,7 +126,7 @@ yarn build
cd test-site && yarn serve
```
-### 5. Run Tests
+### 5. Run tests
```bash
yarn test
@@ -146,14 +146,16 @@ npm version patch # 0.1.2 → 0.1.3
npm publish
```
-### 7. Verify Published Package
+### 7. Verify published package
Update `test-site/package.json`:
+
```json
"@netfoundry/docusaurus-theme": "^0.1.3"
```
Switch `test-site/docusaurus.config.ts` to use package name:
+
```typescript
themes: [
'@netfoundry/docusaurus-theme',
@@ -161,6 +163,7 @@ themes: [
```
Then:
+
```bash
cd test-site
yarn install
@@ -169,22 +172,23 @@ yarn build
---
-## Example: Changing a CSS Variable
+## Example: Changing a CSS variable
Here's a complete walkthrough of changing the light-mode tab color.
-### The Problem
+### The problem
The tab highlight color (`--nf-tab-color`) is pink in light mode. We want grey instead.
-### Step 1: Find the Variable
+### Step 1: Find the variable
```bash
grep -n "nf-tab-color" packages/docusaurus-theme/css/legacy.css
```
Output:
-```
+
+```text
89: --nf-tab-color: 255, 182, 193;
90: --nf-tab-color-render: rgb(255, 182, 193);
143: --nf-tab-color: 67, 72, 98;
@@ -193,13 +197,13 @@ Output:
- Line 89-90: Light mode (`:root`)
- Line 143: Dark mode (`html[data-theme="dark"]`)
-### Step 2: Start Dev Server
+### Step 2: Start dev server
```bash
yarn dev
```
-### Step 3: Make the Change
+### Step 3: Make the change
Edit `packages/docusaurus-theme/css/legacy.css` around line 89:
@@ -219,11 +223,11 @@ Edit `packages/docusaurus-theme/css/legacy.css` around line 89:
Save. Browser hot-reloads with new color.
-### Step 4: Test Dark Mode
+### Step 4: Test dark mode
Toggle dark mode in browser. Dark mode uses a different value (line 143) - verify it still looks correct.
-### Step 5: Run Tests & Build
+### Step 5: Run tests & build
```bash
yarn test
@@ -238,13 +242,13 @@ npm version patch
npm publish
```
-### Step 7: Final Verification
+### Step 7: Final verification
Update `test-site/package.json` to new version, switch config to package name, reinstall, rebuild.
---
-## CSS Variable Reference
+## CSS variable reference
Key variables in `packages/docusaurus-theme/css/legacy.css`:
@@ -259,7 +263,7 @@ Key variables in `packages/docusaurus-theme/css/legacy.css`:
---
-## Testing Checklist
+## Testing checklist
Before publishing:
@@ -288,15 +292,15 @@ Also verify `docusaurus.config.ts` uses local path, not package name.
### Module not found?
-1. Run `yarn install` from repo root
-2. Check the file exists at the path specified in `package.json` exports
-3. Restart dev server
+1. Run `yarn install` from repo root
+2. Check the file exists at the path specified in `package.json` exports
+3. Restart dev server
### CSS not loading?
-1. Check `css/theme.css` imports the file you changed
-2. Verify `src/index.ts` `getClientModules()` includes `theme.css`
-3. Hard refresh browser (Ctrl+Shift+R)
+1. Check `css/theme.css` imports the file you changed
+2. Verify `src/index.ts` `getClientModules()` includes `theme.css`
+3. Hard refresh browser (Ctrl+Shift+R)
### TypeScript errors in consuming project?
@@ -304,7 +308,7 @@ The theme ships source files. Fix errors in theme source, not consumers.
---
-## Package Entry Points
+## Package entry points
The theme exposes multiple entry points:
diff --git a/README.md b/README.md
index 80fc4e3f..86cc5332 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
Shared documentation theme, components, and tooling for NetFoundry's Docusaurus-based documentation sites.
-## Repository Structure
+## Repository structure
-```
+```text
docusaurus-shared/
├── packages/
│ ├── docusaurus-theme/ # @netfoundry/docusaurus-theme npm package
@@ -17,9 +17,9 @@ docusaurus-shared/
See [`packages/test-site/README.md`](./packages/test-site/README.md) for the
test-site dev loop, docs organization, and debug recipes.
-## Quick Start
+## Quick start
-### Using the Theme
+### Using the theme
Install the theme in your Docusaurus project:
@@ -52,7 +52,7 @@ export default {
See the [theme README](./packages/docusaurus-theme/README.md) for full documentation.
-### Creating a New Doc Site
+### Creating a new doc site
```bash
./bootstrap.sh /path/to/new-site [starLabel] [starRepoUrl]
@@ -88,7 +88,7 @@ See [packages/test-site/README.md](./packages/test-site/README.md) for the full
dev-loop walkthrough, debug recipes, and what hot-reloads vs. what needs a
rebuild.
-## Local Development
+## Local development
See [packages/LOCAL-DEV.md](./packages/LOCAL-DEV.md) for the full local development guide, including how to use the test-site, test with remote sites via `file:` protocol, and publish.
@@ -96,9 +96,9 @@ See [packages/LOCAL-DEV.md](./packages/LOCAL-DEV.md) for the full local developm
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, workflow, and a walkthrough example of making theme changes.
-## Best Practices
+## Best practices
-### Relative Linking
+### Relative linking
Use relative paths for internal links:
@@ -115,7 +115,7 @@ Here [is a link](/docs/to/some/path.md)
Add images to the static folder scoped to your site:
-```
+```text
/your-doc-site/static/img/your-doc-site/
```
@@ -131,11 +131,11 @@ import SharedContent from '../_shared.content.md'
```
-## Kinsta Hosting
+## Kinsta hosting
The docusaurus site is hosted on Kinsta. Required nginx rule:
-```
+```nginx
location /docs/ {
try_files $uri $uri/ $uri/index.html /docs/index.html;
}
diff --git a/docs-linter/.markdownlint.json b/docs-linter/.markdownlint.json
index 6c324bed..778eaf25 100644
--- a/docs-linter/.markdownlint.json
+++ b/docs-linter/.markdownlint.json
@@ -5,6 +5,10 @@
"MD033": false, // Inline HTML. Essential for Docusaurus features (tabs, admonitions, specialized formatting).
"MD034": false, // Bare URLs (e.g. https://example.com) without . Common in quickstart guides.
"MD009": false, // Trailing spaces. Flags every invisible space at the end of a line. Huge noise source.
+ "MD010": { "code_blocks": false }, // Hard tabs. Allowed inside code blocks -- Go, Makefiles use real tabs.
+ "MD025": { "front_matter_title": "" }, // Don't count frontmatter title as an H1; Docusaurus docs use frontmatter title + one body H1.
+ "MD028": false, // Blank line between consecutive blockquotes -- legit for stacked GitHub-style admonitions.
+ "MD030": { "ol_single": 2, "ol_multi": 2 }, // Ordered lists use 2 spaces after the marker (4-space content indent, per nf-style-guide).
"MD060": false, // Table column style. Flags tables that aren't perfectly ASCII-aligned in the raw text file. Purely visual.
"MD041": false // First line heading. Fires on every Docusaurus category index page (MDX imports before any heading).
}
\ No newline at end of file
diff --git a/docs-linter/.vale.ini b/docs-linter/.vale.ini
index 4aa2b3c1..fc44104d 100644
--- a/docs-linter/.vale.ini
+++ b/docs-linter/.vale.ini
@@ -17,3 +17,6 @@ Microsoft.Contractions = NO
Microsoft.FirstPerson = NO
Microsoft.HeadingPunctuation = NO
Microsoft.Dashes = NO
+Microsoft.Auto = NO
+Microsoft.Spacing = NO
+Microsoft.Ellipses = NO
diff --git a/docs-linter/styles/config/vocabularies/terms/accept.txt b/docs-linter/styles/config/vocabularies/terms/accept.txt
index 18ce1dff..c6421606 100644
--- a/docs-linter/styles/config/vocabularies/terms/accept.txt
+++ b/docs-linter/styles/config/vocabularies/terms/accept.txt
@@ -2,6 +2,19 @@
(?i)config
(?i)ip
(?i)hostname
+(?i)docusaurus
+(?i)failover
+(?i)failovers
+(?i)markdown
+(?i)h1
+(?i)h2
+(?i)h3
+(?i)h4
+(?i)h5
+(?i)h6
+(?i)test-site
+(?i)code
+(?i)vs
# URLs and domains
openziti.io
@@ -16,8 +29,6 @@ datacenter
datacenters
egress
egressed
-failover
-failovers
ingress
ingressed
microsegmentation
@@ -29,42 +40,93 @@ untrusted
# Documentation-specific terms
sidebar_position
sidebar_label
+showLineNumbers
+STATUS.md
+
+# Theme components and remark plugins (rendered verbatim in headings)
+MarkdownWithoutH1
+OsTabs
+ProductSearch
+StarUs
+remarkReplaceMetaUrl
+remarkScopedPath
+remarkYamlTable
+remarkYouTube
# Product/Brand names
-Docusaurus
Frontdoor
+LLM Gateway
+MCP Gateway
NetFoundry
+NetFoundry Docusaurus Shared
+On-Prem
OpenZiti
Ziti
zLAN
zrok
+# Binaries and CLI tool names (lowercase by design)
+curl
+llm-gateway
+mcp-bridge
+mcp-gateway
+mcp-tools
+
# Third-party products/services
+Beta
Bitbucket
+Claude
+Claude Desktop
+ECharts
GitHub
JavaScript
+KaTeX
Keycloak
+Kinsta
Kubernetes
+Mermaid
Okta
+Ollama
+Scalar
+Stable
TypeScript
# Acronyms and initialisms
+Active LTS
AMI
AMIs
API
CA
CAs
CLI
+CSS
+EOL
FQDN
FQDNs
+Go
+HTML
+HTTP
IdP
IdPs
+JSON
+JSX
JWT
JWTs
+LLM
+LTS
+MCP
+MDX
MFA
+OpenAPI
+PR
+PRs
+TL;DR
TLDs
+URL
+URLs
VM
VMs
+YAML
# Other terms
namespace
diff --git a/nf-style-guide.md b/nf-style-guide.md
index aef5cf73..1703aab0 100644
--- a/nf-style-guide.md
+++ b/nf-style-guide.md
@@ -73,7 +73,7 @@ We use a Diátaxis framework. When creating content, think in terms of these fou
- **Tutorials** (Learning-oriented)
- **Goal:** To help the user *learn* by doing.
- - **Description:** Step-by-step lessons that allow the user to successfully complete a practical, simple project. They
+ - **Description:** Step-by-step lessons that allow the user to complete a practical, simple project. They
are focused on **teaching**, not explaining, and prioritize the shortest path to success.
- **Question answered:** *How do I get started?*
@@ -123,7 +123,7 @@ The exact top-level buckets we use for the ToC can vary depending on the product
- Don't use "the button". Say "Click **Next**."
- Tell the user to *click* UI items. If it's a drop-down, use *select*.
-## Images and File Names
+## Images and file names
- A significant amount of readers are on mobile. Don't use images wider than 600-700 px. Shrink browsers for
screenshots.
diff --git a/packages/LOCAL-DEV.md b/packages/LOCAL-DEV.md
index 04ad4421..b8689e8a 100644
--- a/packages/LOCAL-DEV.md
+++ b/packages/LOCAL-DEV.md
@@ -1,10 +1,10 @@
-# Local Development Guide
+# Local development guide
How to develop and test the theme locally before publishing to npm.
## Overview
-```
+```text
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ test-site/ │ │ file: protocol │ │ npm install │
│ (yarn link) │ --> │ (simulates npm) │ --> │ (production) │
@@ -14,7 +14,7 @@ How to develop and test the theme locally before publishing to npm.
## Project layout
-```
+```text
packages/
├── docusaurus-theme/ # @netfoundry/docusaurus-theme npm package
│ ├── src/ # TypeScript source → compiled to dist/
@@ -115,7 +115,7 @@ yarn build
This compiles TypeScript to CommonJS in `dist/`.
-## Step 4: Test with remote site (file: protocol)
+## Step 4: Test with remote site (`file:` protocol)
This simulates exactly what npm publish will deliver.
@@ -208,7 +208,7 @@ yarn build
Each sub-project in `remotes/` follows the same pattern as the real repos:
-```
+```text
remotes/zrok/website/
├── docusaurus.config.ts # Standalone config (references local theme)
├── docusaurus-plugin-zrok-docs.ts # Plugin config for aggregation
diff --git a/packages/docusaurus-theme/README.md b/packages/docusaurus-theme/README.md
index 78b9c70a..7ee61612 100644
--- a/packages/docusaurus-theme/README.md
+++ b/packages/docusaurus-theme/README.md
@@ -57,16 +57,20 @@ export default {
## What's Included
-### Automatic Layout
+### Automatic layout
+
The theme automatically provides:
+
- **NetFoundry Layout** - Wraps all pages with consistent structure
- **Footer** - Configurable footer with social links and site sections
- **Star Banner** - Optional GitHub star call-to-action banner
-### CSS Variables & Styling
+### CSS variables & styling
+
All CSS is automatically loaded. No need to add `@import` statements to your `custom.css`.
Includes:
+
- NetFoundry brand colors and typography
- Light/dark mode support
- Responsive design tokens
@@ -80,7 +84,7 @@ Import UI components directly:
import { Alert, CodeBlock, OsTabs } from '@netfoundry/docusaurus-theme/ui';
```
-### Remark Plugins
+### Remark plugins
Import remark plugins for your docs config:
@@ -98,7 +102,7 @@ export default {
};
```
-### Node Utilities
+### Node utilities
Import Node.js utilities:
@@ -106,7 +110,7 @@ Import Node.js utilities:
import { pluginHotjar, cleanUrl, docUrl } from '@netfoundry/docusaurus-theme/node';
```
-## Package Exports
+## Package exports
| Export | Description |
|--------|-------------|
@@ -118,7 +122,7 @@ import { pluginHotjar, cleanUrl, docUrl } from '@netfoundry/docusaurus-theme/nod
## Customization
-### Swizzling Components
+### Swizzling components
To customize the layout beyond configuration:
diff --git a/packages/test-site/README.md b/packages/test-site/README.md
index 40f09a4a..4acdc70b 100644
--- a/packages/test-site/README.md
+++ b/packages/test-site/README.md
@@ -93,7 +93,7 @@ resolves correctly.
## Docs organization
-```
+```text
docs/
├── index.mdx landing page with card grid (slug: /)
├── _partials/ underscore-prefixed; ignored by sidebar
@@ -111,10 +111,10 @@ category landing page **and** in the sidebar. No `sidebars.ts` edit needed.
## Adding a new sample page
-1. Pick the right folder (or add a new one with a fresh `_category_.json`).
-2. Drop in a `.mdx` file. Frontmatter is optional; use `sidebar_position` if
- you want to influence ordering.
-3. Save. The dev server picks it up.
+1. Pick the right folder (or add a new one with a fresh `_category_.json`).
+2. Drop in a `.mdx` file. Frontmatter is optional; use `sidebar_position` if
+ you want to influence ordering.
+3. Save. The dev server picks it up.
For something that exists in the theme package but doesn't have a demo here,
the test-site is the right place for it -- not in any downstream site.
@@ -135,14 +135,14 @@ inline backticks.
Two things to check:
-1. You edited the right file. Shared rules belong in
- `packages/docusaurus-theme/css/`, **not** in `packages/test-site/src/custom/custom.css`.
- See [CSS conventions](../../CLAUDE.md#css-conventions) (or whichever file
- you have in front of you).
-2. The dev server is actually watching it. The `getClientModules()` path
- trick (`'../../css/theme.css'`) means edits to `css/*.css` propagate
- without a theme rebuild. If they don't, restart the test-site server -- a
- stale Webpack cache is the usual culprit.
+1. You edited the right file. Shared rules belong in
+ `packages/docusaurus-theme/css/`, **not** in `packages/test-site/src/custom/custom.css`.
+ See [CSS conventions](../../CLAUDE.md#css-conventions) (or whichever file
+ you have in front of you).
+2. The dev server is actually watching it. The `getClientModules()` path
+ trick (`'../../css/theme.css'`) means edits to `css/*.css` propagate
+ without a theme rebuild. If they don't, restart the test-site server -- a
+ stale Webpack cache is the usual culprit.
### "I edited a React component and nothing changed"
@@ -205,7 +205,7 @@ yarn workspace test-site serve # serve the built output locally
The static output lands in `packages/test-site/build/`. Useful for verifying
that a change works in a real production bundle, not just dev mode.
-## When to put something HERE vs. somewhere else
+## When to put something here vs. somewhere else
| Goal | Goes in |
|-----------------------------------------------|--------------------------------------------------------|
diff --git a/packages/test-site/docs/code-theme/config.md b/packages/test-site/docs/code-theme/config.md
index 758e1947..0e73f242 100644
--- a/packages/test-site/docs/code-theme/config.md
+++ b/packages/test-site/docs/code-theme/config.md
@@ -2,7 +2,7 @@
sidebar_label: "Config (YAML / JSON / Docker)"
---
-# Theme test: config formats
+# Theme test: Config formats
## YAML
diff --git a/packages/test-site/docs/code-theme/java-csharp.md b/packages/test-site/docs/code-theme/java-csharp.md
index 1d181ca6..0bab5044 100644
--- a/packages/test-site/docs/code-theme/java-csharp.md
+++ b/packages/test-site/docs/code-theme/java-csharp.md
@@ -2,7 +2,7 @@
sidebar_label: "Java & C#"
---
-# Theme test: Java and C#
+# Theme test: Java and C\#
## Java
@@ -44,7 +44,7 @@ public final class OverlayClient {
}
```
-## C#
+## C\#
```csharp
using System;
diff --git a/packages/test-site/docs/components/product-search.mdx b/packages/test-site/docs/components/product-search.mdx
index b0d16746..003ca8ba 100644
--- a/packages/test-site/docs/components/product-search.mdx
+++ b/packages/test-site/docs/components/product-search.mdx
@@ -43,11 +43,11 @@ import {useCustomFields} from '@netfoundry/docusaurus-theme/ui';
## What to verify when wiring it up
-1. Search box renders and accepts input.
-2. Hits stream in as you type (no submit needed).
-3. Product filter chips reflect the `products` prop.
-4. Pagination renders at the bottom of the results list.
-5. The "Powered by Algolia" badge appears.
-6. Clicking a hit navigates to the matching doc URL (`url_without_anchor` +
- `anchor` are honored).
-7. Snippets highlight matched terms within the result body.
+1. Search box renders and accepts input.
+2. Hits stream in as you type (no submit needed).
+3. Product filter chips reflect the `products` prop.
+4. Pagination renders at the bottom of the results list.
+5. The "Powered by Algolia" badge appears.
+6. Clicking a hit navigates to the matching doc URL (`url_without_anchor` +
+ `anchor` are honored).
+7. Snippets highlight matched terms within the result body.
diff --git a/packages/test-site/docs/components/tab-groups.mdx b/packages/test-site/docs/components/tab-groups.mdx
index 8557f93d..62319ed3 100644
--- a/packages/test-site/docs/components/tab-groups.mdx
+++ b/packages/test-site/docs/components/tab-groups.mdx
@@ -6,8 +6,6 @@ import TabItem from '@theme/TabItem';
Demonstrates the `` / `` MDX components and how the active tab
group is styled by the various `tabs-v*.css` themes in `@netfoundry/docusaurus-theme/css/`.
-
-
@
```
+
-- Once the VM is created, we can get the IP address of the VM from the Instance(s) screen.
+- Once the VM is created, we can get the IP address of the VM from the Instances screen.
Login to the VM by using user name "ubuntu":
+
```text
ssh -i ubuntu@
```
+
@@ -44,25 +46,31 @@ ssh -i ubuntu@
- Once the VM is created, get the IP address of the droplet from the Resources screen. Login to the VM by using user "root" and IP address:
+
```text
ssh root@
```
+
- Once the VM is created, we can get the IP address of the VM from the instance details screen.
- Login to the VM by using user name "ubuntu" and the IP address:
+
```text
ssh -i ubuntu@
```
+
- Once the VM is created, we can get the IP address of the VM from the Devices screen.
- Login to the VM by using user name "ubuntu" and the IP address:
+
```text
ssh -i ubuntu@
```
+
diff --git a/packages/test-site/docs/markdown/admonitions.mdx b/packages/test-site/docs/markdown/admonitions.mdx
index 358ff614..bbd9ee61 100644
--- a/packages/test-site/docs/markdown/admonitions.mdx
+++ b/packages/test-site/docs/markdown/admonitions.mdx
@@ -60,6 +60,7 @@ After running, verify `$ZITI_HOME` is set.
:::
:::tip
+
```yaml
site:
name: MySite
@@ -68,6 +69,7 @@ site:
- search
- analytics
```
+
:::
## Admonition with a table
@@ -86,6 +88,7 @@ Make sure none of these are in use before continuing.
## Admonition with a button (raw HTML)
:::danger[Danger button]
+
```sh
rm -rf /
```
@@ -137,14 +140,14 @@ you can import a README that uses `> [!NOTE]` syntax and it renders correctly.
## Admonition inside a list
-1. First step.
-2. Second step.
+1. First step.
+2. Second step.
- :::warning
- This admonition is indented as a list-item child.
- :::
+ :::warning
+ This admonition is indented as a list-item child.
+ :::
-3. Third step.
+3. Third step.
## Nested admonitions
diff --git a/packages/test-site/docs/markdown/code-blocks.mdx b/packages/test-site/docs/markdown/code-blocks.mdx
index 7a99bde5..dbb71452 100644
--- a/packages/test-site/docs/markdown/code-blocks.mdx
+++ b/packages/test-site/docs/markdown/code-blocks.mdx
@@ -41,7 +41,7 @@ function greet(name) {
}
```
-## Combined: title, line numbers, and highlighting
+## Combined: Title, line numbers, and highlighting
```ts title="src/greet.ts" showLineNumbers {3-4}
export function greet(name: string): number {
@@ -74,7 +74,7 @@ $ ziti edge list services
## Plain (no language) block
-```
+```text
plain text fence
preserves whitespace
and renders without syntax highlighting
@@ -194,7 +194,7 @@ line 20
* a bullet introducing a code block
- ```
+ ```text
code at the indented level
second line of code
```
@@ -202,26 +202,27 @@ line 20
:::note
A note next to the code block, both indented under the bullet:
- ```
+ ```text
code inside a note inside a list item
```
+
:::
* another bullet for spacing
## Code in an ordered list
-1. Run the install command:
+1. Run the install command:
- ```bash
- curl -sSf https://get.openziti.io/install.bash | sudo bash
- ```
+ ```bash
+ curl -sSf https://get.openziti.io/install.bash | sudo bash
+ ```
-2. Verify:
+2. Verify:
- ```bash
- ziti version
- ```
+ ```bash
+ ziti version
+ ```
## Code in a table cell
diff --git a/packages/test-site/docs/markdown/typography.mdx b/packages/test-site/docs/markdown/typography.mdx
index 1f6ea240..6dac1271 100644
--- a/packages/test-site/docs/markdown/typography.mdx
+++ b/packages/test-site/docs/markdown/typography.mdx
@@ -1,21 +1,23 @@
-# Typography reference
+---
+title: Typography reference
+---
A single page that exercises every text-level Markdown feature so you can spot
regressions in `legacy.css` or the IFM defaults.
## Heading levels
-# h1 -- The largest heading
+# h1 -- the largest heading
-## h2 -- A major section
+## h2 -- a major section
-### h3 -- A subsection
+### h3 -- a subsection
-#### h4 -- A sub-subsection
+#### h4 -- a sub-subsection
-##### h5 -- Rarely used
+##### h5 -- rarely used
-###### h6 -- The smallest heading
+###### h6 -- the smallest heading
## Paragraphs and inline marks
@@ -77,7 +79,7 @@ H2O is subscript; E = mc2 uses superscript.
- Another bullet
2. Numbered step two
- Bulleted sub-point with `inline code`
- - Bulleted sub-point with a [link](https://example.com)
+ - Bulleted sub-point with a [sample link](https://example.com)
### Task lists
diff --git a/packages/test-site/docs/media/images.mdx b/packages/test-site/docs/media/images.mdx
index 8904717b..f1cc0bef 100644
--- a/packages/test-site/docs/media/images.mdx
+++ b/packages/test-site/docs/media/images.mdx
@@ -75,5 +75,5 @@ Embedding inside admonitions:
| Logo | Description |
|-----------------------------------------|-------------------|
-|
| The brand mark |
-|
| The brand mark |
+|
| The brand mark |
+|
| The brand mark |
diff --git a/packages/test-site/docs/plugins/openapi-scalar.mdx b/packages/test-site/docs/plugins/openapi-scalar.mdx
index b2aac107..77f9a917 100644
--- a/packages/test-site/docs/plugins/openapi-scalar.mdx
+++ b/packages/test-site/docs/plugins/openapi-scalar.mdx
@@ -35,27 +35,27 @@ import type {ScalarOptions} from '@scalar/docusaurus';
## Required setup in the consuming site
-1. Add the dependency:
+1. Add the dependency:
- ```json
- "dependencies": {
- "@scalar/docusaurus": "^0.8.13"
- }
- ```
+ ```json
+ "dependencies": {
+ "@scalar/docusaurus": "^0.8.13"
+ }
+ ```
-2. Add the Scalar theme stylesheet to `themes` -> `customCss`:
+2. Add the Scalar theme stylesheet to `themes` -> `customCss`:
- ```ts
- require.resolve('@scalar/docusaurus/dist/theme.css'),
- ```
+ ```ts
+ require.resolve('@scalar/docusaurus/dist/theme.css'),
+ ```
-3. Add one plugin entry per API spec (see the table above).
+3. Add one plugin entry per API spec (see the table above).
-4. Reference the rendered page from your sidebar or from other docs:
+4. Reference the rendered page from your sidebar or from other docs:
- ```markdown
- See the [Edge Client API reference](/docs/openziti/reference/developer/api/edge-client-api-reference).
- ```
+ ```markdown
+ See the [Edge Client API reference](/docs/openziti/reference/developer/api/edge-client-api-reference).
+ ```
## Why not redocusaurus
diff --git a/packages/test-site/remotes/onprem/docusaurus/docs/index.mdx b/packages/test-site/remotes/onprem/docusaurus/docs/index.mdx
index 87dc752b..25d7a884 100644
--- a/packages/test-site/remotes/onprem/docusaurus/docs/index.mdx
+++ b/packages/test-site/remotes/onprem/docusaurus/docs/index.mdx
@@ -1,4 +1,4 @@
-# Self-Hosted (On-Prem)
+# Self-hosted (On-Prem)
Welcome to the NetFoundry Self-Hosted documentation.
diff --git a/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo.mdx b/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo.mdx
index d8100233..81396b99 100644
--- a/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo.mdx
+++ b/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo.mdx
@@ -9,7 +9,8 @@ import DetailsExample from './_detail_example.mdx'
This is `what` a `regular` code block `that is inline` will look like.
This here is a "plain" code block
-```
+
+```text
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
@@ -20,7 +21,7 @@ A code block inside a bulleted list:
* this is the bullet text
- ```
+ ```text
this is text
this is text
this is text
@@ -28,20 +29,22 @@ A code block inside a bulleted list:
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
-rw-r--r-- 1 user staff 0 Jan 28 12:34 file.txt
```
+
:::note
this is a note?
- ```
+
+ ```text
and a multiline code comment
and a multiline code comment
```
+
:::
* this is another bullet
-
-
This here is a "example" block. an example with just results and no
matching "language" ends up rendering like a plain code block
+
```example
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -51,6 +54,7 @@ drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
This here is a "example" block that has bash attached to it but does
not have a command/code section
+
```example-bash
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -97,7 +101,7 @@ echo "Hello, World!" # Bash
regular, text here
```
-Regular paragraph here `with a small code chuck` in between.
+Regular paragraph here `with a small code chuck` between the fences.
:::note
@@ -135,17 +139,13 @@ Regular paragraph here `with a small code chuck` in between.
:::
-
-
-
:::caution
:::
-
-## GitHub Markdown Examples
+## GitHub Markdown examples
A ReMark plugin translates imported GitHub admonitions to Docusaurus admonitions. Read more about [importing GitHub Markdown](/formatting-demo2.mdx#import-github-markdown).
diff --git a/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo2.mdx b/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo2.mdx
index 249a9ceb..1b1ee11b 100644
--- a/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo2.mdx
+++ b/packages/test-site/remotes/openziti/docusaurus/docs/formatting-demo2.mdx
@@ -30,9 +30,10 @@ npm run build
}
```
-## 🧱 Inside Admonition
+## 🧱 Inside admonition
:::note YAML inside a note
+
```yaml
site:
name: MySite
@@ -41,6 +42,7 @@ site:
- search
- analytics
```
+
:::
## 📋 Lists with Code
@@ -48,11 +50,12 @@ site:
- Install with `yarn`
- Build with `npm run build`
- Run a local server:
+
```bash
npx serve dist
```
-## 🧩 Mixed Content
+## 🧩 Mixed content
> ❗ Run `./deploy.sh` only from the `main` branch.
>
@@ -60,7 +63,7 @@ site:
> ./deploy.sh --prod
> ```
-## 🔗 Code inside Table
+## 🔗 Code inside table
| Command | Description |
|---------|---------------------|
@@ -76,7 +79,7 @@ Use raw HTML for buttons:
```
-## 🧬 Complex Function
+## 🧬 Complex function
```ts
function debounce void>(fn: F, delay = 300): F {
@@ -88,7 +91,6 @@ function debounce void>(fn: F, delay = 300): F {
}
```
-
Demonstrates every admonition, ``, inline HTML, code blocks, and embedded buttons.
---
@@ -167,7 +169,7 @@ type Important = "Very";
---
-## 🔍 Expandable Content
+## 🔍 Expandable content
Click to expand
@@ -177,11 +179,13 @@ type Important = "Very";
```bash
echo "Inside details"
```
+
:::note
```bash
echo "Inside admonition, inside details"
```
+
:::
@@ -190,7 +194,7 @@ echo "Inside admonition, inside details"
---
-## ✅ Confirmed Features
+## ✅ Confirmed features
- [x] Admonitions
- [x] Code blocks
@@ -210,7 +214,7 @@ import SomeMd from '/docs/remotes/some-remote/README.md';
See GitHub Markdown admonition examples in the [Code Block Formatting Demo](./formatting-demo.mdx#github-markdown-examples)
-### When to Use the MarkdownWithoutH1 Component with Imported Markdown
+### When to use the MarkdownWithoutH1 component with imported Markdown
When importing GitHub Markdown as MDX, you have two choices for the H1 page title: use the title from the imported Markdown or set an alternative title.
diff --git a/packages/test-site/remotes/zrok/website/docs/formatting-demo.mdx b/packages/test-site/remotes/zrok/website/docs/formatting-demo.mdx
index 24a63e82..0722d6a1 100644
--- a/packages/test-site/remotes/zrok/website/docs/formatting-demo.mdx
+++ b/packages/test-site/remotes/zrok/website/docs/formatting-demo.mdx
@@ -6,7 +6,7 @@ title: zrok formatting demo
This is `what` a `regular` code block `that is inline` will look like.
-```
+```text
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
diff --git a/packages/test-site/src/pages/mdtest.mdx b/packages/test-site/src/pages/mdtest.mdx
index 72c9f9a8..9098f3c7 100644
--- a/packages/test-site/src/pages/mdtest.mdx
+++ b/packages/test-site/src/pages/mdtest.mdx
@@ -1,11 +1,11 @@
----
-title: Home
-description: Minimal Docusaurus page
----
-
-# Hello
-
-This is a basic Docusaurus page.
-
-- Add content here.
-- Markdown and MDX both work.
\ No newline at end of file
+---
+title: Home
+description: Minimal Docusaurus page
+---
+
+# Hello
+
+This is a basic Docusaurus page.
+
+- Add content here.
+- Markdown and MDX both work.
diff --git a/packages/test-site/versioned_docs/version-beta/formatting-demo.mdx b/packages/test-site/versioned_docs/version-beta/formatting-demo.mdx
index d8100233..81396b99 100644
--- a/packages/test-site/versioned_docs/version-beta/formatting-demo.mdx
+++ b/packages/test-site/versioned_docs/version-beta/formatting-demo.mdx
@@ -9,7 +9,8 @@ import DetailsExample from './_detail_example.mdx'
This is `what` a `regular` code block `that is inline` will look like.
This here is a "plain" code block
-```
+
+```text
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
@@ -20,7 +21,7 @@ A code block inside a bulleted list:
* this is the bullet text
- ```
+ ```text
this is text
this is text
this is text
@@ -28,20 +29,22 @@ A code block inside a bulleted list:
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
-rw-r--r-- 1 user staff 0 Jan 28 12:34 file.txt
```
+
:::note
this is a note?
- ```
+
+ ```text
and a multiline code comment
and a multiline code comment
```
+
:::
* this is another bullet
-
-
This here is a "example" block. an example with just results and no
matching "language" ends up rendering like a plain code block
+
```example
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -51,6 +54,7 @@ drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
This here is a "example" block that has bash attached to it but does
not have a command/code section
+
```example-bash
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -97,7 +101,7 @@ echo "Hello, World!" # Bash
regular, text here
```
-Regular paragraph here `with a small code chuck` in between.
+Regular paragraph here `with a small code chuck` between the fences.
:::note
@@ -135,17 +139,13 @@ Regular paragraph here `with a small code chuck` in between.
:::
-
-
-
:::caution
:::
-
-## GitHub Markdown Examples
+## GitHub Markdown examples
A ReMark plugin translates imported GitHub admonitions to Docusaurus admonitions. Read more about [importing GitHub Markdown](/formatting-demo2.mdx#import-github-markdown).
diff --git a/packages/test-site/versioned_docs/version-beta/formatting-demo2.mdx b/packages/test-site/versioned_docs/version-beta/formatting-demo2.mdx
index 249a9ceb..1b1ee11b 100644
--- a/packages/test-site/versioned_docs/version-beta/formatting-demo2.mdx
+++ b/packages/test-site/versioned_docs/version-beta/formatting-demo2.mdx
@@ -30,9 +30,10 @@ npm run build
}
```
-## 🧱 Inside Admonition
+## 🧱 Inside admonition
:::note YAML inside a note
+
```yaml
site:
name: MySite
@@ -41,6 +42,7 @@ site:
- search
- analytics
```
+
:::
## 📋 Lists with Code
@@ -48,11 +50,12 @@ site:
- Install with `yarn`
- Build with `npm run build`
- Run a local server:
+
```bash
npx serve dist
```
-## 🧩 Mixed Content
+## 🧩 Mixed content
> ❗ Run `./deploy.sh` only from the `main` branch.
>
@@ -60,7 +63,7 @@ site:
> ./deploy.sh --prod
> ```
-## 🔗 Code inside Table
+## 🔗 Code inside table
| Command | Description |
|---------|---------------------|
@@ -76,7 +79,7 @@ Use raw HTML for buttons:
```
-## 🧬 Complex Function
+## 🧬 Complex function
```ts
function debounce void>(fn: F, delay = 300): F {
@@ -88,7 +91,6 @@ function debounce void>(fn: F, delay = 300): F {
}
```
-
Demonstrates every admonition, ``, inline HTML, code blocks, and embedded buttons.
---
@@ -167,7 +169,7 @@ type Important = "Very";
---
-## 🔍 Expandable Content
+## 🔍 Expandable content
Click to expand
@@ -177,11 +179,13 @@ type Important = "Very";
```bash
echo "Inside details"
```
+
:::note
```bash
echo "Inside admonition, inside details"
```
+
:::
@@ -190,7 +194,7 @@ echo "Inside admonition, inside details"
---
-## ✅ Confirmed Features
+## ✅ Confirmed features
- [x] Admonitions
- [x] Code blocks
@@ -210,7 +214,7 @@ import SomeMd from '/docs/remotes/some-remote/README.md';
See GitHub Markdown admonition examples in the [Code Block Formatting Demo](./formatting-demo.mdx#github-markdown-examples)
-### When to Use the MarkdownWithoutH1 Component with Imported Markdown
+### When to use the MarkdownWithoutH1 component with imported Markdown
When importing GitHub Markdown as MDX, you have two choices for the H1 page title: use the title from the imported Markdown or set an alternative title.
diff --git a/packages/test-site/versioned_docs/version-eol/formatting-demo.mdx b/packages/test-site/versioned_docs/version-eol/formatting-demo.mdx
index d8100233..81396b99 100644
--- a/packages/test-site/versioned_docs/version-eol/formatting-demo.mdx
+++ b/packages/test-site/versioned_docs/version-eol/formatting-demo.mdx
@@ -9,7 +9,8 @@ import DetailsExample from './_detail_example.mdx'
This is `what` a `regular` code block `that is inline` will look like.
This here is a "plain" code block
-```
+
+```text
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
@@ -20,7 +21,7 @@ A code block inside a bulleted list:
* this is the bullet text
- ```
+ ```text
this is text
this is text
this is text
@@ -28,20 +29,22 @@ A code block inside a bulleted list:
drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
-rw-r--r-- 1 user staff 0 Jan 28 12:34 file.txt
```
+
:::note
this is a note?
- ```
+
+ ```text
and a multiline code comment
and a multiline code comment
```
+
:::
* this is another bullet
-
-
This here is a "example" block. an example with just results and no
matching "language" ends up rendering like a plain code block
+
```example
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -51,6 +54,7 @@ drwxr-xr-x 20 user staff 640 Jan 28 12:34 ..
This here is a "example" block that has bash attached to it but does
not have a command/code section
+
```example-bash
@results: total 12
drwxr-xr-x 5 user staff 160 Jan 28 12:34 .
@@ -97,7 +101,7 @@ echo "Hello, World!" # Bash
regular, text here
```
-Regular paragraph here `with a small code chuck` in between.
+Regular paragraph here `with a small code chuck` between the fences.
:::note
@@ -135,17 +139,13 @@ Regular paragraph here `with a small code chuck` in between.
:::
-
-
-
:::caution
:::
-
-## GitHub Markdown Examples
+## GitHub Markdown examples
A ReMark plugin translates imported GitHub admonitions to Docusaurus admonitions. Read more about [importing GitHub Markdown](/formatting-demo2.mdx#import-github-markdown).
diff --git a/packages/test-site/versioned_docs/version-eol/formatting-demo2.mdx b/packages/test-site/versioned_docs/version-eol/formatting-demo2.mdx
index 249a9ceb..1b1ee11b 100644
--- a/packages/test-site/versioned_docs/version-eol/formatting-demo2.mdx
+++ b/packages/test-site/versioned_docs/version-eol/formatting-demo2.mdx
@@ -30,9 +30,10 @@ npm run build
}
```
-## 🧱 Inside Admonition
+## 🧱 Inside admonition
:::note YAML inside a note
+
```yaml
site:
name: MySite
@@ -41,6 +42,7 @@ site:
- search
- analytics
```
+
:::
## 📋 Lists with Code
@@ -48,11 +50,12 @@ site:
- Install with `yarn`
- Build with `npm run build`
- Run a local server:
+
```bash
npx serve dist
```
-## 🧩 Mixed Content
+## 🧩 Mixed content
> ❗ Run `./deploy.sh` only from the `main` branch.
>
@@ -60,7 +63,7 @@ site:
> ./deploy.sh --prod
> ```
-## 🔗 Code inside Table
+## 🔗 Code inside table
| Command | Description |
|---------|---------------------|
@@ -76,7 +79,7 @@ Use raw HTML for buttons:
```
-## 🧬 Complex Function
+## 🧬 Complex function
```ts
function debounce void>(fn: F, delay = 300): F {
@@ -88,7 +91,6 @@ function debounce void>(fn: F, delay = 300): F {
}
```
-
Demonstrates every admonition, ``, inline HTML, code blocks, and embedded buttons.
---
@@ -167,7 +169,7 @@ type Important = "Very";
---
-## 🔍 Expandable Content
+## 🔍 Expandable content
Click to expand
@@ -177,11 +179,13 @@ type Important = "Very";
```bash
echo "Inside details"
```
+
:::note
```bash
echo "Inside admonition, inside details"
```
+
:::
@@ -190,7 +194,7 @@ echo "Inside admonition, inside details"
---
-## ✅ Confirmed Features
+## ✅ Confirmed features
- [x] Admonitions
- [x] Code blocks
@@ -210,7 +214,7 @@ import SomeMd from '/docs/remotes/some-remote/README.md';
See GitHub Markdown admonition examples in the [Code Block Formatting Demo](./formatting-demo.mdx#github-markdown-examples)
-### When to Use the MarkdownWithoutH1 Component with Imported Markdown
+### When to use the MarkdownWithoutH1 component with imported Markdown
When importing GitHub Markdown as MDX, you have two choices for the H1 page title: use the title from the imported Markdown or set an alternative title.
diff --git a/skills/doc-check/SKILL.md b/skills/doc-check/SKILL.md
index 3e841938..9d8a0e38 100644
--- a/skills/doc-check/SKILL.md
+++ b/skills/doc-check/SKILL.md
@@ -3,7 +3,7 @@ name: doc-check
description: Check merged PRs in a repo for customer-facing changes, cross-reference against existing docs, and flag what is missing, stale, or already covered
---
-Check merged PRs in a product's source repo(s) for customer-facing changes. For each flagged PR, search the local doc
+Check merged PRs in a product's source repos for customer-facing changes. For each flagged PR, search the local doc
directory to assess whether coverage already exists, is stale, or is missing entirely. Produce an actionable report,
then optionally generate a doc draft.
@@ -11,13 +11,13 @@ then optionally generate a doc draft.
Before using this skill, configure it for your organization:
-1. **Update the product registry** below with your products, source repos, auth method, and local doc paths.
-2. **Set auth environment variables** as needed (see Auth section below).
-3. **Update the style guide reference** in step 7 to point to your own documentation style guide.
+1. **Update the product registry** below with your products, source repos, auth method, and local doc paths.
+2. **Set auth environment variables** as needed (see Auth section below).
+3. **Update the style guide reference** in step 7 to point to your own documentation style guide.
## Invocation
-```
+```text
/doc-check status
/doc-check
/doc-check --since YYYY-MM-DD
@@ -126,12 +126,12 @@ combined total (most recent first across repos). In the report, prefix each PR w
**Monorepos**: If a source repo hosts multiple products, read the PR title, description, and diff to determine which
product it belongs to before assessing doc coverage. Mark PRs for other products as skipped with a note.
-### 3. Assess each PR: customer-facing or internal?
+### 3. Assess each PR: Customer-facing or internal?
Fetch the diff for each PR in **two passes**. Never truncate or pipe through `head` — silently dropping later parts
of a diff means you miss entire files and their changes, which leads to false coverage assessments.
-#### Pass 1: get the full file list
+#### Pass 1: Get the full file list
```bash
# GitHub
@@ -151,7 +151,7 @@ This gives you the complete list of changed files before you read any content. U
what's left uncovered
- Scope the content pass to files that matter
-#### Pass 2: read the content of relevant files
+#### Pass 2: Read the content of relevant files
For each file identified in pass 1 that could affect user-facing behavior, read its diff section in full.
For large diffs, use targeted extraction rather than reading the whole diff linearly:
@@ -178,7 +178,7 @@ descriptions; the diff is the authoritative source. Look for:
Also read any doc files the author changed in the PR — understand what they covered so your coverage assessment
reflects what's actually missing, not what was already addressed.
-**Bitbucket note:** The simple `/pullrequests//diff` endpoint returns empty results. Batch-extract commit
+**Bitbucket note:** The simple `/pullrequests//diff` endpoint returns empty results. Batch-fetch commit
hashes from the PR list response (fields `source.commit.hash` and `destination.commit.hash`) and construct the
diff URL as shown above.
@@ -213,7 +213,7 @@ current local state — don't abort the scan.
For each customer-facing PR, search the local doc path for the product (see registry above) to determine whether
coverage already exists. Use grep and file reads — do not guess.
-Extract 2–4 key terms from the PR (feature name, CLI flag, config key, endpoint name, etc.) and search for them:
+Identify 2–4 key terms from the PR (feature name, CLI flag, config key, endpoint name, etc.) and search for them:
```bash
grep -r "" --include="*.md" --include="*.mdx" -l
@@ -232,7 +232,7 @@ Dismiss **covered** PRs from the flagged list entirely (move them to a "no actio
### 5. Output the report
-```
+```markdown
## doc-check: product-a (since 2026-03-18)
Sources: your-org/repo-a (6 PRs), your-org/repo-b (4 PRs) — 10 total · 2 need doc work · 1 already covered · 7 skipped (internal)
@@ -268,36 +268,36 @@ After the report, prompt:
When `--draft ` is passed:
-1. Fetch the PR title, description, and full diff from the specified repo
-2. If the status was **stale**, read the existing doc file first — the draft should update it, not replace it
-3. If the status was **missing**, write a new file from scratch
-4. Identify what changed from a user perspective
-5. Determine the appropriate doc type (how-to, reference, concept explanation) using Diátaxis
-6. Write the draft following your organization's documentation style guide:
- - Sentence-style headers; imperative verb phrases for how-to titles
- - Active voice, second person ("you/your")
- - Backticks for CLI flags, commands, config keys, code tokens
- - 120-character line length limit
-7. **Only write what the diff and PR description directly support.** Do not infer, extrapolate, or invent behavior that
- isn't shown. If the diff shows a flag exists but not what it does, say so — don't guess. If the PR description is
- vague or the diff is too large to confidently summarize, stop and ask the user to clarify before drafting. It's
- better to ask one question than to ship a plausible-sounding but wrong doc.
-8. Present the full draft inline in the terminal, followed by the suggested file path, then prompt the user with these
- options:
-
- > **What would you like to do?**
- > - **Execute** — write the draft directly to the suggested file path in the doc repo
- > - **Edit** — describe changes and I'll update the draft before writing
- > - **Save to drafts** — save to `output//drafts/--.md` for later without touching the doc repo
-
- **Do not call Edit or Write on any doc repo file until the user selects "Execute".** Saving to drafts is always safe
- and does not require confirmation.
+1. Fetch the PR title, description, and full diff from the specified repo
+2. If the status was **stale**, read the existing doc file first — the draft should update it, not replace it
+3. If the status was **missing**, write a new file from scratch
+4. Identify what changed from a user perspective
+5. Determine the appropriate doc type (how-to, reference, concept explanation) using Diátaxis
+6. Write the draft following your organization's documentation style guide:
+ - Sentence-style headers; imperative verb phrases for how-to titles
+ - Active voice, second person ("you/your")
+ - Backticks for CLI flags, commands, config keys, code tokens
+ - 120-character line length limit
+7. **Only write what the diff and PR description directly support.** Do not infer, extrapolate, or invent behavior that
+ isn't shown. If the diff shows a flag exists but not what it does, say so — don't guess. If the PR description is
+ vague or the diff is too large to confidently summarize, stop and ask the user to clarify before drafting. It's
+ better to ask one question than to ship a plausible-sounding but wrong doc.
+8. Present the full draft inline in the terminal, followed by the suggested file path, then prompt the user with these
+ options:
+
+ > **What would you like to do?**
+ > - **Execute** — write the draft directly to the suggested file path in the doc repo
+ > - **Edit** — describe changes and I'll update the draft before writing
+ > - **Save to drafts** — save to `output//drafts/--.md` for later without touching the doc repo
+
+ **Do not call Edit or Write on any doc repo file until the user selects "Execute".** Saving to drafts is always safe
+ and does not require confirmation.
#### Drafts folder
Drafts are saved to `output//drafts/` under the doc-check skill folder:
-```
+```text
/doc-check/output//drafts/--.md
```
@@ -328,7 +328,7 @@ Filename format: `YYYY-MM-DD.md` (use today's date). If a file with that name al
Write the report in the same markdown format shown in step 5, with a one-line header added at the top:
-```
+```markdown
# doc-check: — YYYY-MM-DD
```
@@ -336,7 +336,7 @@ Write the report in the same markdown format shown in step 5, with a one-line he
After saving the report, regenerate `STATUS.md` at the root of the doc-check skill folder:
-```
+```text
/doc-check/STATUS.md
```
diff --git a/unified-doc/README.md b/unified-doc/README.md
index 700e51e7..304e5d4c 100644
--- a/unified-doc/README.md
+++ b/unified-doc/README.md
@@ -8,7 +8,7 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
yarn
```
-## Local Development
+## Local development
```bash
yarn start
@@ -59,12 +59,12 @@ Not using SSH:
GIT_USER= yarn deploy
```
-
-### Kinsta Hosting
+### Kinsta hosting
As of Sep 2025 - the technical docs have been published to the public folder on kinsta.
A **CUSTOM** rule was added by tech support:
-```
+
+```nginx
location /docs {
try_files $uri /docs/index.html;
}
@@ -74,7 +74,7 @@ This rule is **mandatory** for SPA deep linking. The tech support people had to
---
-## Visual Regression Testing
+## Visual regression testing
The unified-doc site includes BackstopJS for visual regression testing against production. This helps catch unintended visual changes when updating the theme, components, or content.
@@ -85,7 +85,7 @@ The unified-doc site includes BackstopJS for visual regression testing against p
- 3 viewports: desktop (1920x1080), tablet (768x1024), mobile (375x812)
- Generates HTML diff reports highlighting visual differences
-### Quick Start
+### Quick start
```bash
# Install dependencies (includes backstopjs)
@@ -108,7 +108,7 @@ yarn vrt:test:zlan
yarn vrt:report:zlan
```
-### Available Commands
+### Available commands
Each product has its own set of commands:
@@ -125,11 +125,11 @@ Products: `home`, `openziti`, `frontdoor`, `selfhosted`, `zrok`, `zlan`
### Workflow
-1. **Generate scenarios** - Fetches sitemap from production and creates `backstop..json` configs
-2. **Capture reference** - Screenshots production site as the baseline
-3. **Run tests** - Screenshots local site and compares against reference
-4. **Review report** - HTML report shows side-by-side diffs with highlighted changes
-5. **Approve changes** - If changes are intentional, approve to update reference
+1. **Generate scenarios** - Fetches sitemap from production and creates `backstop..json` configs
+2. **Capture reference** - Screenshots production site as the baseline
+3. **Run tests** - Screenshots local site and compares against reference
+4. **Review report** - HTML report shows side-by-side diffs with highlighted changes
+5. **Approve changes** - If changes are intentional, approve to update reference
### Configuration
@@ -140,6 +140,7 @@ Generated config files (`backstop..json`) include:
- **delay: 2000ms** - Waits for page load before screenshot
The `onReady.js` script automatically hides:
+
- Cookie consent banners
- Chat widgets
- Hotjar feedback widgets
@@ -147,6 +148,7 @@ The `onReady.js` script automatically hides:
### Filtering
The scenario generator excludes:
+
- Blog posts (`/blog/`)
- zrok versioned docs (only tests latest)
- Tag pages
@@ -154,7 +156,7 @@ The scenario generator excludes:
### Files
-```
+```text
unified-doc/
├── scripts/
│ └── generate-vrt-scenarios.mjs # Sitemap parser & config generator
diff --git a/unified-doc/build-docs.mjs b/unified-doc/build-docs.mjs
new file mode 100644
index 00000000..60a3d61c
--- /dev/null
+++ b/unified-doc/build-docs.mjs
@@ -0,0 +1,585 @@
+#!/usr/bin/env node
+// =============================================================================
+// build-docs.mjs — Build the unified NetFoundry documentation site.
+//
+// This is the SINGLE source of truth for the unified-doc build orchestration.
+// build-docs.sh and build-docs.ps1 are thin platform shims that call this file;
+// do not reimplement build logic in them.
+//
+// Clones (or updates) all product doc repos into _remotes/, runs lint checks,
+// builds SDK reference docs via ziti-doc's gendoc script, then runs a Docusaurus
+// production build.
+//
+// USAGE
+// node build-docs.mjs [OPTIONS]
+//
+// OPTIONS
+// --ziti-doc-branch=BRANCH Branch for openziti/ziti-doc (default: main)
+// --zrok-branch=BRANCH Branch for openziti/zrok (default: main)
+// --frontdoor-branch=BRANCH Branch for netfoundry/zrok-connector (default: develop)
+// --selfhosted-branch=BRANCH Branch for netfoundry/k8s-on-prem-installations (default: main)
+// --zlan-branch=BRANCH Branch for netfoundry/zlan (default: main)
+// --platform-branch=BRANCH Branch for netfoundry/platform-doc (default: main)
+// --data-connector-branch=BRANCH Branch for netfoundry/nf-data-connector (default: main)
+// --customer-connect-branch=BRANCH Branch for netfoundry/customer-connect-docs (default: main)
+// --clean Wipe _remotes and .docusaurus cache before building
+// --lint-only Run lint checks only; skip build
+// --qualifier=VALUE Append VALUE to output dir (e.g. --qualifier=-preview -> build-preview)
+// -l (gendoc) Skip linked doc generation (doxygen/wget)
+// -g (gendoc) Skip git clones inside gendoc
+// -c (gendoc) Skip clean steps inside gendoc
+// -h, --help Show this help and exit
+//
+// ENVIRONMENT VARIABLES
+// GH_ZITI_CI_REPO_ACCESS_PAT GitHub PAT for ziti-doc and zlan (falls back to SSH)
+// BB_REPO_TOKEN_FRONTDOOR Bitbucket token for zrok-connector (falls back to SSH)
+// BB_REPO_TOKEN_ONPREM Bitbucket token for k8s-on-prem-installations (falls back to SSH)
+// BB_REPO_TOKEN_PLATFORM_DOC Bitbucket token for platform-doc (falls back to SSH)
+// BB_REPO_TOKEN_DATA_CONNECTOR Bitbucket token for nf-data-connector (falls back to SSH)
+// BB_REPO_TOKEN_CUSTOMER_CONNECT Bitbucket token for customer-connect-docs (falls back to SSH)
+// BB_USERNAME Bitbucket username (default: x-token-auth)
+// DOCUSAURUS_BUILD_MASK Hex bitmask: 0x1=openziti 0x2=frontdoor 0x4=selfhosted
+// 0x8=zrok 0x10=zlan 0x20=platform
+// 0x40=data-connector 0x80=llm-gateway 0x100=mcp-gateway
+// 0x200=customer-connect 0x3FF=all (config default: 0x3FF)
+// DOCUSAURUS_PUBLISH_ENV Set to 'prod' to use production Algolia index
+// NO_MINIFY Set to any value to pass --no-minify to Docusaurus
+// IS_VERCEL Set to 'true' on Vercel preview deployments
+//
+// EXAMPLES
+// node build-docs.mjs --ziti-doc-branch=my.branch.name
+// DOCUSAURUS_BUILD_MASK=0x1 node build-docs.mjs --ziti-doc-branch=my.branch.name
+// node build-docs.mjs --clean --lint-only
+// node build-docs.mjs --qualifier=-preview -l
+// =============================================================================
+
+import { spawnSync } from "node:child_process";
+import {
+ existsSync, readdirSync, rmSync, mkdirSync, cpSync, writeFileSync,
+} from "node:fs";
+import { join, dirname, basename } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const isWin = process.platform === "win32";
+const GIT = "git";
+const YARN = isWin ? "yarn.cmd" : "yarn";
+const VALE = "vale";
+const MDLINT = isWin ? "markdownlint.cmd" : "markdownlint";
+
+// On Windows, markdownlint runs through cmd.exe (~8191-char command-line cap),
+// so lint files in small batches there; larger batches elsewhere.
+const LINT_BATCH = isWin ? 50 : 200;
+
+const scriptDir = dirname(fileURLToPath(import.meta.url));
+const remotesDir = join(scriptDir, "_remotes");
+
+// =============================================================================
+// SMALL PROCESS / FS HELPERS
+// =============================================================================
+
+// Node on Windows refuses to spawnSync a .cmd/.bat without a shell (EINVAL, since
+// the CVE-2024-27980 fix). Route those through cmd.exe and quote args ourselves —
+// shell:true does NOT quote array args for us. Non-.cmd/.exe commands and every
+// platform other than Windows go straight through.
+function spawnCompat(cmd, args, opts = {}) {
+ if (isWin && /\.(cmd|bat)$/i.test(cmd)) {
+ const quoted = args.map((a) =>
+ /[\s"&|<>^()%!]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a,
+ );
+ return spawnSync(cmd, quoted, { ...opts, shell: true });
+ }
+ return spawnSync(cmd, args, opts);
+}
+
+// Run a command, stream its output, and abort the whole build on failure
+// (mirrors `set -euo pipefail`).
+function run(cmd, args, opts = {}) {
+ const r = spawnCompat(cmd, args, { stdio: "inherit", env: process.env, ...opts });
+ if (r.error) {
+ console.error(`ERROR: failed to run '${cmd}': ${r.error.message}`);
+ process.exit(1);
+ }
+ if (r.status !== 0) {
+ console.error(`ERROR: '${cmd}' exited with code ${r.status}`);
+ process.exit(r.status ?? 1);
+ }
+ return r;
+}
+
+// Run a command and capture stdout/stderr without aborting.
+function capture(cmd, args, opts = {}) {
+ return spawnCompat(cmd, args, {
+ encoding: "utf8", env: process.env, maxBuffer: 64 * 1024 * 1024, ...opts,
+ });
+}
+
+// Is a command available on PATH?
+function has(cmd) {
+ const r = spawnCompat(cmd, ["--version"], { stdio: "ignore" });
+ return !r.error && r.status === 0;
+}
+
+function chunk(arr, n) {
+ const out = [];
+ for (let i = 0; i < arr.length; i += n) out.push(arr.slice(i, i + n));
+ return out;
+}
+
+// =============================================================================
+// ARGUMENT PARSING (mirror of build-docs.sh)
+// =============================================================================
+
+const USAGE = `Usage: node build-docs.mjs [OPTIONS] — see header of build-docs.mjs for full docs.`;
+
+const branches = {
+ zitiDoc : "main",
+ zrok : "main",
+ frontdoor : "develop",
+ selfhosted : "main",
+ zlan : "main",
+ platform : "main",
+ dataConnector : "main",
+ customerConnect: "main",
+};
+const BRANCH_FLAG = {
+ "--ziti-doc-branch": "zitiDoc",
+ "--zrok-branch": "zrok",
+ "--frontdoor-branch": "frontdoor",
+ "--selfhosted-branch": "selfhosted",
+ "--zlan-branch": "zlan",
+ "--platform-branch": "platform",
+ "--data-connector-branch": "dataConnector",
+ "--customer-connect-branch": "customerConnect",
+};
+
+let clean = false;
+let lintOnly = false;
+let qualifier = "";
+const otherFlags = []; // forwarded verbatim to gendoc (-l/-g/-c/...)
+
+const argv = process.argv.slice(2);
+for (let i = 0; i < argv.length; i++) {
+ const a = argv[i];
+
+ const need = (flag) => {
+ const v = argv[++i];
+ if (v === undefined) {
+ console.error(`Error: ${flag} requires a value`);
+ process.exit(1);
+ }
+ return v;
+ };
+
+ let handled = false;
+ for (const [flag, key] of Object.entries(BRANCH_FLAG)) {
+ if (a === flag) { branches[key] = need(flag); handled = true; break; }
+ if (a.startsWith(`${flag}=`)) { branches[key] = a.slice(flag.length + 1); handled = true; break; }
+ }
+ if (handled) continue;
+
+ if (a === "--clean") { clean = true; continue; }
+ if (a === "--lint-only") { lintOnly = true; continue; }
+ if (a === "-h" || a === "--help") { console.log(USAGE); process.exit(0); }
+ if (a === "--qualifier") { qualifier = need("--qualifier"); continue; }
+ if (a.startsWith("--qualifier=")) { qualifier = a.slice("--qualifier=".length); continue; }
+ if (a.startsWith("-")) { otherFlags.push(a); continue; } // gendoc passthrough
+ // non-dash extra args are ignored, matching build-docs.sh
+}
+
+// =============================================================================
+// BUILD CONFIGURATION BANNER
+// =============================================================================
+
+const line = "========================================";
+console.log(line);
+console.log("BUILD CONFIGURATION");
+console.log(line);
+console.log(` BRANCH_ZITI_DOC='${branches.zitiDoc}'`);
+console.log(` BRANCH_ZROK='${branches.zrok}'`);
+console.log(` BRANCH_FRONTDOOR='${branches.frontdoor}'`);
+console.log(` BRANCH_SELFHOSTED='${branches.selfhosted}'`);
+console.log(` BRANCH_ZLAN='${branches.zlan}'`);
+console.log(` BRANCH_PLATFORM='${branches.platform}'`);
+console.log(` BRANCH_DATA_CONNECTOR='${branches.dataConnector}'`);
+console.log(` BRANCH_CUSTOMER_CONNECT='${branches.customerConnect}'`);
+console.log(` CLEAN=${clean ? 1 : 0}`);
+console.log(` IS_VERCEL='${process.env.IS_VERCEL ?? ""}'`);
+console.log(` node: ${process.version}`);
+console.log(` yarn: ${capture(YARN, ["--version"]).stdout?.trim() || "not found"}`);
+console.log(line);
+
+// =============================================================================
+// clone_or_update
+// =============================================================================
+
+// Rewrite a public https URL into an authenticated one when the matching token
+// env var is set; otherwise fall back to SSH. Mirrors the case block in .sh.
+function authUrl(url) {
+ const bbUser = process.env.BB_USERNAME || "x-token-auth";
+ const gh = (path, token) => `https://x-access-token:${token}@github.com/${path}`;
+ const bb = (path, token) => `https://${bbUser}:${token}@bitbucket.org/${path}`;
+
+ if (url.includes("zlan")) {
+ if (process.env.GH_ZITI_CI_REPO_ACCESS_PAT) {
+ console.error("🔑 Using GH_ZITI_CI_REPO_ACCESS_PAT token for zlan");
+ return gh("netfoundry/zlan.git", process.env.GH_ZITI_CI_REPO_ACCESS_PAT);
+ }
+ console.error("🔑 Using SSH for zlan");
+ return "git@github.com:netfoundry/zlan.git";
+ }
+ if (url.includes("k8s-on-prem-installations")) {
+ if (process.env.BB_REPO_TOKEN_ONPREM) {
+ console.error("🔑 Using BB_REPO_TOKEN_ONPREM token");
+ return bb("netfoundry/k8s-on-prem-installations.git", process.env.BB_REPO_TOKEN_ONPREM);
+ }
+ console.error("🔑 Using SSH for self-hosted");
+ return "git@bitbucket.org:netfoundry/k8s-on-prem-installations.git";
+ }
+ if (url.includes("zrok-connector")) {
+ if (process.env.BB_REPO_TOKEN_FRONTDOOR) {
+ console.error("🔑 Using BB_REPO_TOKEN_FRONTDOOR token");
+ return bb("netfoundry/zrok-connector.git", process.env.BB_REPO_TOKEN_FRONTDOOR);
+ }
+ console.error("🔑 Using SSH for frontdoor");
+ return "git@bitbucket.org:netfoundry/zrok-connector.git";
+ }
+ if (url.includes("ziti-doc")) {
+ if (process.env.GH_ZITI_CI_REPO_ACCESS_PAT) {
+ console.error("🔑 Using GH_ZITI_CI_REPO_ACCESS_PAT token for ziti-doc");
+ return gh("openziti/ziti-doc.git", process.env.GH_ZITI_CI_REPO_ACCESS_PAT);
+ }
+ console.error("🔑 Using SSH for ziti-doc");
+ return "git@github.com:openziti/ziti-doc.git";
+ }
+ if (url.includes("platform-doc")) {
+ if (process.env.BB_REPO_TOKEN_PLATFORM_DOC) {
+ console.error("🔑 Using BB_REPO_TOKEN_PLATFORM_DOC token");
+ return bb("netfoundry/platform-doc.git", process.env.BB_REPO_TOKEN_PLATFORM_DOC);
+ }
+ console.error("🔑 Using SSH for platform-doc");
+ return "git@bitbucket.org:netfoundry/platform-doc.git";
+ }
+ if (url.includes("nf-data-connector")) {
+ if (process.env.BB_REPO_TOKEN_DATA_CONNECTOR) {
+ console.error("🔑 Using BB_REPO_TOKEN_DATA_CONNECTOR token");
+ return bb("netfoundry/nf-data-connector.git", process.env.BB_REPO_TOKEN_DATA_CONNECTOR);
+ }
+ console.error("🔑 Using SSH for nf-data-connector");
+ return "git@bitbucket.org:netfoundry/nf-data-connector.git";
+ }
+ if (url.includes("customer-connect-docs")) {
+ if (process.env.BB_REPO_TOKEN_CUSTOMER_CONNECT) {
+ console.error("🔑 Using BB_REPO_TOKEN_CUSTOMER_CONNECT token");
+ return bb("netfoundry/customer-connect-docs.git", process.env.BB_REPO_TOKEN_CUSTOMER_CONNECT);
+ }
+ console.error("🔑 Using SSH for customer-connect-docs");
+ return "git@bitbucket.org:netfoundry/customer-connect-docs.git";
+ }
+ return url; // public (e.g. openziti/zrok) — no auth needed
+}
+
+function redact(url) {
+ return url.replace(/:\/\/[^@]+@/, "://[REDACTED]@");
+}
+
+function failWithBranches(branch, url) {
+ console.log(`❌ Branch '${branch}' not found in ${redact(url)}`);
+ console.log("👉 Available branches:");
+ const ls = capture(GIT, ["ls-remote", "--heads", url]);
+ for (const l of (ls.stdout || "").split("\n")) {
+ const ref = l.split(/\s+/)[1];
+ if (ref) console.log(ref.replace("refs/heads/", ""));
+ }
+ process.exit(1);
+}
+
+function cloneOrUpdate(rawUrl, dest, branch = "main") {
+ const target = join(remotesDir, dest);
+ const url = authUrl(rawUrl);
+
+ if (existsSync(join(target, ".git"))) {
+ console.log(`Updating '${dest}' @ '${branch}'...`);
+ if (capture(GIT, ["-C", target, "remote", "set-url", "origin", url]).status !== 0) {
+ capture(GIT, ["-C", target, "remote", "add", "origin", url]);
+ }
+ const fetched = spawnSync(GIT, ["-C", target, "fetch", "--depth", "1", "origin", branch], {
+ stdio: "inherit", env: process.env,
+ });
+ if (fetched.status !== 0) failWithBranches(branch, url);
+ const reset = spawnSync(GIT, ["-C", target, "reset", "--hard", "FETCH_HEAD"], {
+ stdio: "inherit", env: process.env,
+ });
+ if (reset.status !== 0) failWithBranches(branch, url);
+ } else if (existsSync(target)) {
+ console.error(`❌ ${target} exists but is not a git repo.`);
+ process.exit(1);
+ } else {
+ console.log(`Cloning '${dest}' @ '${branch}'...`);
+ const cloned = spawnSync(
+ GIT,
+ ["clone", "--single-branch", "--branch", branch, "--depth", "1", url, target],
+ { stdio: "inherit", env: process.env },
+ );
+ if (cloned.status !== 0) failWithBranches(branch, url);
+ }
+}
+
+// =============================================================================
+// Directory walking (used by lint, stale-artifact cleanup)
+// =============================================================================
+
+const PRUNE_DIRS = new Set([
+ "node_modules", ".git", "build", "versioned_docs", "_partials", "_remotes",
+]);
+
+function* walkMarkdown(dir) {
+ let entries;
+ try {
+ entries = readdirSync(dir, { withFileTypes: true });
+ } catch {
+ return;
+ }
+ for (const e of entries) {
+ const p = join(dir, e.name);
+ if (e.isDirectory()) {
+ if (PRUNE_DIRS.has(e.name)) continue;
+ yield* walkMarkdown(p);
+ } else if (/\.(md|mdx)$/i.test(e.name) && !e.name.startsWith("_")) {
+ yield p;
+ }
+ }
+}
+
+// =============================================================================
+// sync_versioned_remote
+// =============================================================================
+
+// Copy a remote's versioned-docs subtree up to the unified-doc site root.
+// (Mirror of Invoke-SyncVersionedRemote in build-docs.ps1 -- keep in sync.)
+function syncVersionedRemote(remote) {
+ let remoteDir = "";
+ for (const sub of ["website", "docusaurus"]) {
+ const candidate = join(remotesDir, remote, sub);
+ if (existsSync(candidate)) { remoteDir = candidate; break; }
+ }
+ if (!remoteDir) {
+ console.error(`ERROR: cannot find _remotes/${remote}/{website,docusaurus}`);
+ process.exit(1);
+ }
+
+ console.log(`Copying versioned docs from ${remoteDir}...`);
+ for (const item of [
+ `${remote}_versioned_docs`,
+ `${remote}_versioned_sidebars`,
+ `${remote}_versions.json`,
+ ]) {
+ const src = join(remoteDir, item);
+ const dest = join(scriptDir, item);
+ if (!existsSync(src)) {
+ console.log(` WARN: source missing: ${src} -- skipping`);
+ continue;
+ }
+ rmSync(dest, { recursive: true, force: true });
+ cpSync(src, dest, { recursive: true });
+ console.log(` copied ${item}`);
+ }
+}
+
+// =============================================================================
+// lint_docs
+// =============================================================================
+
+function stripAnsi(s) {
+ // eslint-disable-next-line no-control-regex
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
+}
+
+function cleanLog(s) {
+ return (s || "")
+ .split(/\r?\n/)
+ .map((l) => stripAnsi(l).replace(/.*[\\/]_remotes[\\/]/, "_remotes/"))
+ .join("\n");
+}
+
+function lintDocs() {
+ console.log("🔍 Starting Quality Checks...");
+
+ const potentialTargets = [
+ join(remotesDir, "zlan", "docusaurus", "docs"),
+ join(remotesDir, "frontdoor", "docusaurus", "docs"),
+ join(remotesDir, "zrok", "website", "docs"),
+ join(remotesDir, "selfhosted", "docusaurus", "docs"),
+ join(remotesDir, "openziti", "docusaurus", "docs"),
+ join(remotesDir, "platform", "docusaurus", "docs"),
+ join(remotesDir, "data-connector", "docusaurus", "docs"),
+ join(remotesDir, "customer-connect", "docusaurus", "docs"),
+ ];
+ const validTargets = potentialTargets.filter((t) => existsSync(t));
+
+ if (validTargets.length === 0) {
+ console.log("⚠️ No documentation directories found. Skipping lint.");
+ return;
+ }
+
+ console.log("🎯 Gathering file list...");
+ const files = [];
+ for (const t of validTargets) {
+ for (const f of walkMarkdown(t)) files.push(f);
+ }
+ console.log(`📊 Found ${files.length} files to scan...`);
+ if (files.length === 0) {
+ console.log("⚠️ No files found. Skipping.");
+ return;
+ }
+
+ const valeIni = join(scriptDir, "..", "docs-linter", ".vale.ini");
+ const mdlintJson = join(scriptDir, "..", "docs-linter", ".markdownlint.json");
+
+ let valeOut = "";
+ if (has(VALE)) {
+ console.log("📝 Running Vale...");
+ for (const batch of chunk(files, LINT_BATCH)) {
+ const r = capture(VALE, ["--config", valeIni, "--no-wrap", "--no-exit", ...batch], {
+ timeout: 120000,
+ });
+ valeOut += (r.stdout || "") + (r.stderr || "");
+ }
+ }
+
+ let mdOut = "";
+ if (has(MDLINT)) {
+ console.log("🧹 Running Markdownlint...");
+ for (const batch of chunk(files, LINT_BATCH)) {
+ const r = capture(MDLINT, ["--config", mdlintJson, ...batch], { timeout: 120000 });
+ mdOut += (r.stdout || "") + (r.stderr || "");
+ }
+ }
+
+ const valeClean = cleanLog(valeOut);
+ const mdClean = cleanLog(mdOut);
+
+ const countLines = (s, sub) => s.split("\n").filter((l) => l.includes(sub)).length;
+ const vErr = countLines(valeClean, "error");
+ const vWarn = countLines(valeClean, "warning");
+ const vSug = countLines(valeClean, "suggestion");
+ const mdErr = mdClean.split("\n").filter((l) => l.trim() !== "").length;
+ const total = vErr + vWarn + vSug + mdErr;
+
+ console.log("");
+ console.log("========================================================");
+ console.log("📊 QUALITY CHECK SUMMARY");
+ console.log("========================================================");
+ console.log(` 📄 Files Scanned: ${files.length}`);
+ console.log(` 🛑 Vale Errors: ${vErr}`);
+ console.log(` ⚠️ Vale Warnings: ${vWarn}`);
+ console.log(` 💡 Vale Suggestions: ${vSug}`);
+ console.log(` 🧹 Markdownlint Issues: ${mdErr}`);
+ console.log("--------------------------------------------------------");
+ console.log(` 🚨 TOTAL ISSUES: ${total}`);
+ console.log("========================================================");
+ console.log("");
+
+ if (mdErr > 0) {
+ console.log("################### MARKDOWNLINT REPORT ###################");
+ console.log(mdClean.trim());
+ console.log("");
+ }
+ if (vErr + vWarn + vSug > 0) {
+ console.log("####################### VALE REPORT #######################");
+ console.log(valeClean.trim());
+ if (mdErr > 0) {
+ console.log(`🛑 BUT WAIT! You also have ${mdErr} Markdownlint errors (see above).`);
+ }
+ console.log("");
+ }
+
+ console.log("✅ Quality Checks Complete.");
+}
+
+// =============================================================================
+// MAIN EXECUTION
+// =============================================================================
+
+if (clean) {
+ console.log("CLEAN: removing _remotes contents (preserving package.json)");
+ for (const name of readdirSync(remotesDir)) {
+ if (name === "package.json") continue;
+ rmSync(join(remotesDir, name), { recursive: true, force: true });
+ }
+}
+
+cloneOrUpdate("https://bitbucket.org/netfoundry/zrok-connector.git", "frontdoor", branches.frontdoor);
+cloneOrUpdate("https://bitbucket.org/netfoundry/k8s-on-prem-installations.git", "selfhosted", branches.selfhosted);
+cloneOrUpdate("https://github.com/openziti/ziti-doc.git", "openziti", branches.zitiDoc);
+cloneOrUpdate("https://github.com/netfoundry/zlan.git", "zlan", branches.zlan);
+cloneOrUpdate("https://github.com/openziti/zrok.git", "zrok", branches.zrok);
+cloneOrUpdate("https://bitbucket.org/netfoundry/platform-doc.git", "platform", branches.platform);
+cloneOrUpdate("https://bitbucket.org/netfoundry/nf-data-connector.git", "data-connector", branches.dataConnector);
+cloneOrUpdate("https://bitbucket.org/netfoundry/customer-connect-docs.git", "customer-connect", branches.customerConnect);
+
+// Remove stale Docusaurus caches/outputs left inside cloned remotes.
+console.log("Cleaning stale build artifacts from remotes...");
+for (const remote of (existsSync(remotesDir) ? readdirSync(remotesDir, { withFileTypes: true }) : [])) {
+ if (!remote.isDirectory()) continue;
+ for (const sub of ["docusaurus", "website"]) {
+ for (const artifact of ["build", ".docusaurus"]) {
+ const p = join(remotesDir, remote.name, sub, artifact);
+ if (existsSync(p)) rmSync(p, { recursive: true, force: true });
+ }
+ }
+}
+
+syncVersionedRemote("zrok");
+syncVersionedRemote("openziti");
+
+lintDocs();
+
+if (lintOnly) {
+ console.log("🛑 --lint-only specified. Exiting before build.");
+ process.exit(0);
+}
+
+// --- BUILD OPENZITI SDK REFERENCE DOCS (ziti-doc's gendoc) ---
+const sdkTarget = join(scriptDir, "static", "openziti", "reference", "developer", "sdk");
+console.log(`creating openziti SDK target if necessary at: ${sdkTarget}`);
+mkdirSync(sdkTarget, { recursive: true });
+
+const gendocEnv = { ...process.env, SDK_ROOT_TARGET: sdkTarget };
+const gendocFlags = ["-d", ...otherFlags]; // -d = skip gendoc's own docusaurus build
+if (isWin) {
+ run("pwsh", ["-NoProfile", "-File", join(remotesDir, "openziti", "gendoc.ps1"), ...gendocFlags], {
+ env: gendocEnv, cwd: scriptDir,
+ });
+} else {
+ // Execute gendoc.sh directly so its `#!/bin/bash -eu` shebang applies (as the
+ // former build-docs.sh did); running it via `bash gendoc.sh` would drop -eu.
+ run(join(remotesDir, "openziti", "gendoc.sh"), gendocFlags, {
+ env: gendocEnv, cwd: scriptDir,
+ });
+}
+
+// --- DOCUSAURUS BUILD ---
+run(YARN, ["install"], { cwd: scriptDir });
+
+if (clean) {
+ console.log("CLEAN: clearing Docusaurus cache");
+ run(YARN, ["clear"], { cwd: scriptDir });
+}
+
+const now = new Date().toString();
+const commitRes = capture(GIT, ["-C", scriptDir, "rev-parse", "--short", "HEAD"]);
+const commit = commitRes.status === 0 && commitRes.stdout.trim() ? commitRes.stdout.trim() : "unknown";
+writeFileSync(join(scriptDir, "static", "build-time.txt"), `${now}\n${commit}\n`);
+
+const minifyFlag = process.env.NO_MINIFY ? ["--no-minify"] : [];
+const outDir = `build${qualifier}`;
+
+console.log(line);
+console.log("DOCUSAURUS BUILD");
+console.log(line);
+console.log(` Output dir: ${outDir}`);
+console.log(` Build mask: ${process.env.DOCUSAURUS_BUILD_MASK ?? "0x3FF (config default)"}`);
+console.log(` No-minify: ${process.env.NO_MINIFY ? "true" : "false"}`);
+console.log(line);
+
+run(YARN, ["build", ...minifyFlag, "--out-dir", outDir], { cwd: scriptDir });
diff --git a/unified-doc/build-docs.ps1 b/unified-doc/build-docs.ps1
index 149c9f1b..a01bd247 100644
--- a/unified-doc/build-docs.ps1
+++ b/unified-doc/build-docs.ps1
@@ -1,17 +1,15 @@
#Requires -Version 5.1
<#
.SYNOPSIS
- Builds the unified NetFoundry documentation site.
+ Thin Windows shim for the unified NetFoundry documentation build.
.DESCRIPTION
- Clones (or updates) all product doc repos into _remotes/, runs lint checks,
- builds SDK reference docs via ziti-doc's gendoc.sh, then runs a Docusaurus
- production build.
+ All build logic lives in build-docs.mjs, the single cross-platform source of
+ truth (shared with build-docs.sh). This script only maps its PowerShell-style
+ parameters onto build-docs.mjs flags/env vars so existing Windows callers
+ (e.g. visual-diff.ps1 -SkipLinkedDoc) keep working unchanged.
- Auth: token env vars are used when set; otherwise falls back to SSH key auth.
- Token env vars: GH_ZITI_CI_REPO_ACCESS_PAT, BB_REPO_TOKEN_ONPREM,
- BB_REPO_TOKEN_FRONTDOOR, BB_REPO_TOKEN_PLATFORM_DOC,
- BB_REPO_TOKEN_DATA_CONNECTOR, BB_USERNAME.
+ Run `node build-docs.mjs --help` for the full option/env-var reference.
.EXAMPLE
.\build-docs.ps1 -ZitiDocBranch my.branch.name
@@ -24,14 +22,15 @@
#>
[CmdletBinding()]
param(
- # Branch to clone for each remote repo
- [string]$ZitiDocBranch = "main",
- [string]$ZrokBranch = "main",
- [string]$FrontdoorBranch = "develop",
- [string]$SelfhostedBranch = "main",
- [string]$ZlanBranch = "main",
- [string]$PlatformBranch = "main",
+ # Branch to clone for each remote repo (only forwarded when explicitly set)
+ [string]$ZitiDocBranch = "main",
+ [string]$ZrokBranch = "main",
+ [string]$FrontdoorBranch = "develop",
+ [string]$SelfhostedBranch = "main",
+ [string]$ZlanBranch = "main",
+ [string]$PlatformBranch = "main",
[string]$DataConnectorBranch = "main",
+ [string]$CustomerConnectBranch = "main",
# Remove all _remotes content and .docusaurus cache before building
[switch]$Clean,
@@ -39,7 +38,7 @@ param(
# Run lint checks only; skip the Docusaurus build
[switch]$LintOnly,
- # Pass -l to gendoc.sh: skip doxygen/wget SDK reference doc generation
+ # Skip doxygen/wget SDK reference doc generation (forwards -l to gendoc)
[switch]$SkipLinkedDoc,
# Pass --no-minify to Docusaurus build
@@ -49,342 +48,38 @@ param(
[string]$Qualifier = "",
# Docusaurus build mask (hex). 0x1=openziti, 0x2=frontdoor, 0x4=selfhosted,
- # 0x8=zrok, 0x10=zlan, 0x20=platform, 0x40=data-connector, 0xFF=all
- [string]$BuildMask = "0xFF"
+ # 0x8=zrok, 0x10=zlan, 0x20=platform, 0x40=data-connector,
+ # 0x80=llm-gateway, 0x100=mcp-gateway, 0x200=customer-connect, 0x3FF=all.
+ # Only forwarded (as $env:DOCUSAURUS_BUILD_MASK) when explicitly set;
+ # otherwise build-docs.mjs lets docusaurus.config.ts default it (0x3FF).
+ [string]$BuildMask = "0x3FF"
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
-$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
-$remotesDir = Join-Path $scriptDir "_remotes"
-
-# ─── HELPERS ──────────────────────────────────────────────────────────────────
-
-function Write-Separator([string]$Title = "") {
- Write-Host "========================================"
- if ($Title) { Write-Host $Title; Write-Host "========================================" }
-}
-
-# Builds an authenticated clone URL. Falls back to $SshUrl when the token var is unset.
-function Get-RepoUrl {
- param(
- [string]$DefaultHttpsUrl,
- [string]$TokenEnvVar,
- [string]$SshUrl,
- # For Bitbucket: username env var (defaults to "x-token-auth")
- [string]$UsernameEnvVar = ""
- )
-
- $token = [System.Environment]::GetEnvironmentVariable($TokenEnvVar)
- if ($token) {
- $user = "x-access-token"
- if ($UsernameEnvVar) {
- $u = [System.Environment]::GetEnvironmentVariable($UsernameEnvVar)
- if ($u) { $user = $u } else { $user = "x-token-auth" }
- }
- $uri = [Uri]$DefaultHttpsUrl
- return "$($uri.Scheme)://${user}:${token}@$($uri.Host)$($uri.AbsolutePath)"
- }
- return $SshUrl
-}
-
-# Clones $Url into _remotes\$Dest at $Branch (depth 1).
-# If the clone fails and an existing repo is present, does fetch + reset instead.
-# On any unrecoverable error, lists available remote branches and exits.
-function Invoke-CloneOrUpdate {
- param(
- [string]$Url,
- [string]$Dest,
- [string]$Branch
- )
-
- $target = Join-Path $remotesDir $Dest
- $redactedUrl = $Url -replace '://[^@]+@', '://[REDACTED]@'
-
- if (Test-Path (Join-Path $target ".git")) {
- Write-Host "Updating '$Dest' @ '$Branch'..."
- git -C $target remote set-url origin $Url 2>&1 | Out-Null
- if ($LASTEXITCODE -ne 0) {
- git -C $target remote add origin $Url 2>&1 | Out-Null
- }
- git -C $target fetch --depth 1 origin $Branch
- if ($LASTEXITCODE -ne 0) {
- Write-Host "ERROR: Branch '$Branch' not found in $redactedUrl"
- Write-Host "Available branches:"
- git ls-remote --heads $Url | ForEach-Object {
- ($_ -split '\s+')[1] -replace 'refs/heads/', ''
- }
- exit 1
- }
- git -C $target reset --hard FETCH_HEAD 2>&1
- if ($LASTEXITCODE -ne 0) {
- Write-Error "Failed to reset '$target' to FETCH_HEAD"
- }
- return
- } elseif (Test-Path $target) {
- Write-Host "ERROR: $target exists but is not a git repo"
- Get-ChildItem $target | Format-List Name
- exit 1
- } else {
- Write-Host "Cloning '$Dest' @ '$Branch'..."
- git clone --single-branch --branch $Branch --depth 1 $Url $target
- if ($LASTEXITCODE -ne 0) {
- Write-Host "ERROR: Clone failed. Available branches in $redactedUrl :"
- git ls-remote --heads $Url | ForEach-Object {
- ($_ -split '\s+')[1] -replace 'refs/heads/', ''
- }
- exit 1
- }
- }
-}
-
-# Exposes a remote's versioned-docs at the unified-doc site root.
-# (Mirror of sync-versioned-remote.sh -- keep both in sync.)
-#
-# Docusaurus's plugin-content-docs hardcodes its lookup to siteDir/_versioned_docs/,
-# so the snapshots have to live at the root. Uses straight copies; symlinks/
-# junctions/hardlinks all caused Docusaurus to resolve files to their
-# underlying _remotes/ path, which broke per-plugin remark pipelines (they
-# path-match against the plugin's declared `path`).
-#
-# Each remote's versioned-docs subtree (_versioned_docs/,
-# _versioned_sidebars/, _versions.json) lives inside the cloned
-# remote, but the subdir name varies (zrok uses 'website', openziti uses
-# 'docusaurus'); this auto-detects.
-function Invoke-SyncVersionedRemote([string]$Remote) {
- $remoteDir = $null
- foreach ($sub in @("website", "docusaurus")) {
- $candidate = Join-Path $remotesDir "$Remote\$sub"
- if (Test-Path $candidate) { $remoteDir = $candidate; break }
- }
- if (-not $remoteDir) {
- Write-Error "Cannot find _remotes\$Remote\{website,docusaurus}"
- return
- }
- Write-Host "Copying versioned docs from $remoteDir..."
-
- $items = @("${Remote}_versioned_docs", "${Remote}_versioned_sidebars", "${Remote}_versions.json")
- foreach ($item in $items) {
- $dest = Join-Path $scriptDir $item
- $src = Join-Path $remoteDir $item
- if (-not (Test-Path $src)) {
- Write-Host " WARN: source missing: $src -- skipping"
- continue
- }
- Remove-Item -Recurse -Force $dest -ErrorAction SilentlyContinue
- Copy-Item -Recurse $src $dest
- Write-Host " copied $item"
- }
-}
-
-function Invoke-LintDocs {
- Write-Host "Starting quality checks..."
-
- $potentialTargets = @(
- (Join-Path $remotesDir "zlan\docusaurus\docs"),
- (Join-Path $remotesDir "frontdoor\docusaurus\docs"),
- (Join-Path $remotesDir "zrok\website\docs"),
- (Join-Path $remotesDir "selfhosted\docusaurus\docs"),
- (Join-Path $remotesDir "openziti\docusaurus\docs"),
- (Join-Path $remotesDir "platform\docusaurus\docs"),
- (Join-Path $remotesDir "data-connector\docusaurus\docs")
- )
- $targets = $potentialTargets | Where-Object { Test-Path $_ }
-
- if (-not $targets) {
- Write-Host "No documentation directories found. Skipping lint."
- return
- }
-
- $valeIni = Join-Path $scriptDir "..\docs-linter\.vale.ini"
- $mdlintJson = Join-Path $scriptDir "..\docs-linter\.markdownlint.json"
-
- $valeOk = $null -ne (Get-Command vale -ErrorAction SilentlyContinue)
- $mdlintOk = $null -ne (Get-Command markdownlint -ErrorAction SilentlyContinue)
-
- $valeErrors = 0; $valeWarnings = 0; $valeSugg = 0; $mdErrors = 0
-
- if ($valeOk) {
- Write-Host "Running Vale..."
- $valeOut = & vale --config $valeIni --no-wrap --no-exit @targets 2>&1
- $valeErrors = ($valeOut | Select-String "\berror\b" | Measure-Object).Count
- $valeWarnings = ($valeOut | Select-String "\bwarning\b" | Measure-Object).Count
- $valeSugg = ($valeOut | Select-String "\bsuggestion\b" | Measure-Object).Count
- if ($valeErrors + $valeWarnings + $valeSugg -gt 0) {
- $valeOut | Write-Host
- }
- }
-
- if ($mdlintOk) {
- Write-Host "Running markdownlint..."
- $mdOut = & markdownlint --config $mdlintJson @targets 2>&1
- $mdErrors = ($mdOut | Measure-Object).Count
- if ($mdErrors -gt 0) { $mdOut | Write-Host }
- }
-
- $total = $valeErrors + $valeWarnings + $valeSugg + $mdErrors
- Write-Host ""
- Write-Host "========================================================"
- Write-Host " QUALITY CHECK SUMMARY"
- Write-Host "========================================================"
- Write-Host " Files: (by directory)"
- $targets | ForEach-Object { Write-Host " $_" }
- Write-Host " Vale Errors: $valeErrors"
- Write-Host " Vale Warnings: $valeWarnings"
- Write-Host " Vale Suggestions: $valeSugg"
- Write-Host " Markdownlint Issues: $mdErrors"
- Write-Host " TOTAL ISSUES: $total"
- Write-Host "========================================================"
-}
-
-# ─── MAIN ─────────────────────────────────────────────────────────────────────
-
-Write-Separator "BUILD CONFIGURATION"
-Write-Host " ZitiDocBranch: $ZitiDocBranch"
-Write-Host " ZrokBranch: $ZrokBranch"
-Write-Host " FrontdoorBranch: $FrontdoorBranch"
-Write-Host " SelfhostedBranch: $SelfhostedBranch"
-Write-Host " ZlanBranch: $ZlanBranch"
-Write-Host " PlatformBranch: $PlatformBranch"
-Write-Host " DataConnectorBranch: $DataConnectorBranch"
-Write-Host " Clean: $Clean"
-Write-Host " LintOnly: $LintOnly"
-Write-Host " SkipLinkedDoc: $SkipLinkedDoc"
-Write-Host " NoMinify: $NoMinify"
-Write-Host " Qualifier: '$Qualifier'"
-Write-Host " BuildMask: $BuildMask"
-Write-Host " IS_VERCEL: $($env:IS_VERCEL)"
-Write-Host " node: $(node --version 2>$null)"
-Write-Host " yarn: $(yarn --version 2>$null)"
-
-if ($Clean) {
- Write-Host "CLEAN: removing _remotes contents (preserving package.json)"
- Get-ChildItem $remotesDir -Exclude "package.json" |
- Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
-}
-
-# ─── RESOLVE AUTHENTICATED URLS ───────────────────────────────────────────────
-
-$urlZitiDoc = Get-RepoUrl `
- -DefaultHttpsUrl "https://github.com/openziti/ziti-doc.git" `
- -TokenEnvVar "GH_ZITI_CI_REPO_ACCESS_PAT" `
- -SshUrl "git@github.com:openziti/ziti-doc.git"
-
-$urlZlan = Get-RepoUrl `
- -DefaultHttpsUrl "https://github.com/netfoundry/zlan.git" `
- -TokenEnvVar "GH_ZITI_CI_REPO_ACCESS_PAT" `
- -SshUrl "git@github.com:netfoundry/zlan.git"
-
-$urlFrontdoor = Get-RepoUrl `
- -DefaultHttpsUrl "https://bitbucket.org/netfoundry/zrok-connector.git" `
- -TokenEnvVar "BB_REPO_TOKEN_FRONTDOOR" `
- -SshUrl "git@bitbucket.org:netfoundry/zrok-connector.git" `
- -UsernameEnvVar "BB_USERNAME"
-
-$urlSelfhosted = Get-RepoUrl `
- -DefaultHttpsUrl "https://bitbucket.org/netfoundry/k8s-on-prem-installations.git" `
- -TokenEnvVar "BB_REPO_TOKEN_ONPREM" `
- -SshUrl "git@bitbucket.org:netfoundry/k8s-on-prem-installations.git" `
- -UsernameEnvVar "BB_USERNAME"
-
-$urlZrok = "https://github.com/openziti/zrok.git" # public; no auth needed
-
-$urlPlatform = Get-RepoUrl `
- -DefaultHttpsUrl "https://bitbucket.org/netfoundry/platform-doc.git" `
- -TokenEnvVar "BB_REPO_TOKEN_PLATFORM_DOC" `
- -SshUrl "git@bitbucket.org:netfoundry/platform-doc.git" `
- -UsernameEnvVar "BB_USERNAME"
-
-$urlDataConnector = Get-RepoUrl `
- -DefaultHttpsUrl "https://bitbucket.org/netfoundry/nf-data-connector.git" `
- -TokenEnvVar "BB_REPO_TOKEN_DATA_CONNECTOR" `
- -SshUrl "git@bitbucket.org:netfoundry/nf-data-connector.git" `
- -UsernameEnvVar "BB_USERNAME"
-
-# ─── CLONE / UPDATE REMOTES ───────────────────────────────────────────────────
-
-Invoke-CloneOrUpdate $urlFrontdoor "frontdoor" $FrontdoorBranch
-Invoke-CloneOrUpdate $urlSelfhosted "selfhosted" $SelfhostedBranch
-Invoke-CloneOrUpdate $urlZitiDoc "openziti" $ZitiDocBranch
-Invoke-CloneOrUpdate $urlZlan "zlan" $ZlanBranch
-Invoke-CloneOrUpdate $urlZrok "zrok" $ZrokBranch
-Invoke-CloneOrUpdate $urlPlatform "platform" $PlatformBranch
-Invoke-CloneOrUpdate $urlDataConnector "data-connector" $DataConnectorBranch
-
-# Remove stale Docusaurus caches and build outputs from inside the cloned remotes.
-# A leftover .docusaurus/ or build/ from a prior run can confuse the unified-doc build.
-Get-ChildItem $remotesDir -Recurse -Directory -ErrorAction SilentlyContinue |
- Where-Object { $_.Name -in 'build', '.docusaurus' } |
- Where-Object { $_.FullName -match '(\\docusaurus|\\website)(\\build|\\\.docusaurus)$' } |
- ForEach-Object {
- Write-Host "Removing stale artifact: $($_.FullName)"
- Remove-Item $_.FullName -Recurse -Force
- }
-
-# ─── SYNC VERSIONED DOCS ──────────────────────────────────────────────────────
-
-Invoke-SyncVersionedRemote "zrok"
-Invoke-SyncVersionedRemote "openziti"
-
-# ─── LINT ─────────────────────────────────────────────────────────────────────
-
-Invoke-LintDocs
-
-if ($LintOnly) {
- Write-Host "-LintOnly specified. Exiting before build."
- exit 0
-}
-
-# ─── SDK REFERENCE DOCS (ziti-doc's gendoc.sh) ────────────────────────────────
-# gendoc.sh is always called with -d (skip its own Docusaurus build; unified-doc
-# handles that). Pass -l as well when -SkipLinkedDoc is set (skips doxygen/wget).
-
-$sdkTarget = Join-Path $scriptDir "static\openziti\reference\developer\sdk"
-New-Item -ItemType Directory -Force -Path $sdkTarget | Out-Null
-$env:SDK_ROOT_TARGET = $sdkTarget
-
-$gendocScript = Join-Path $remotesDir "openziti\gendoc.ps1"
-$gendocFlags = @("-d")
-if ($SkipLinkedDoc) { $gendocFlags += "-l" }
-
-Write-Host "Running gendoc.ps1 $($gendocFlags -join ' ')..."
-& pwsh -NoProfile -File $gendocScript @gendocFlags
-if ($LASTEXITCODE -ne 0) {
- Write-Error "gendoc.ps1 failed with exit code $LASTEXITCODE"
-}
-
-# ─── DOCUSAURUS BUILD ─────────────────────────────────────────────────────────
-
-Push-Location $scriptDir
-try {
- & yarn install
- if ($LASTEXITCODE -ne 0) { Write-Error "yarn install failed" }
-
- if ($Clean) {
- Write-Host "CLEAN: clearing Docusaurus cache"
- & yarn clear
- }
-
- # Stamp build time and commit into static/ for diagnostics
- $now = Get-Date -Format "ddd MMM dd HH:mm:ss UTC yyyy" -AsUTC
- $commit = git -C $scriptDir rev-parse --short HEAD 2>$null
- if (-not $commit) { $commit = "unknown" }
- "$now`n$commit" | Set-Content (Join-Path $scriptDir "static\build-time.txt")
-
- $env:DOCUSAURUS_BUILD_MASK = $BuildMask
-
- $outDir = "build$Qualifier"
- $buildArgs = @("build", "--out-dir", $outDir)
- if ($NoMinify) { $buildArgs += "--no-minify" }
-
- Write-Separator "DOCUSAURUS BUILD"
- Write-Host " Output dir: $outDir"
- Write-Host " Build mask: $BuildMask"
- Write-Host " No-minify: $NoMinify"
-
- & yarn @buildArgs
- if ($LASTEXITCODE -ne 0) { Write-Error "yarn build failed" }
-} finally {
- Pop-Location
-}
+$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$mjs = Join-Path $scriptDir "build-docs.mjs"
+
+# Map PowerShell params -> build-docs.mjs flags. Only forward params the caller
+# explicitly passed so build-docs.mjs owns the defaults (single source of truth).
+$mjsArgs = @()
+if ($PSBoundParameters.ContainsKey('ZitiDocBranch')) { $mjsArgs += "--ziti-doc-branch=$ZitiDocBranch" }
+if ($PSBoundParameters.ContainsKey('ZrokBranch')) { $mjsArgs += "--zrok-branch=$ZrokBranch" }
+if ($PSBoundParameters.ContainsKey('FrontdoorBranch')) { $mjsArgs += "--frontdoor-branch=$FrontdoorBranch" }
+if ($PSBoundParameters.ContainsKey('SelfhostedBranch')) { $mjsArgs += "--selfhosted-branch=$SelfhostedBranch" }
+if ($PSBoundParameters.ContainsKey('ZlanBranch')) { $mjsArgs += "--zlan-branch=$ZlanBranch" }
+if ($PSBoundParameters.ContainsKey('PlatformBranch')) { $mjsArgs += "--platform-branch=$PlatformBranch" }
+if ($PSBoundParameters.ContainsKey('DataConnectorBranch')) { $mjsArgs += "--data-connector-branch=$DataConnectorBranch" }
+if ($PSBoundParameters.ContainsKey('CustomerConnectBranch')){ $mjsArgs += "--customer-connect-branch=$CustomerConnectBranch" }
+if ($Clean) { $mjsArgs += "--clean" }
+if ($LintOnly) { $mjsArgs += "--lint-only" }
+if ($SkipLinkedDoc) { $mjsArgs += "-l" }
+if ($Qualifier) { $mjsArgs += "--qualifier=$Qualifier" }
+
+# Env-var controls, matching build-docs.sh semantics.
+if ($NoMinify) { $env:NO_MINIFY = "1" }
+if ($PSBoundParameters.ContainsKey('BuildMask')) { $env:DOCUSAURUS_BUILD_MASK = $BuildMask }
+
+& node $mjs @mjsArgs
+exit $LASTEXITCODE
diff --git a/unified-doc/build-docs.sh b/unified-doc/build-docs.sh
index 80d5b347..24ae1361 100755
--- a/unified-doc/build-docs.sh
+++ b/unified-doc/build-docs.sh
@@ -1,447 +1,13 @@
#!/usr/bin/env bash
# =============================================================================
-# build-docs.sh — Build the unified NetFoundry documentation site.
+# build-docs.sh — thin platform shim.
#
-# Clones (or updates) all product doc repos into _remotes/, runs lint checks,
-# builds SDK reference docs via ziti-doc's gendoc.sh, then runs a Docusaurus
-# production build.
+# All build logic lives in build-docs.mjs, the single cross-platform source of
+# truth (shared with build-docs.ps1). This script only forwards its arguments so
+# existing callers (CI, publish-unified-doc.sh, visual-diff.sh) keep working.
#
-# USAGE
-# build-docs.sh [OPTIONS]
-#
-# OPTIONS
-# --ziti-doc-branch=BRANCH Branch for openziti/ziti-doc (default: main)
-# --zrok-branch=BRANCH Branch for openziti/zrok (default: main)
-# --frontdoor-branch=BRANCH Branch for netfoundry/zrok-connector (default: develop)
-# --selfhosted-branch=BRANCH Branch for netfoundry/k8s-on-prem-installations (default: main)
-# --zlan-branch=BRANCH Branch for netfoundry/zlan (default: main)
-# --platform-branch=BRANCH Branch for netfoundry/platform-doc (default: main)
-# --data-connector-branch=BRANCH Branch for netfoundry/nf-data-connector (default: main)
-# --clean Wipe _remotes and .docusaurus cache before building
-# --lint-only Run lint checks only; skip build
-# --qualifier=VALUE Append VALUE to output dir (e.g. --qualifier=-preview -> build-preview)
-# -l (gendoc) Skip linked doc generation (doxygen/wget)
-# -g (gendoc) Skip git clones inside gendoc
-# -c (gendoc) Skip clean steps inside gendoc
-# -h, --help Show this help and exit
-#
-# ENVIRONMENT VARIABLES
-# GH_ZITI_CI_REPO_ACCESS_PAT GitHub PAT for ziti-doc and zlan (falls back to SSH)
-# BB_REPO_TOKEN_FRONTDOOR Bitbucket token for zrok-connector (falls back to SSH)
-# BB_REPO_TOKEN_ONPREM Bitbucket token for k8s-on-prem-installations (falls back to SSH)
-# BB_REPO_TOKEN_PLATFORM_DOC Bitbucket token for platform-doc (falls back to SSH)
-# BB_REPO_TOKEN_DATA_CONNECTOR Bitbucket token for nf-data-connector (falls back to SSH)
-# BB_USERNAME Bitbucket username (default: x-token-auth)
-# DOCUSAURUS_BUILD_MASK Hex bitmask: 0x1=openziti 0x2=frontdoor 0x4=selfhosted
-# 0x8=zrok 0x10=zlan 0x20=platform
-# 0x40=data-connector 0xFF=all (default: 0xFF)
-# DOCUSAURUS_PUBLISH_ENV Set to 'prod' to use production Algolia index
-# NO_MINIFY Set to any value to pass --no-minify to Docusaurus
-# IS_VERCEL Set to 'true' on Vercel preview deployments
-#
-# EXAMPLES
-# ./build-docs.sh --ziti-doc-branch=my.branch.name
-# DOCUSAURUS_BUILD_MASK=0x1 ./build-docs.sh --ziti-doc-branch=my.branch.name
-# ./build-docs.sh --clean --lint-only
-# ./build-docs.sh --qualifier=-preview -l
+# Run `node build-docs.mjs --help` for the full option/env-var reference.
# =============================================================================
set -euo pipefail
-
-# --- ARGUMENT PARSING ---
-CLEAN=0
-LINT_ONLY=0
-BUILD_QUALIFIER=""
-QUALIFIER_FLAG=()
-OTHER_FLAGS=()
-EXTRA_ARGS=()
-
-BRANCH_ZITI_DOC="main"
-BRANCH_ZROK="main"
-BRANCH_FRONTDOOR="develop"
-BRANCH_SELFHOSTED="main"
-BRANCH_ZLAN="main"
-BRANCH_PLATFORM="main"
-BRANCH_DATA_CONNECTOR="main"
-
-usage() {
- sed -n '/^# USAGE/,/^# =====/{ /^# =====/d; s/^# \{0,1\}//; p }' "$0"
-}
-
-while [[ $# -gt 0 ]]; do
- case $1 in
- --ziti-doc-branch=*) BRANCH_ZITI_DOC="${1#*=}"; shift ;;
- --zrok-branch=*) BRANCH_ZROK="${1#*=}"; shift ;;
- --frontdoor-branch=*) BRANCH_FRONTDOOR="${1#*=}"; shift ;;
- --selfhosted-branch=*) BRANCH_SELFHOSTED="${1#*=}"; shift ;;
- --zlan-branch=*) BRANCH_ZLAN="${1#*=}"; shift ;;
- --platform-branch=*) BRANCH_PLATFORM="${1#*=}"; shift ;;
- --data-connector-branch=*) BRANCH_DATA_CONNECTOR="${1#*=}"; shift ;;
- --ziti-doc-branch) BRANCH_ZITI_DOC="${2:?--ziti-doc-branch requires a value}"; shift 2 ;;
- --zrok-branch) BRANCH_ZROK="${2:?--zrok-branch requires a value}"; shift 2 ;;
- --frontdoor-branch) BRANCH_FRONTDOOR="${2:?--frontdoor-branch requires a value}"; shift 2 ;;
- --selfhosted-branch) BRANCH_SELFHOSTED="${2:?--selfhosted-branch requires a value}"; shift 2 ;;
- --zlan-branch) BRANCH_ZLAN="${2:?--zlan-branch requires a value}"; shift 2 ;;
- --platform-branch) BRANCH_PLATFORM="${2:?--platform-branch requires a value}"; shift 2 ;;
- --data-connector-branch) BRANCH_DATA_CONNECTOR="${2:?--data-connector-branch requires a value}"; shift 2 ;;
- --clean) CLEAN=1; shift ;;
- --lint-only) LINT_ONLY=1; shift ;;
- -h|--help) usage; exit 0 ;;
- --qualifier=*) BUILD_QUALIFIER="${1#*=}"; QUALIFIER_FLAG=("$1"); shift ;;
- --qualifier)
- if [[ -n "${2:-}" && ! "$2" =~ ^- ]]; then
- BUILD_QUALIFIER="$2"; QUALIFIER_FLAG=("--qualifier=$2"); shift 2
- else
- echo "Error: --qualifier requires a value" >&2; exit 1
- fi
- ;;
- -*) OTHER_FLAGS+=("$1"); shift ;;
- *) EXTRA_ARGS+=("$1"); shift ;;
- esac
-done
-
-echo "========================================"
-echo "BUILD CONFIGURATION"
-echo "========================================"
-echo " BRANCH_ZITI_DOC='$BRANCH_ZITI_DOC'"
-echo " BRANCH_ZROK='$BRANCH_ZROK'"
-echo " BRANCH_FRONTDOOR='$BRANCH_FRONTDOOR'"
-echo " BRANCH_SELFHOSTED='$BRANCH_SELFHOSTED'"
-echo " BRANCH_ZLAN='$BRANCH_ZLAN'"
-echo " BRANCH_PLATFORM='$BRANCH_PLATFORM'"
-echo " BRANCH_DATA_CONNECTOR='$BRANCH_DATA_CONNECTOR'"
-echo " CLEAN=$CLEAN"
-echo " IS_VERCEL='${IS_VERCEL:-}'"
-echo " node: $(node --version 2>/dev/null || echo 'not found')"
-echo " yarn: $(yarn --version 2>/dev/null || echo 'not found')"
-echo "========================================"
-
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
-
-# =============================================================================
-# HELPER FUNCTIONS
-# =============================================================================
-
-clone_or_update() {
- local url="$1" dest="$2" branch="${3:-main}"
- local target="$script_dir/_remotes/$dest"
-
-
- # --- AUTHENTICATION LOGIC ---
- case "$url" in
- *zlan*)
- if [ -n "${GH_ZITI_CI_REPO_ACCESS_PAT:-}" ]; then
- url="https://x-access-token:${GH_ZITI_CI_REPO_ACCESS_PAT}@github.com/netfoundry/zlan.git"
- echo "🔑 Using GH_ZITI_CI_REPO_ACCESS_PAT token for zlan" >&2
- else
- url="git@github.com:netfoundry/zlan.git"
- echo "🔑 Using SSH for zlan" >&2
- fi
- ;;
- *k8s-on-prem-installations*)
- if [ -n "${BB_REPO_TOKEN_ONPREM:-}" ]; then
- local bb_user="${BB_USERNAME:-x-token-auth}"
- url="https://${bb_user}:${BB_REPO_TOKEN_ONPREM}@bitbucket.org/netfoundry/k8s-on-prem-installations.git"
- echo "🔑 Using BB_REPO_TOKEN_ONPREM token" >&2
- else
- url="git@bitbucket.org:netfoundry/k8s-on-prem-installations.git"
- echo "🔑 Using SSH for self-hosted" >&2
- fi
- ;;
- *zrok-connector*)
- if [ -n "${BB_REPO_TOKEN_FRONTDOOR:-}" ]; then
- local bb_user="${BB_USERNAME:-x-token-auth}"
- url="https://${bb_user}:${BB_REPO_TOKEN_FRONTDOOR}@bitbucket.org/netfoundry/zrok-connector.git"
- echo "🔑 Using BB_REPO_TOKEN_FRONTDOOR token" >&2
- else
- url="git@bitbucket.org:netfoundry/zrok-connector.git"
- echo "🔑 Using SSH for frontdoor" >&2
- fi
- ;;
- *ziti-doc*)
- if [ -n "${GH_ZITI_CI_REPO_ACCESS_PAT:-}" ]; then
- url="https://x-access-token:${GH_ZITI_CI_REPO_ACCESS_PAT}@github.com/openziti/ziti-doc.git"
- echo "🔑 Using GH_ZITI_CI_REPO_ACCESS_PAT token for ziti-doc" >&2
- else
- url="git@github.com:openziti/ziti-doc.git"
- echo "🔑 Using SSH for ziti-doc" >&2
- fi
- ;;
- *platform-doc*)
- if [ -n "${BB_REPO_TOKEN_PLATFORM_DOC:-}" ]; then
- local bb_user="${BB_USERNAME:-x-token-auth}"
- url="https://${bb_user}:${BB_REPO_TOKEN_PLATFORM_DOC}@bitbucket.org/netfoundry/platform-doc.git"
- echo "🔑 Using BB_REPO_TOKEN_PLATFORM_DOC token" >&2
- else
- url="git@bitbucket.org:netfoundry/platform-doc.git"
- echo "🔑 Using SSH for platform-doc" >&2
- fi
- ;;
- *nf-data-connector*)
- if [ -n "${BB_REPO_TOKEN_DATA_CONNECTOR:-}" ]; then
- local bb_user="${BB_USERNAME:-x-token-auth}"
- url="https://${bb_user}:${BB_REPO_TOKEN_DATA_CONNECTOR}@bitbucket.org/netfoundry/nf-data-connector.git"
- echo "🔑 Using BB_REPO_TOKEN_DATA_CONNECTOR token" >&2
- else
- url="git@bitbucket.org:netfoundry/nf-data-connector.git"
- echo "🔑 Using SSH for nf-data-connector" >&2
- fi
- ;;
- esac
-
- if [ -d "$target/.git" ]; then
- echo "Updating '$dest' @ '$branch'..."
- git -C "$target" remote set-url origin "$url" 2>&1 || git -C "$target" remote add origin "$url" 2>&1 || true
- if ! git -C "$target" fetch --depth 1 origin "$branch" 2>&1 \
- || ! git -C "$target" reset --hard FETCH_HEAD 2>&1; then
- echo "❌ Branch '$branch' not found in ${url//:*@/://[REDACTED]@}"
- echo "👉 Available branches:"
- git ls-remote --heads "$url" | awk '{print $2}' | sed 's|refs/heads/||'
- exit 1
- fi
- elif [ -d "$target" ]; then
- echo "❌ ${target} exists but is not a git repo."
- ls -la "$target" 2>&1 || true
- exit 1
- else
- echo "Cloning '$dest' @ '$branch'..."
- if ! git clone --single-branch --branch "$branch" --depth 1 "$url" "$target" 2>&1; then
- echo "❌ Clone failed. Available branches in ${url//:*@/://[REDACTED]@}:"
- git ls-remote --heads "$url" | awk '{print $2}' | sed 's|refs/heads/||'
- exit 1
- fi
- fi
-}
-
-# Exposes a remote's versioned-docs at the unified-doc site root via straight
-# copies (Docusaurus's plugin-content-docs hardcodes its lookup to
-# siteDir/_versioned_docs/, so the snapshots have to live at the root).
-# Symlinks/junctions/hardlinks all caused Docusaurus to resolve files to their
-# underlying _remotes/ path, which broke per-plugin remark pipelines.
-#
-# Each remote's versioned-docs subtree lives inside the cloned remote, but the
-# subdir varies (zrok uses 'website', openziti uses 'docusaurus'); auto-detect.
-# (Mirror of Invoke-SyncVersionedRemote in build-docs.ps1 -- keep in sync.)
-sync_versioned_remote() {
- local remote="$1"
- local remote_dir=""
- for sub in website docusaurus; do
- if [ -d "$script_dir/_remotes/$remote/$sub" ]; then
- remote_dir="$script_dir/_remotes/$remote/$sub"
- break
- fi
- done
-
- if [ -z "$remote_dir" ]; then
- echo "ERROR: cannot find _remotes/$remote/{website,docusaurus}"
- exit 1
- fi
-
- echo "Copying versioned docs from $remote_dir..."
-
- for item in "${remote}_versioned_docs" "${remote}_versioned_sidebars" "${remote}_versions.json"; do
- local dest="$script_dir/$item"
- local src="$remote_dir/$item"
- if [ ! -e "$src" ]; then
- echo " WARN: source missing: $src -- skipping"
- continue
- fi
- rm -rf "$dest"
- cp -r "$src" "$dest"
- echo " copied $item"
- done
-}
-
-lint_docs() {
- echo "🔍 Starting Quality Checks..."
-
- # 1. DEFINE TARGETS
- POTENTIAL_TARGETS=(
- "${script_dir}/_remotes/zlan/docusaurus/docs"
- "${script_dir}/_remotes/frontdoor/docusaurus/docs"
- "${script_dir}/_remotes/zrok/website/docs"
- "${script_dir}/_remotes/selfhosted/docusaurus/docs"
- "${script_dir}/_remotes/openziti/docusaurus/docs"
- "${script_dir}/_remotes/platform/docusaurus/docs"
- "${script_dir}/_remotes/data-connector/docusaurus/docs"
- )
-
- # 2. VERIFY FOLDERS
- VALID_TARGETS=()
- for target in "${POTENTIAL_TARGETS[@]}"; do
- if [ -d "$target" ]; then
- VALID_TARGETS+=("$target")
- fi
- done
-
- if [ ${#VALID_TARGETS[@]} -eq 0 ]; then
- echo "⚠️ No documentation directories found. Skipping lint."
- return
- fi
-
- # 3. GENERATE CLEAN FILE LIST
- echo "🎯 Gathering file list..."
- LIST_FILE=$(mktemp)
-
- find "${VALID_TARGETS[@]}" -type f \( -name "*.md" -o -name "*.mdx" \) \
- | grep -v "/node_modules/" \
- | grep -v "/docs/_remotes/" \
- | grep -v "/versioned_docs/" \
- | grep -v "/build/" \
- | grep -v "/.git/" \
- | grep -v "/_partials/" \
- | grep -v "/_[^/]*$" \
- > "$LIST_FILE"
-
- FILE_COUNT=$(wc -l < "$LIST_FILE")
- echo "📊 Found $FILE_COUNT files to scan..."
-
- if [ "$FILE_COUNT" -eq 0 ]; then
- echo "⚠️ No files found. Skipping."
- rm "$LIST_FILE"
- return
- fi
-
- # 4. RUN LINTERS & CAPTURE
- VALE_LOG=$(mktemp)
- MDLINT_LOG=$(mktemp)
-
- # --- Run Vale ---
- if command -v vale &> /dev/null; then
- echo "📝 Running Vale..."
- tr '\n' '\0' < "$LIST_FILE" | xargs -0 -r timeout 2m vale \
- --config "${script_dir}/../docs-linter/.vale.ini" \
- --no-wrap \
- --no-exit \
- > "$VALE_LOG" 2>&1
- fi
-
- # --- Run Markdownlint ---
- if command -v markdownlint &> /dev/null; then
- echo "🧹 Running Markdownlint..."
- tr '\n' '\0' < "$LIST_FILE" | xargs -0 -r timeout 2m markdownlint \
- --config "${script_dir}/../docs-linter/.markdownlint.json" \
- > "$MDLINT_LOG" 2>&1 || true
- fi
-
- # 5. FORMAT & CLEAN LOGS
- VALE_CLEAN=$(mktemp)
- sed "s|.*/_remotes/|_remotes/|g" "$VALE_LOG" | sed 's/\x1b\[[0-9;]*m//g' > "$VALE_CLEAN"
-
- MDLINT_CLEAN=$(mktemp)
- sed "s|.*/_remotes/|_remotes/|g" "$MDLINT_LOG" | sed 's/\x1b\[[0-9;]*m//g' | \
- awk -F: '
- $1!=last { if(NR>1)print""; print $1; last=$1 }
- { $1=""; print " " substr($0,2) }
- ' > "$MDLINT_CLEAN"
-
- # 6. CALCULATE STATS
- V_ERR=$(grep -c "error" "$VALE_CLEAN" || true)
- V_WARN=$(grep -c "warning" "$VALE_CLEAN" || true)
- V_SUG=$(grep -c "suggestion" "$VALE_CLEAN" || true)
- MD_ERR=$(grep -c "^ " "$MDLINT_CLEAN" || true)
-
- TOTAL_ISSUES=$((V_ERR + V_WARN + V_SUG + MD_ERR))
-
- # 7. PRINT SUMMARY REPORT
- echo ""
- echo "========================================================"
- echo "📊 QUALITY CHECK SUMMARY"
- echo "========================================================"
- echo " 📄 Files Scanned: $FILE_COUNT"
- echo " 🛑 Vale Errors: $V_ERR"
- echo " ⚠️ Vale Warnings: $V_WARN"
- echo " 💡 Vale Suggestions: $V_SUG"
- echo " 🧹 Markdownlint Issues: $MD_ERR"
- echo "--------------------------------------------------------"
- echo " 🚨 TOTAL ISSUES: $TOTAL_ISSUES"
- echo "========================================================"
- echo ""
-
- if [ "$MD_ERR" -gt 0 ]; then
- echo "################### MARKDOWNLINT REPORT ###################"
- cat "$MDLINT_CLEAN"
- echo ""
- fi
-
- if [ $((V_ERR + V_WARN + V_SUG)) -gt 0 ]; then
- echo "####################### VALE REPORT #######################"
- cat "$VALE_CLEAN"
-
- if [ "$MD_ERR" -gt 0 ]; then
- echo "🛑 BUT WAIT! You also have $MD_ERR Markdownlint errors (see above)."
- fi
- echo ""
- fi
-
- # Cleanup
- rm "$LIST_FILE" "$VALE_LOG" "$MDLINT_LOG" "$VALE_CLEAN" "$MDLINT_CLEAN"
-
- echo "✅ Quality Checks Complete."
-}
-
-# =============================================================================
-# MAIN EXECUTION
-# =============================================================================
-if [ "${CLEAN:-0}" -eq 1 ]; then
- echo "CLEAN: removing _remotes contents (preserving package.json)"
- find "$script_dir/_remotes" -mindepth 1 -maxdepth 1 ! -name 'package.json' -exec rm -rf {} +
-fi
-
-clone_or_update "https://bitbucket.org/netfoundry/zrok-connector.git" frontdoor "$BRANCH_FRONTDOOR"
-clone_or_update "https://bitbucket.org/netfoundry/k8s-on-prem-installations.git" selfhosted "$BRANCH_SELFHOSTED"
-clone_or_update "https://github.com/openziti/ziti-doc.git" openziti "$BRANCH_ZITI_DOC"
-clone_or_update "https://github.com/netfoundry/zlan.git" zlan "$BRANCH_ZLAN"
-clone_or_update "https://github.com/openziti/zrok.git" zrok "$BRANCH_ZROK"
-clone_or_update "https://bitbucket.org/netfoundry/platform-doc.git" platform "$BRANCH_PLATFORM"
-clone_or_update "https://bitbucket.org/netfoundry/nf-data-connector.git" data-connector "$BRANCH_DATA_CONNECTOR"
-
-echo "Cleaning stale build artifacts from remotes..."
-find "$script_dir/_remotes" -type d \( -path "*/docusaurus/build" -o -path "*/docusaurus/.docusaurus" -o -path "*/website/build" -o -path "*/website/.docusaurus" \) -exec rm -rf {} + 2>/dev/null || true
-
-sync_versioned_remote zrok
-sync_versioned_remote openziti
-
-# --- LINT DOCS ---
-lint_docs
-
-if [ "$LINT_ONLY" -eq 1 ]; then
- echo "🛑 --lint-only specified. Exiting before build."
- exit 0
-fi
-
-# --- BUILD OPENZITI SDK REFERENCE DOCS ---
-export SDK_ROOT_TARGET="${script_dir}/static/openziti/reference/developer/sdk"
-echo "creating openziti SDK target if necessary at: ${SDK_ROOT_TARGET}"
-mkdir -p "${SDK_ROOT_TARGET}"
-
-# -d = skip docusaurus build (unified-doc does its own build)
-"${script_dir}/_remotes/openziti/gendoc.sh" -d "${OTHER_FLAGS[@]}"
-
-# --- DOCUSAURUS BUILD ---
-pushd "${script_dir}" >/dev/null
-yarn install
-
-if [ "${CLEAN:-0}" -eq 1 ]; then
- echo "CLEAN: clearing Docusaurus cache"
- yarn clear
-fi
-
-now=$(date)
-commit=$(git -C "${script_dir}" rev-parse --short HEAD 2>/dev/null || echo "unknown")
-printf "%s\n%s\n" "$now" "$commit" > "${script_dir}/static/build-time.txt"
-
-MINIFY_FLAG=""
-if [ -n "${NO_MINIFY:-}" ]; then
- MINIFY_FLAG="--no-minify"
-fi
-
-echo "========================================"
-echo "DOCUSAURUS BUILD"
-echo "========================================"
-echo " Output dir: build${BUILD_QUALIFIER}"
-echo " Build mask: ${DOCUSAURUS_BUILD_MASK:-0xFF}"
-echo " No-minify: ${NO_MINIFY:-false}"
-echo "========================================"
-
-yarn build $MINIFY_FLAG --out-dir "build${BUILD_QUALIFIER}" 2>&1
-popd >/dev/null
+exec node "${script_dir}/build-docs.mjs" "$@"
diff --git a/unified-doc/docs/intro.md b/unified-doc/docs/intro.md
index e2feae23..a295eda0 100644
--- a/unified-doc/docs/intro.md
+++ b/unified-doc/docs/intro.md
@@ -2,4 +2,4 @@
sidebar_position: 1
---
-# Empty Page
\ No newline at end of file
+# Empty page
diff --git a/unified-doc/docs/llm-gateway/api-keys.md b/unified-doc/docs/llm-gateway/api-keys.md
index 690efa6d..60287a29 100644
--- a/unified-doc/docs/llm-gateway/api-keys.md
+++ b/unified-doc/docs/llm-gateway/api-keys.md
@@ -65,7 +65,7 @@ Keys are stored as plaintext in the config file, consistent with how upstream AP
Every incoming request passes through the auth middleware before reaching any handler:
-```
+```text
Client request
|
v
diff --git a/unified-doc/docs/llm-gateway/configuration.md b/unified-doc/docs/llm-gateway/configuration.md
index 3e2c6d7f..d0130b96 100644
--- a/unified-doc/docs/llm-gateway/configuration.md
+++ b/unified-doc/docs/llm-gateway/configuration.md
@@ -165,7 +165,7 @@ llm-gateway run config.yaml
## CLI flags
-```
+```text
llm-gateway run [flags]
Flags:
@@ -181,14 +181,14 @@ CLI flags take precedence over the config file.
When the gateway starts, it:
-1. Loads and parses the YAML config file.
-2. Applies any CLI flag overrides.
-3. Expands environment variables.
-4. Initializes providers (OpenAI, Anthropic, local/self-hosted) in order.
-5. Creates the model-to-provider router.
-6. Initializes OpenTelemetry metrics (if enabled).
-7. Initializes the semantic router (if configured).
-8. Starts the HTTP server (local or via zrok share).
+1. Loads and parses the YAML config file.
+2. Applies any CLI flag overrides.
+3. Expands environment variables.
+4. Initializes providers (OpenAI, Anthropic, local/self-hosted) in order.
+5. Creates the model-to-provider router.
+6. Initializes OpenTelemetry metrics (if enabled).
+7. Initializes the semantic router (if configured).
+8. Starts the HTTP server (local or via zrok share).
On shutdown (SIGINT/SIGTERM), the gateway closes all providers, deletes ephemeral zrok shares, and
releases zrok access objects before exiting.
diff --git a/unified-doc/docs/llm-gateway/connect-zrok.md b/unified-doc/docs/llm-gateway/connect-zrok.md
index 19cf848b..34f4824c 100644
--- a/unified-doc/docs/llm-gateway/connect-zrok.md
+++ b/unified-doc/docs/llm-gateway/connect-zrok.md
@@ -18,7 +18,7 @@ Both use zrok's overlay network built on [OpenZiti](https://openziti.io).
The gateway requires a zrok environment on the host machine. If `zrok enable` hasn't been run, the
gateway fails at startup:
-```
+```text
zrok environment is not enabled; run 'zrok enable' first
```
@@ -33,7 +33,7 @@ to the share token rather than an IP address.
An ephemeral share is created at startup and deleted when the gateway shuts down.
-1. Add the zrok config to `config.yaml`:
+1. Add the zrok config to `config.yaml`:
```yaml
zrok:
@@ -48,13 +48,13 @@ An ephemeral share is created at startup and deleted when the gateway shuts down
llm-gateway run config.yaml --zrok --zrok-mode private
```
-2. Start the gateway. The share token is logged at startup:
+2. Start the gateway. The share token is logged at startup:
- ```
+ ```text
serving via zrok share 'abc123def456'
```
-3. Give clients the share token to connect.
+3. Give clients the share token to connect.
**Public mode** creates a share accessible by anyone with the token. **Private mode** (the default)
requires the client to have a zrok environment enabled and creates an access-controlled connection
diff --git a/unified-doc/docs/llm-gateway/get-started.md b/unified-doc/docs/llm-gateway/get-started.md
index 41ca409f..f48b7fd4 100644
--- a/unified-doc/docs/llm-gateway/get-started.md
+++ b/unified-doc/docs/llm-gateway/get-started.md
@@ -16,19 +16,19 @@ Choose the installation method that fits your environment.
Pre-built binaries are available for Linux, macOS, and Windows:
-1. Visit the [GitHub Releases](https://github.com/openziti/llm-gateway/releases) page.
-2. Download the binary for your platform.
-3. Make it executable:
+1. Visit the [GitHub Releases](https://github.com/openziti/llm-gateway/releases) page.
+2. Download the binary for your platform.
+3. Make it executable:
- ```bash
- chmod +x llm-gateway
- ```
+ ```bash
+ chmod +x llm-gateway
+ ```
-4. Run it:
+4. Run it:
- ```bash
- ./llm-gateway run config.yaml
- ```
+ ```bash
+ ./llm-gateway run config.yaml
+ ```
### Install with Go
@@ -56,26 +56,26 @@ The examples below progress from a simple single-provider proxy to a full produc
### Proxy a local inference server
-1. Start Ollama:
+1. Start Ollama:
```bash
ollama serve
```
-2. Create `config.yaml`:
+2. Create `config.yaml`:
```yaml
local:
base_url: http://localhost:11434
```
-3. Start the gateway:
+3. Start the gateway:
```bash
llm-gateway run config.yaml
```
-4. Send a request:
+4. Send a request:
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
@@ -127,14 +127,14 @@ curl -X POST http://localhost:8080/v1/chat/completions \
### Restrict API access with virtual keys
-1. Generate an API key:
+1. Generate an API key:
```bash
llm-gateway genkey
# sk-gw-a1b2c3d4e5f6...
```
-2. Add `api_keys` to your config, referencing the key and setting per-key model permissions:
+2. Add `api_keys` to your config, referencing the key and setting per-key model permissions:
```yaml
api_keys:
@@ -158,7 +158,7 @@ curl -X POST http://localhost:8080/v1/chat/completions \
base_url: http://localhost:11434
```
-3. Clients send their key in the `Authorization` header:
+3. Clients send their key in the `Authorization` header:
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
diff --git a/unified-doc/docs/llm-gateway/intro.md b/unified-doc/docs/llm-gateway/intro.md
index be10c234..04b785ec 100644
--- a/unified-doc/docs/llm-gateway/intro.md
+++ b/unified-doc/docs/llm-gateway/intro.md
@@ -45,4 +45,3 @@ translated to and from OpenAI format, so existing tools that speak OpenAI work w
Prometheus metrics track request volume, latency, token usage, routing decisions, and endpoint health.
Per-request body logging is available for debugging routing behavior.
-
diff --git a/unified-doc/docs/llm-gateway/providers.md b/unified-doc/docs/llm-gateway/providers.md
index 6cbea189..ba9dd979 100644
--- a/unified-doc/docs/llm-gateway/providers.md
+++ b/unified-doc/docs/llm-gateway/providers.md
@@ -13,7 +13,7 @@ Anthropic, and a local/self-hosted provider for any backend that implements `/v1
All clients interact with the gateway using the [OpenAI chat completions format](https://developers.openai.com/api/reference/chat-completions/overview):
-```
+```text
POST /v1/chat/completions chat completions (streaming and non-streaming)
GET /v1/models list available models from all providers
GET /health health check
diff --git a/unified-doc/docs/llm-gateway/semantic-routing.md b/unified-doc/docs/llm-gateway/semantic-routing.md
index d7c368d7..3efa4a01 100644
--- a/unified-doc/docs/llm-gateway/semantic-routing.md
+++ b/unified-doc/docs/llm-gateway/semantic-routing.md
@@ -15,7 +15,7 @@ falls back to a configured default route.
The router evaluates layers in order and stops at the first confident result:
-```
+```text
Request arrives
|
v
@@ -38,7 +38,7 @@ Request arrives
Each step appends to a **cascade log** visible in the gateway's output:
-```
+```text
semantic routing: method=semantic route='coding' model='claude-haiku-4-5-20251001'
confidence=0.87 latency=12ms cascade=[heuristic:no_match,semantic:coding:0.87]
```
@@ -182,7 +182,7 @@ Three modes control how the embedding layer compares a request against stored ro
### Thresholds
-```
+```text
score >= threshold → confident match, return immediately
ambiguous_threshold <= score < threshold → ambiguous, escalate to classifier
score < ambiguous_threshold → no match, continue to next layer
diff --git a/unified-doc/docs/llm-gateway/streaming.md b/unified-doc/docs/llm-gateway/streaming.md
index 519cd81a..95a51eec 100644
--- a/unified-doc/docs/llm-gateway/streaming.md
+++ b/unified-doc/docs/llm-gateway/streaming.md
@@ -11,12 +11,12 @@ All providers support streaming chat completions via Server-Sent Events (SSE).
When the client sends `"stream": true`, the gateway:
-1. Sends the request to the upstream provider with streaming enabled.
-2. Sets SSE response headers (`Content-Type: text/event-stream`, `Cache-Control: no-cache`,
- `X-Accel-Buffering: no`).
-3. Reads chunks from the provider as they arrive.
-4. Writes each chunk as a `data: {json}\n\n` SSE event and flushes immediately.
-5. Sends `data: [DONE]\n\n` when the stream completes.
+1. Sends the request to the upstream provider with streaming enabled.
+2. Sets SSE response headers (`Content-Type: text/event-stream`, `Cache-Control: no-cache`,
+ `X-Accel-Buffering: no`).
+3. Reads chunks from the provider as they arrive.
+4. Writes each chunk as a `data: {json}\n\n` SSE event and flushes immediately.
+5. Sends `data: [DONE]\n\n` when the stream completes.
## Send a streaming request
@@ -58,7 +58,7 @@ for chunk in stream:
The gateway returns a series of SSE events. Each chunk follows the OpenAI format:
-```
+```text
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":"Quantum"},"index":0}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"delta":{"content":" entanglement"},"index":0}]}
diff --git a/unified-doc/docs/mcp-gateway/intro.md b/unified-doc/docs/mcp-gateway/intro.md
index ef4d93df..98bcf4a5 100644
--- a/unified-doc/docs/mcp-gateway/intro.md
+++ b/unified-doc/docs/mcp-gateway/intro.md
@@ -41,7 +41,7 @@ backend connections — no shared state, no cross-talk between sessions.
## Quick example
-1. Share a local MCP server over the overlay:
+1. Share a local MCP server over the overlay:
```bash
mcp-bridge mcp-filesystem ~/Documents
@@ -51,9 +51,8 @@ backend connections — no shared state, no cross-talk between sessions.
{"share_token":"a1b2c3d4e5f6"}
```
-2. Connect to it from anywhere with a zrok-enabled environment:
+2. Connect to it from anywhere with a zrok-enabled environment:
```bash
mcp-tools run a1b2c3d4e5f6
```
-
diff --git a/unified-doc/docs/mcp-gateway/persistent-shares.md b/unified-doc/docs/mcp-gateway/persistent-shares.md
index 7be183b2..1ce04d05 100644
--- a/unified-doc/docs/mcp-gateway/persistent-shares.md
+++ b/unified-doc/docs/mcp-gateway/persistent-shares.md
@@ -8,7 +8,7 @@ sidebar_label: Persistent shares
By default, share tokens are ephemeral — they disappear when the process exits. For production use,
create persistent shares that survive restarts and keep a stable token.
-1. Create a persistent share by running this once to reserve a named token:
+1. Create a persistent share by running this once to reserve a named token:
```bash
zrok2 create share my-gateway
@@ -17,7 +17,7 @@ create persistent shares that survive restarts and keep a stable token.
Share names must be 3–32 characters, lowercase alphanumeric and hyphens (`[a-z0-9-]`). If you omit
the name, zrok generates a random token.
-2. Reference the token in your config:
+2. Reference the token in your config:
**mcp-gateway** — set `share_token` at the top level:
@@ -35,7 +35,7 @@ create persistent shares that survive restarts and keep a stable token.
mcp-bridge --share-token my-bridge mcp-filesystem ~/Documents
```
-3. When you no longer need the share, delete it:
+3. When you no longer need the share, delete it:
```bash
zrok2 delete share my-gateway
diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts
index 1eb97e36..41b4628e 100644
--- a/unified-doc/docusaurus.config.ts
+++ b/unified-doc/docusaurus.config.ts
@@ -12,20 +12,14 @@ import {
} from "@netfoundry/docusaurus-theme/plugins";
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
import {pluginHotjar, pluginReo} from "@netfoundry/docusaurus-theme/node";
-import {
- consoleLink,
- frontdoorLink,
- selfhostedLink,
- zlanLink,
- openzitiLink,
- zrokLink,
-} from "@netfoundry/docusaurus-theme";
+import {unifiedPickerColumns} from "@netfoundry/docusaurus-theme";
import {PublishConfig} from 'src/components/docusaurus'
import {zrokDocsPluginConfig, zrokRedirects} from "./_remotes/zrok/website/docusaurus-plugin-zrok-docs.ts";
import {onpremRedirects} from "./_remotes/selfhosted/docusaurus/docusaurus-plugin-onprem-docs.ts";
import {platformDocsPluginConfig, platformRedirects} from "./_remotes/platform/docusaurus/docusaurus-plugin-platform-docs.ts";
import {openzitiDocsPluginConfig, openzitiRedirects} from "./_remotes/openziti/docusaurus/docusaurus-plugin-openziti-docs.ts";
import {dataconnectorDocsPluginConfig} from "./_remotes/data-connector/docusaurus/docusaurus-plugin-dataconnector-docs.ts";
+import {customerConnectDocsPluginConfig} from "./_remotes/customer-connect/docusaurus/docusaurus-plugin-customer-connect-docs.ts";
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
const frontdoor = `./_remotes/frontdoor`;
@@ -35,6 +29,7 @@ const zrokRoot = `./_remotes/zrok/website`;
const zlan = `./_remotes/zlan`;
const platform = `./_remotes/platform`;
const dataConnector = `./_remotes/data-connector`;
+const customerConnect = `./_remotes/customer-connect`;
const llmGateway = `./docs/llm-gateway`;
const mcpGateway = `./docs/mcp-gateway`;
@@ -48,7 +43,7 @@ function routeBase(name: string) {
return isVercel ? `docs/${name}` : name;
}
-const buildMask = parseInt(process.env.DOCUSAURUS_BUILD_MASK ?? "0x1FF", 16);
+const buildMask = parseInt(process.env.DOCUSAURUS_BUILD_MASK ?? "0x3FF", 16);
const BUILD_FLAGS = {
NONE: 0x0,
@@ -59,8 +54,9 @@ const BUILD_FLAGS = {
ZLAN: 0x10,
PLATFORM: 0x20,
DATA_CONNECTOR: 0x40,
- LLM_GATEWAY: 0x80,
- MCP_GATEWAY: 0x100,
+ LLM_GATEWAY: 0x80,
+ MCP_GATEWAY: 0x100,
+ CUSTOMER_CONNECT: 0x200,
};
function build(flag: number) {
@@ -121,6 +117,7 @@ const REMARK_MAPPINGS = [
{ from: '@dataconnectordocs', to: `${docsBase}dataconnector`},
{ from: '@llmgatewaydocs', to: `${docsBase}llm-gateway`},
{ from: '@mcpgatewaydocs', to: `${docsBase}mcp-gateway`},
+ { from: '@customerconnectdocs', to: `${docsBase}customer-connect`},
{ from: '@static', to: docsBase},
{ from: '/openziti', to: `${docsBase}${routeBase('openziti')}` },
{ from: '/frontdoor', to: `${docsBase}${routeBase('frontdoor')}` },
@@ -295,6 +292,7 @@ const config: Config = {
'_remotes/openziti/docusaurus/static/',
'_remotes/zlan/docusaurus/static/',
'_remotes/platform/docusaurus/static/',
+ '_remotes/customer-connect/docusaurus/static/',
`${zrokRoot}/static/`,
`${zrokRoot}/docs/images`
],
@@ -353,6 +351,7 @@ const config: Config = {
'@staticdir': path.resolve(__dirname, `docusaurus/static`),
'@platform': path.resolve(__dirname, `${platform}/docusaurus`),
'@dataconnector': path.resolve(__dirname, `${dataConnector}/docusaurus`),
+ '@customerconnectdocs': path.resolve(__dirname, `${customerConnect}/docusaurus`),
},
},
module: {
@@ -376,6 +375,7 @@ const config: Config = {
build(BUILD_FLAGS.ZROK) && ['@docusaurus/plugin-content-pages',{id: `zrok-pages`, path: `${zrokRoot}/src/pages`, routeBasePath: `/${routeBase('zrok')}`}],
build(BUILD_FLAGS.PLATFORM) && ['@docusaurus/plugin-content-pages',{id: `platform-pages`, path: `${platform}/docusaurus/src/pages`, routeBasePath: `/${routeBase('platform')}`}],
build(BUILD_FLAGS.DATA_CONNECTOR) && ['@docusaurus/plugin-content-pages',{id: `dataconnector-pages`, path: `${dataConnector}/docusaurus/src/pages`, routeBasePath: `/${routeBase('dataconnector')}`}],
+ build(BUILD_FLAGS.CUSTOMER_CONNECT) && ['@docusaurus/plugin-content-pages',{id: `customer-connect-pages`, path: `${customerConnect}/docusaurus/src/pages`, routeBasePath: `/${routeBase('customer-connect')}`}],
build(BUILD_FLAGS.ZROK) && extendDocsPlugins(zrokDocsPluginConfig(zrokRoot, REMARK_MAPPINGS, routeBase('zrok'))),
build(BUILD_FLAGS.SELFHOSTED) && [
'@docusaurus/plugin-content-docs',
@@ -441,6 +441,11 @@ const config: Config = {
routeBase('dataconnector'),
),
),
+ build(BUILD_FLAGS.CUSTOMER_CONNECT) && customerConnectDocsPluginConfig(
+ `${customerConnect}/docusaurus`,
+ REMARK_MAPPINGS,
+ routeBase('customer-connect'),
+ ),
build(BUILD_FLAGS.LLM_GATEWAY) && [
'@docusaurus/plugin-content-docs',
{
@@ -502,6 +507,13 @@ const config: Config = {
showNavLink: false,
configuration: {url: `${docsBase}console-api-spec.yaml`, hideClientButton: true, hideTestRequestButton: true, searchHotKey: 'i'},
} as ScalarOptions],
+ build(BUILD_FLAGS.CUSTOMER_CONNECT) && ['@scalar/docusaurus', {
+ id: 'customer-connect-api',
+ label: 'API reference',
+ route: `/${routeBase('customer-connect')}/api-guides/openapi-reference`,
+ showNavLink: false,
+ configuration: {url: `${docsBase}customer-connect-api-spec.yaml`, hideClientButton: true, hideTestRequestButton: true, searchHotKey: 'i'},
+ } as ScalarOptions],
build(BUILD_FLAGS.FRONTDOOR) && ['@scalar/docusaurus', {
id: 'frontdoor-api',
label: 'API reference',
@@ -597,9 +609,7 @@ const config: Config = {
{ href: 'https://openziti.discourse.group/', title: 'Discourse', iconName: 'discourse' },
],
productPickerColumns: [
- { header: 'Cloud SaaS', links: [consoleLink, frontdoorLink] },
- { header: 'Self-Hosted Licensed', links: [selfhostedLink, zlanLink] },
- { header: 'Self-Hosted Open Source', links: [openzitiLink, zrokLink] },
+ ...unifiedPickerColumns,
{ header: 'AI Gateways', links: [
{
label: 'LLM Gateway',
diff --git a/unified-doc/package.json b/unified-doc/package.json
index e7d1c57d..f9116b8c 100644
--- a/unified-doc/package.json
+++ b/unified-doc/package.json
@@ -55,7 +55,7 @@
"@docusaurus/theme-mermaid": "^3.10.1",
"@hotjar/browser": "^1.0.9",
"@mdx-js/react": "^3.0.0",
- "@netfoundry/docusaurus-theme": "^0.16.0",
+ "@netfoundry/docusaurus-theme": "^0.17.0",
"@scalar/docusaurus": "^0.8.13",
"algoliasearch": "^5.36.0",
"asciinema-player": "^3.10.1",
diff --git a/unified-doc/yarn.lock b/unified-doc/yarn.lock
index dfa6a890..34559394 100644
--- a/unified-doc/yarn.lock
+++ b/unified-doc/yarn.lock
@@ -2622,10 +2622,10 @@
"@emnapi/runtime" "^1.5.0"
"@tybys/wasm-util" "^0.10.1"
-"@netfoundry/docusaurus-theme@^0.16.0":
- version "0.16.0"
- resolved "https://registry.yarnpkg.com/@netfoundry/docusaurus-theme/-/docusaurus-theme-0.16.0.tgz#d27e9f4567f75bc4d3f05783fa80973d7602fb61"
- integrity sha512-NC8KCegv5Fqd9hW/U9y2LwRLwhwX1GhGNSRooiUyz/5jRIhGnnhrGYymxJ49d0GI/h04beOSyB+jKbcIPg8ElA==
+"@netfoundry/docusaurus-theme@^0.17.0":
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/@netfoundry/docusaurus-theme/-/docusaurus-theme-0.17.0.tgz#a88df7a8aecb4164b99adeb3c292e4095aca83d6"
+ integrity sha512-vGzh9OYeDfyskdqsX/EpFSE4oTc9+8fO2v9NuRGsvfegv2d+swZWLQ2xl+FIvuoEW2Vr+oMu1ytrjQGyXuKQxw==
dependencies:
"@docsearch/react" "^3"
algoliasearch "^5"
@@ -4599,15 +4599,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001733:
- version "1.0.30001766"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz"
- integrity sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==
-
-caniuse-lite@^1.0.30001782:
- version "1.0.30001792"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz#ca8bb9be244835a335e2018272ce7223691873c5"
- integrity sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001733, caniuse-lite@^1.0.30001782:
+ version "1.0.30001806"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz"
+ integrity sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==
castable-video@~1.1.10:
version "1.1.10"