From e43487b9f6aaa7dde51b1d43c1ad6028d3abb2f9 Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi <198173519+veyron-kairo@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:43:21 +0530 Subject: [PATCH 1/2] test(docs): validate the manifest examples in the docs parse Add a node --test check that walks the documentation, takes the fenced yaml blocks that declare both apiVersion and kind (the manifests a reader would apply), and asserts each one parses. Bare snippets and annotated example lists are skipped, so it only holds real manifests to being valid. This caught one broken example: the zh metax scheduling guide wrote the annotations key and its value on a single line, which is invalid YAML. Fix it to the nested form the en page already uses, in both current and the v2.9.0 snapshot. Add js-yaml as a devDependency for the check. Signed-off-by: Shridhar Panigrahi <198173519+veyron-kairo@users.noreply.github.com> --- .../metax-gpu/enable-metax-gpu-schedule.md | 5 +- .../metax-gpu/enable-metax-gpu-schedule.md | 3 +- package-lock.json | 143 +++++++++++++++++- package.json | 1 + test/doc-manifests.test.mjs | 61 ++++++++ 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 test/doc-manifests.test.mjs diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md b/i18n/zh/docusaurus-plugin-content-docs/current/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md index b091341c9..e0f10b91e 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md @@ -51,13 +51,14 @@ apiVersion: v1 kind: Pod metadata: name: gpu-pod1 - annotations: hami.io/node-scheduler-policy: "spread" # 当此参数设置为 spread 时,调度器将尝试为此任务找到最佳拓扑。 + annotations: + hami.io/node-scheduler-policy: "spread" # 当此参数设置为 spread 时,调度器将尝试为此任务找到最佳拓扑。 spec: containers: - name: ubuntu-container image: cr.metax-tech.com/public-ai-release/c500/colossalai:2.24.0.5-py38-ubuntu20.04-amd64 imagePullPolicy: IfNotPresent - command: ["sleep","infinity"] + command: ["sleep", "infinity"] resources: limits: metax-tech.com/gpu: 1 # 请求 1 个 GPU diff --git a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md index 5d9f0b754..46254204e 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md +++ b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/userguide/metax-device/metax-gpu/enable-metax-gpu-schedule.md @@ -51,7 +51,8 @@ apiVersion: v1 kind: Pod metadata: name: gpu-pod1 - annotations: hami.io/node-scheduler-policy: "spread" # 当此参数设置为 spread 时,调度器将尝试为此任务找到最佳拓扑。 + annotations: + hami.io/node-scheduler-policy: "spread" # 当此参数设置为 spread 时,调度器将尝试为此任务找到最佳拓扑。 spec: containers: - name: ubuntu-container diff --git a/package-lock.json b/package-lock.json index db314bcd2..93e783211 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "devDependencies": { "@docusaurus/faster": "^3.10.1", "@swc/core": "^1.15.47", + "js-yaml": "^5.2.3", "markdownlint-cli": "^0.49.0", "prettier": "^3.9.6", "swc-loader": "^0.2.7" @@ -52,6 +53,28 @@ "node": ">=11" } }, + "node_modules/@11ty/gray-matter/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@algolia/abtesting": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.22.0.tgz", @@ -3768,6 +3791,28 @@ "react-dom": "^18.0.0 || ^19.0.0" } }, + "node_modules/@docusaurus/plugin-content-docs/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@docusaurus/plugin-content-pages": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.2.tgz", @@ -4204,6 +4249,50 @@ "node": ">=20.0" } }, + "node_modules/@docusaurus/utils-validation/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@docusaurus/utils/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@easyops-cn/autocomplete.js": { "version": "0.38.1", "resolved": "https://registry.npmjs.org/@easyops-cn/autocomplete.js/-/autocomplete.js-0.38.1.tgz", @@ -8827,6 +8916,28 @@ } } }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -12480,9 +12591,10 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz", + "integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==", + "dev": true, "funding": [ { "type": "github", @@ -12498,7 +12610,7 @@ "argparse": "^2.0.1" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "js-yaml": "bin/js-yaml.mjs" } }, "node_modules/jsesc": { @@ -13226,6 +13338,29 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/markdownlint-cli/node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/markdownlint-cli/node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", diff --git a/package.json b/package.json index 127ca2f5f..158bfd3fe 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "devDependencies": { "@docusaurus/faster": "^3.10.1", "@swc/core": "^1.15.47", + "js-yaml": "^5.2.3", "markdownlint-cli": "^0.49.0", "prettier": "^3.9.6", "swc-loader": "^0.2.7" diff --git a/test/doc-manifests.test.mjs b/test/doc-manifests.test.mjs new file mode 100644 index 000000000..7c863c445 --- /dev/null +++ b/test/doc-manifests.test.mjs @@ -0,0 +1,61 @@ +/** + * Validates the Kubernetes manifest examples embedded in the documentation. + * + * Many pages show YAML the reader is expected to copy and apply. A block is + * treated as a manifest only when it declares both `apiVersion` and `kind`; + * bare snippets (a lone `resources:` block) and annotated example lists (which + * deliberately repeat keys to contrast valid and invalid requests) are not full + * manifests and are skipped. Every manifest that remains must parse as YAML, so + * a broken example can't ship. + * + * Versioned snapshots under versioned_docs/ are frozen and excluded, matching + * the lint:md scope. + */ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; + +import { loadAll } from "js-yaml"; + +const roots = ["docs", "tutorials", "i18n/zh/docusaurus-plugin-content-docs/current"]; + +function markdownFiles(dir, out = []) { + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + if (statSync(p).isDirectory()) markdownFiles(p, out); + else if (entry.endsWith(".md") || entry.endsWith(".mdx")) out.push(p); + } + return out; +} + +function manifestBlocks(markdown) { + const blocks = []; + const fence = /```ya?ml\n([\s\S]*?)```/g; + let match; + while ((match = fence.exec(markdown))) { + const body = match[1]; + if (/^\s*apiVersion:/m.test(body) && /^\s*kind:/m.test(body)) { + blocks.push(body); + } + } + return blocks; +} + +describe("documentation manifest examples", () => { + it("parse as valid YAML", () => { + const failures = []; + for (const root of roots) { + for (const file of markdownFiles(root)) { + manifestBlocks(readFileSync(file, "utf8")).forEach((body, i) => { + try { + loadAll(body); + } catch (err) { + failures.push(`${file} (manifest #${i + 1}): ${err.reason || err.message}`); + } + }); + } + } + assert.deepEqual(failures, [], `\n${failures.join("\n")}`); + }); +}); From 6146d9fde9a52fd6e9eb908244a0a157ef144b4b Mon Sep 17 00:00:00 2001 From: Shridhar Panigrahi <198173519+veyron-kairo@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:08:16 +0530 Subject: [PATCH 2/2] test(docs): walk docs with Dirent to avoid following symlinks Use readdirSync withFileTypes and Dirent.isDirectory/isFile instead of statSync, which follows symlinks and could recurse outside the docs tree. Addresses review feedback. Signed-off-by: Shridhar Panigrahi <198173519+veyron-kairo@users.noreply.github.com> --- test/doc-manifests.test.mjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/doc-manifests.test.mjs b/test/doc-manifests.test.mjs index 7c863c445..893c2dee6 100644 --- a/test/doc-manifests.test.mjs +++ b/test/doc-manifests.test.mjs @@ -13,7 +13,7 @@ */ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { readFileSync, readdirSync, statSync } from "node:fs"; +import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { loadAll } from "js-yaml"; @@ -21,10 +21,12 @@ import { loadAll } from "js-yaml"; const roots = ["docs", "tutorials", "i18n/zh/docusaurus-plugin-content-docs/current"]; function markdownFiles(dir, out = []) { - for (const entry of readdirSync(dir)) { - const p = join(dir, entry); - if (statSync(p).isDirectory()) markdownFiles(p, out); - else if (entry.endsWith(".md") || entry.endsWith(".mdx")) out.push(p); + // withFileTypes avoids following symlinks into directories. + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const p = join(dir, entry.name); + if (entry.isDirectory()) markdownFiles(p, out); + else if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))) + out.push(p); } return out; }