diff --git a/.github/workflows/vale-check.yml b/.github/workflows/vale-check.yml index fb00698..aa2bd20 100644 --- a/.github/workflows/vale-check.yml +++ b/.github/workflows/vale-check.yml @@ -32,6 +32,11 @@ jobs: # Install Markdownlint npm install -g markdownlint-cli markdownlint --version + # Install mdx2vast. Vale shells out to it for every .mdx file; without + # it Vale emits "E100 [lintMDX] Runtime error" per file and moves on, + # so the whole .mdx corpus goes unlinted. + npm install -g mdx2vast + mdx2vast --version - name: Run Quality Check run: | chmod +x docs-linter/lint.sh diff --git a/docs-linter/lint.sh b/docs-linter/lint.sh index 7b8dfa1..dced167 100755 --- a/docs-linter/lint.sh +++ b/docs-linter/lint.sh @@ -16,6 +16,40 @@ if [ ! -d "$TARGET_DIR" ]; then exit 1 fi +# --- TOOLCHECK --- +# Vale shells out to mdx2vast for every .mdx file. When that binary is missing, +# Vale does not fail the run -- it emits one "E100 [lintMDX] Runtime error" per +# file and keeps going, so every .mdx goes unlinted while the summary still +# looks healthy. Report what is actually installed before linting anything. +echo "๐Ÿ”ง Toolcheck..." + +check_tool() { + local name="$1" required="$2" note="$3" + local path version + path=$(command -v "$name" 2>/dev/null) + if [ -z "$path" ]; then + if [ "$required" = "required" ]; then + echo " โŒ $name: NOT FOUND -- $note" + MISSING_REQUIRED=1 + else + echo " โš ๏ธ $name: NOT FOUND -- $note" + fi + return + fi + version=$("$name" --version 2>&1 | head -n1 | tr -d '\r') + echo " โœ… $name: ${version:-unknown} ($path)" +} + +MISSING_REQUIRED=0 +check_tool vale required "install from https://vale.sh (CI pins the version in .github/workflows/vale-check.yml)" +check_tool markdownlint required "npm install -g markdownlint-cli" +check_tool mdx2vast required "npm install -g mdx2vast -- without it every .mdx file is skipped" + +if [ "$MISSING_REQUIRED" -ne 0 ]; then + echo "โŒ Toolchain incomplete. Install the tools above and re-run." + exit 1 +fi + # --- TEMP FILES --- LIST_FILE=$(mktemp) VALE_LOG=$(mktemp) @@ -27,7 +61,7 @@ MD_CLEAN=$(mktemp) echo "๐ŸŽฏ Gathering files from '$TARGET_DIR'..." find "$TARGET_DIR" -type f \( -name "*.md" -o -name "*.mdx" \) \ - | grep -vE "/(node_modules|versioned_docs)/" \ + | grep -vE "/([^/]*_)?(node_modules|versioned_docs)/" \ | grep -v "/_" \ > "$LIST_FILE" @@ -75,8 +109,11 @@ awk -F: ' V_ERR=$(grep -c " error " "$VALE_CLEAN" || true) V_WARN=$(grep -c " warning " "$VALE_CLEAN" || true) V_SUG=$(grep -c " suggestion " "$VALE_CLEAN" || true) +# E100 lines carry no severity token, so they slip past the three greps above. +# Count them separately: each one is a file Vale gave up on, not a clean file. +V_RUN=$(grep -c "^E100 " "$VALE_CLEAN" || true) MD_ERR=$(grep -c "^ " "$MD_CLEAN" || true) -TOTAL=$((V_ERR + V_WARN + V_SUG + MD_ERR)) +TOTAL=$((V_ERR + V_WARN + V_SUG + V_RUN + MD_ERR)) echo -e "\n========================================================" echo "๐Ÿ“Š LINT SUMMARY" @@ -85,6 +122,7 @@ echo " ๐Ÿ“„ Files Scanned: $FILE_COUNT" echo " ๐Ÿ›‘ Vale Errors: $V_ERR" echo " โš ๏ธ Vale Warnings: $V_WARN" echo " ๐Ÿ’ก Vale Suggestions: $V_SUG" +echo " ๐Ÿ’ฅ Vale Runtime Errors: $V_RUN" echo " ๐Ÿงน Markdownlint Issues: $MD_ERR" echo "--------------------------------------------------------" echo " ๐Ÿšจ TOTAL ISSUES: $TOTAL" diff --git a/docs-linter/styles/config/vocabularies/terms/accept.txt b/docs-linter/styles/config/vocabularies/terms/accept.txt index 2503d62..f3089e5 100644 --- a/docs-linter/styles/config/vocabularies/terms/accept.txt +++ b/docs-linter/styles/config/vocabularies/terms/accept.txt @@ -139,6 +139,6 @@ repo Customer Connect Live Audit HAL -GET +(?i)get POST vs \ No newline at end of file diff --git a/packages/docusaurus-theme/src/products.ts b/packages/docusaurus-theme/src/products.ts index f1564d7..867e17d 100644 --- a/packages/docusaurus-theme/src/products.ts +++ b/packages/docusaurus-theme/src/products.ts @@ -15,7 +15,8 @@ export type ProductId = | 'dataconnector' | 'zlan' | 'openziti' - | 'zrok'; + | 'zrok' + | 'ziticni'; export interface Product { id: ProductId; @@ -92,13 +93,20 @@ export const PRODUCTS: Record = { logoDark: `${IMG}/zrok-1.0.0-rocket-green.svg`, description: 'Secure peer-to-peer sharing built on OpenZiti.', }, + ziticni: { + id: 'ziticni', + label: 'ziti-cni', + path: 'ziti-cni/intro', + logo: NF_LOGO, + description: 'Kubernetes CNI plugin for zero-trust pod networking on OpenZiti.', + }, }; /** Visual layout of the picker -- order here = order on screen. */ const PICKER_LAYOUT: { header: string; items: ProductId[] }[] = [ { header: 'Cloud SaaS', items: ['console', 'customerconnect', 'dataconnector', 'frontdoor'] }, { header: 'Self-Hosted Licensed', items: ['selfhosted', 'zlan'] }, - { header: 'Self-Hosted Open Source', items: ['openziti', 'zrok'] }, + { header: 'Self-Hosted Open Source', items: ['openziti', 'zrok', 'ziticni'] }, ]; export interface PickerLink { @@ -146,6 +154,7 @@ export const dataconnectorLink: PickerLink = linkFor('dataconnector', '/docs export const zlanLink: PickerLink = linkFor('zlan', '/docs'); export const openzitiLink: PickerLink = linkFor('openziti', '/docs'); export const zrokLink: PickerLink = linkFor('zrok', '/docs'); +export const ziticniLink: PickerLink = linkFor('ziticni', '/docs'); export const consoleLinkAbs: PickerLink = linkFor('console', DOCS_BASE); export const customerconnectLinkAbs: PickerLink = linkFor('customerconnect', DOCS_BASE); @@ -155,6 +164,7 @@ export const dataconnectorLinkAbs: PickerLink = linkFor('dataconnector', DOC export const zlanLinkAbs: PickerLink = linkFor('zlan', DOCS_BASE); export const openzitiLinkAbs: PickerLink = linkFor('openziti', DOCS_BASE); export const zrokLinkAbs: PickerLink = linkFor('zrok', DOCS_BASE); +export const ziticniLinkAbs: PickerLink = linkFor('ziticni', DOCS_BASE); /** * Picker columns for the unified docs site at netfoundry.io/docs. @@ -166,7 +176,7 @@ export const zrokLinkAbs: PickerLink = linkFor('zrok', DOC export const unifiedPickerColumns: PickerColumn[] = [ { header: 'Cloud SaaS', links: [consoleLink, customerconnectLink, dataconnectorLink, frontdoorLink] }, { header: 'Self-Hosted Licensed', links: [selfhostedLink, zlanLink] }, - { header: 'Self-Hosted Open Source', links: [openzitiLink, zrokLink] }, + { header: 'Self-Hosted Open Source', links: [openzitiLink, zrokLink, ziticniLink] }, ]; /** @@ -176,5 +186,5 @@ export const unifiedPickerColumns: PickerColumn[] = [ export const subsitePickerColumns: PickerColumn[] = [ { header: 'Cloud SaaS', links: [consoleLinkAbs, customerconnectLinkAbs, dataconnectorLinkAbs, frontdoorLinkAbs] }, { header: 'Self-Hosted Licensed', links: [selfhostedLinkAbs, zlanLinkAbs] }, - { header: 'Self-Hosted Open Source', links: [openzitiLinkAbs, zrokLinkAbs] }, + { header: 'Self-Hosted Open Source', links: [openzitiLinkAbs, zrokLinkAbs, ziticniLinkAbs] }, ]; diff --git a/scripts/vercel-ignore.sh b/scripts/vercel-ignore.sh index 6c603c0..fd6072e 100755 --- a/scripts/vercel-ignore.sh +++ b/scripts/vercel-ignore.sh @@ -23,11 +23,14 @@ fi git fetch origin main --depth=100 -if git diff --quiet origin/main...HEAD -- "$WATCH_PATH"; then - echo "No changes under $WATCH_PATH vs origin/main -- skipping build." +# Every project's build also depends on @netfoundry/docusaurus-theme, so a +# change there is relevant regardless of which single path a given project +# was told to watch. +if git diff --quiet origin/main...HEAD -- "$WATCH_PATH" packages/docusaurus-theme; then + echo "No changes under $WATCH_PATH or packages/docusaurus-theme vs origin/main -- skipping build." exit 0 else - echo "Changes detected under $WATCH_PATH vs origin/main -- building." + echo "Changes detected under $WATCH_PATH or packages/docusaurus-theme vs origin/main -- building." exit 1 fi diff --git a/unified-doc/build-docs.mjs b/unified-doc/build-docs.mjs index 60a3d61..95ed2c5 100644 --- a/unified-doc/build-docs.mjs +++ b/unified-doc/build-docs.mjs @@ -22,6 +22,7 @@ // --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) +// --ziti-cni-branch=BRANCH Branch for netfoundry/ziti-cni (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) @@ -31,7 +32,7 @@ // -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) +// GH_ZITI_CI_REPO_ACCESS_PAT GitHub PAT for ziti-doc, zlan, and ziti-cni (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) @@ -41,7 +42,8 @@ // 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) +// 0x200=customer-connect 0x400=ziti-cni +// 0x7FF=all (config default: 0x7FF) // 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 @@ -65,6 +67,7 @@ const GIT = "git"; const YARN = isWin ? "yarn.cmd" : "yarn"; const VALE = "vale"; const MDLINT = isWin ? "markdownlint.cmd" : "markdownlint"; +const MDX2VAST = isWin ? "mdx2vast.cmd" : "mdx2vast"; // On Windows, markdownlint runs through cmd.exe (~8191-char command-line cap), // so lint files in small batches there; larger batches elsewhere. @@ -140,6 +143,7 @@ const branches = { platform : "main", dataConnector : "main", customerConnect: "main", + zitiCni : "lipscomb-cni-docs-draft", }; const BRANCH_FLAG = { "--ziti-doc-branch": "zitiDoc", @@ -150,6 +154,7 @@ const BRANCH_FLAG = { "--platform-branch": "platform", "--data-connector-branch": "dataConnector", "--customer-connect-branch": "customerConnect", + "--ziti-cni-branch": "zitiCni", }; let clean = false; @@ -202,6 +207,7 @@ 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(` BRANCH_ZITI_CNI='${branches.zitiCni}'`); console.log(` CLEAN=${clean ? 1 : 0}`); console.log(` IS_VERCEL='${process.env.IS_VERCEL ?? ""}'`); console.log(` node: ${process.version}`); @@ -275,6 +281,14 @@ function authUrl(url) { console.error("๐Ÿ”‘ Using SSH for customer-connect-docs"); return "git@bitbucket.org:netfoundry/customer-connect-docs.git"; } + if (url.includes("ziti-cni")) { + if (process.env.GH_ZITI_CI_REPO_ACCESS_PAT) { + console.error("๐Ÿ”‘ Using GH_ZITI_CI_REPO_ACCESS_PAT token for ziti-cni"); + return gh("netfoundry/ziti-cni.git", process.env.GH_ZITI_CI_REPO_ACCESS_PAT); + } + console.error("๐Ÿ”‘ Using SSH for ziti-cni"); + return "git@github.com:netfoundry/ziti-cni.git"; + } return url; // public (e.g. openziti/zrok) โ€” no auth needed } @@ -401,8 +415,31 @@ function cleanLog(s) { .join("\n"); } +// Report the resolved version of each linting tool. Vale shells out to mdx2vast +// for .mdx input; when that binary is missing Vale keeps going and emits one +// "E100 [lintMDX] Runtime error" per file, so the .mdx corpus goes unlinted +// without anything in the summary saying so. +function toolcheck() { + console.log("๐Ÿ”ง Toolcheck..."); + const tools = [ + [VALE, "install from https://vale.sh"], + [MDLINT, "npm install -g markdownlint-cli"], + [MDX2VAST, "npm install -g mdx2vast -- without it every .mdx file is skipped"], + ]; + for (const [cmd, note] of tools) { + const r = capture(cmd, ["--version"]); + if (r.error || r.status !== 0) { + console.log(` โš ๏ธ ${cmd}: NOT FOUND -- ${note}`); + continue; + } + const version = (r.stdout || "").split(/\r?\n/)[0].trim(); + console.log(` โœ… ${cmd}: ${version || "unknown"}`); + } +} + function lintDocs() { console.log("๐Ÿ” Starting Quality Checks..."); + toolcheck(); const potentialTargets = [ join(remotesDir, "zlan", "docusaurus", "docs"), @@ -413,6 +450,7 @@ function lintDocs() { join(remotesDir, "platform", "docusaurus", "docs"), join(remotesDir, "data-connector", "docusaurus", "docs"), join(remotesDir, "customer-connect", "docusaurus", "docs"), + join(remotesDir, "ziti-cni", "docusaurus", "docs"), ]; const validTargets = potentialTargets.filter((t) => existsSync(t)); @@ -516,6 +554,7 @@ 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); +cloneOrUpdate("https://github.com/netfoundry/ziti-cni.git", "ziti-cni", branches.zitiCni); // Remove stale Docusaurus caches/outputs left inside cloned remotes. console.log("Cleaning stale build artifacts from remotes..."); @@ -578,7 +617,7 @@ 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(` Build mask: ${process.env.DOCUSAURUS_BUILD_MASK ?? "0x7FF (config default)"}`); console.log(` No-minify: ${process.env.NO_MINIFY ? "true" : "false"}`); console.log(line); diff --git a/unified-doc/build-docs.ps1 b/unified-doc/build-docs.ps1 index a01bd24..df34d8c 100644 --- a/unified-doc/build-docs.ps1 +++ b/unified-doc/build-docs.ps1 @@ -31,6 +31,7 @@ param( [string]$PlatformBranch = "main", [string]$DataConnectorBranch = "main", [string]$CustomerConnectBranch = "main", + [string]$ZitiCniBranch = "main", # Remove all _remotes content and .docusaurus cache before building [switch]$Clean, @@ -49,10 +50,11 @@ param( # Docusaurus build mask (hex). 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. + # 0x80=llm-gateway, 0x100=mcp-gateway, 0x200=customer-connect, + # 0x400=ziti-cni, 0x7FF=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" + # otherwise build-docs.mjs lets docusaurus.config.ts default it (0x7FF). + [string]$BuildMask = "0x7FF" ) Set-StrictMode -Version Latest @@ -72,6 +74,7 @@ if ($PSBoundParameters.ContainsKey('ZlanBranch')) { $mjsArgs += "--zla 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 ($PSBoundParameters.ContainsKey('ZitiCniBranch')) { $mjsArgs += "--ziti-cni-branch=$ZitiCniBranch" } if ($Clean) { $mjsArgs += "--clean" } if ($LintOnly) { $mjsArgs += "--lint-only" } if ($SkipLinkedDoc) { $mjsArgs += "-l" } diff --git a/unified-doc/docusaurus.config.ts b/unified-doc/docusaurus.config.ts index 54d637c..f85507b 100644 --- a/unified-doc/docusaurus.config.ts +++ b/unified-doc/docusaurus.config.ts @@ -21,6 +21,7 @@ import {openzitiDocsPluginConfig, openzitiRedirects} from "./_remotes/openziti/d import {redirects} from "./redirects.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"; +import {zitiCniDocsPluginConfig} from "./_remotes/ziti-cni/docusaurus/docusaurus-plugin-ziti-cni-docs.ts"; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) const frontdoor = `./_remotes/frontdoor`; @@ -31,6 +32,7 @@ const zlan = `./_remotes/zlan`; const platform = `./_remotes/platform`; const dataConnector = `./_remotes/data-connector`; const customerConnect = `./_remotes/customer-connect`; +const ziticni = `./_remotes/ziti-cni`; const llmGateway = `./docs/llm-gateway`; const mcpGateway = `./docs/mcp-gateway`; @@ -44,7 +46,7 @@ function routeBase(name: string) { return isVercel ? `docs/${name}` : name; } -const buildMask = parseInt(process.env.DOCUSAURUS_BUILD_MASK ?? "0x3FF", 16); +const buildMask = parseInt(process.env.DOCUSAURUS_BUILD_MASK ?? "0x7FF", 16); const BUILD_FLAGS = { NONE: 0x0, @@ -58,6 +60,7 @@ const BUILD_FLAGS = { LLM_GATEWAY: 0x80, MCP_GATEWAY: 0x100, CUSTOMER_CONNECT: 0x200, + ZITICNI: 0x400, }; function build(flag: number) { @@ -119,6 +122,7 @@ const REMARK_MAPPINGS = [ { from: '@llmgatewaydocs', to: `${docsBase}llm-gateway`}, { from: '@mcpgatewaydocs', to: `${docsBase}mcp-gateway`}, { from: '@customerconnectdocs', to: `${docsBase}customer-connect`}, + { from: '@ziticnidocs', to: `${docsBase}ziti-cni`}, { from: '@static', to: docsBase}, { from: '/openziti', to: `${docsBase}${routeBase('openziti')}` }, { from: '/frontdoor', to: `${docsBase}${routeBase('frontdoor')}` }, @@ -127,6 +131,7 @@ const REMARK_MAPPINGS = [ { from: '/zlan', to: `${docsBase}${routeBase('zlan')}` }, { from: '/platform', to: `${docsBase}${routeBase('platform')}` }, { from: '/dataconnector', to: `${docsBase}${routeBase('dataconnector')}` }, + { from: '/ziti-cni', to: `${docsBase}${routeBase('ziti-cni')}` }, ]; console.log("CANONICAL URL : " + cfg.docusaurus.url); @@ -294,6 +299,7 @@ const config: Config = { '_remotes/zlan/docusaurus/static/', '_remotes/platform/docusaurus/static/', '_remotes/customer-connect/docusaurus/static/', + '_remotes/ziti-cni/docusaurus/static/', `${zrokRoot}/static/`, `${zrokRoot}/docs/images` ], @@ -353,6 +359,7 @@ const config: Config = { '@platform': path.resolve(__dirname, `${platform}/docusaurus`), '@dataconnector': path.resolve(__dirname, `${dataConnector}/docusaurus`), '@customerconnectdocs': path.resolve(__dirname, `${customerConnect}/docusaurus`), + '@ziticni': path.resolve(__dirname, `${ziticni}/docusaurus`), }, }, module: { @@ -377,6 +384,7 @@ const config: Config = { 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.ZITICNI) && ['@docusaurus/plugin-content-pages',{id: `ziticni-pages`, path: `${ziticni}/docusaurus/src/pages`, routeBasePath: `/${routeBase('ziti-cni')}`}], build(BUILD_FLAGS.ZROK) && extendDocsPlugins(zrokDocsPluginConfig(zrokRoot, REMARK_MAPPINGS, routeBase('zrok'))), build(BUILD_FLAGS.SELFHOSTED) && [ '@docusaurus/plugin-content-docs', @@ -447,6 +455,13 @@ const config: Config = { REMARK_MAPPINGS, routeBase('customer-connect'), ), + build(BUILD_FLAGS.ZITICNI) && extendDocsPlugins( + zitiCniDocsPluginConfig( + `${ziticni}/docusaurus`, + REMARK_MAPPINGS, + routeBase('ziti-cni'), + ), + ), build(BUILD_FLAGS.LLM_GATEWAY) && [ '@docusaurus/plugin-content-docs', {