diff --git a/package.json b/package.json index 2d60271e6be2c..686f49974671e 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc", "generatewiki": "node scripts/generateWiki.js", - "generate-lang-map": "node scripts/generate-language-map.js" + "generate-lang-map": "node scripts/generate-language-map.js", + "normalize-frontmatter": "node scripts/normalize-frontmatter.js" + }, "dependencies": { "@ant-design/icons": "^5.4.0", @@ -72,8 +74,11 @@ "baseline-browser-mapping": "^2.9.14", "concurrently": "^9.2.1", "cross-env": "^10.1.0", + "fast-glob": "^3.3.3", "gh-pages": "^6.3.0", "glob": "^11.0.2", + "gray-matter": "^4.0.3", + "js-yaml": "^4.1.1", "typescript": "^4.7.4" }, "browserslist": { diff --git a/scripts/normalize-frontmatter.js b/scripts/normalize-frontmatter.js new file mode 100644 index 0000000000000..dd0502544fad6 --- /dev/null +++ b/scripts/normalize-frontmatter.js @@ -0,0 +1,170 @@ +#!/usr/bin/env node +/* scripts/normalize-frontmatter.js */ +const fs = require("fs"); +const path = require("path"); +const matter = require("gray-matter"); +const fg = require("fast-glob"); +const yaml = require("js-yaml"); +const { execSync } = require("child_process"); + +const ROOT = process.cwd(); +const BASE_URL = "https://wiki.seeedstudio.com"; + +const LANG_RULES = [ + { dir: path.join("sites", "en", "docs"), prefix: "" }, + { dir: path.join("sites", "zh-CN", "docs"), prefix: "/cn" }, + { dir: path.join("sites", "ja", "docs"), prefix: "/ja" }, + { dir: path.join("sites", "es", "docs"), prefix: "/es" }, +]; + +function detectLangPrefix(fileAbsPath) { + const rel = path.relative(ROOT, fileAbsPath).split(path.sep).join("/"); + for (const r of LANG_RULES) { + const dirNorm = r.dir.split(path.sep).join("/"); + if (rel.startsWith(dirNorm + "/") || rel === dirNorm) return r.prefix; + } + return null; +} + +function runGit(cmd) { + try { + return execSync(cmd, { cwd: ROOT, stdio: ["ignore", "pipe", "ignore"] }) + .toString("utf8") + .trim(); + } catch { + return ""; + } +} + +function getGitTimes(fileAbsPath) { + // 最新提交时间 + const latest = runGit(`git log -1 --format=%aI -- "${fileAbsPath}"`); + // 最早提交时间(文件首次出现) + // --follow 对重命名更友好;--diff-filter=A 获取“新增该文件”的提交 + // 若历史中有重命名导致 A 取不到,则 fallback 到 reverse 的第一条 + let first = runGit( + `git log --follow --diff-filter=A --format=%aI -1 -- "${fileAbsPath}"` + ); + + if (!first) { + const all = runGit(`git log --follow --format=%aI --reverse -- "${fileAbsPath}"`); + first = all ? all.split(/\r?\n/)[0].trim() : ""; + } + + return { createdAt: first, updatedAt: latest }; +} + +function toYMD(iso) { + if (!iso || typeof iso !== "string") return ""; + // git 的 %aI 格式是 ISO 8601,前 10 位就是 YYYY-MM-DD + return iso.slice(0, 10); +} + +function normalizeSlug(slug) { + if (!slug || typeof slug !== "string") return ""; + let s = slug.trim(); + if (!s.startsWith("/")) s = "/" + s; + + // ✅ 空白(空格/tab/多空白)=> 下划线 + s = s.replace(/\s+/g, "_"); + + // ✅ 可选:避免出现多个连续下划线 + s = s.replace(/_+/g, "_"); + + return s; +} + +function buildUrl(prefix, slug) { + const p = prefix ? (prefix.startsWith("/") ? prefix : "/" + prefix) : ""; + const s = normalizeSlug(slug); + if (!s) return ""; + let u = `${BASE_URL}${p}${s}`; + if (!u.endsWith("/")) u += "/"; + return u; +} + +function dumpFrontmatter(data) { + // 不排序:尽量保留你原本 key 的相对习惯(js-yaml 默认不保证顺序,但多数情况下保持插入顺序) + // noRefs: 避免出现 &a *a 之类引用 + return yaml.dump(data, { + noRefs: true, + lineWidth: 1000, + }).trimEnd(); +} + +function normalizeFile(fileAbsPath, { checkOnly = false } = {}) { + const raw = fs.readFileSync(fileAbsPath, "utf8"); + const parsed = matter(raw); + + const data = parsed.data || {}; + const content = parsed.content || ""; + + const prefix = detectLangPrefix(fileAbsPath); + // 你给的语言目录里应该都能匹配上;匹配不上就跳过 url 生成 + const slug = normalizeSlug(data.slug); + + const gitTimes = getGitTimes(fileAbsPath); + + // createdAt:优先用已有值(如果你之前手工写过),否则用 git 最早提交时间 + // 如果已有值为空/无效,再补 + if (!data.createdAt && gitTimes.createdAt) data.createdAt = toYMD(gitTimes.createdAt); + // updatedAt:总是更新为 git 最新提交时间(若取不到则不动) + if (gitTimes.updatedAt) data.updatedAt = toYMD(gitTimes.updatedAt); + + // url:只要能识别语言 + slug,就生成/更新 + if (prefix !== null && slug) { + data.url = buildUrl(prefix, slug); + // 同时把规范化后的 slug 写回(可选,但通常是你想要的) + data.slug = slug; + } + + const newFm = dumpFrontmatter(data); + const newRaw = `---\n${newFm}\n---\n${content.replace(/^\n+/, "")}`; + + const changed = newRaw !== raw; + + if (changed) { + if (!checkOnly) fs.writeFileSync(fileAbsPath, newRaw, "utf8"); + } + return { changed, fileAbsPath }; +} + +async function main() { + const args = process.argv.slice(2); + const checkOnly = args.includes("--check"); + + const patterns = [ + "sites/en/docs/**/*.md", + "sites/en/docs/**/*.mdx", + "sites/zh-CN/docs/**/*.md", + "sites/zh-CN/docs/**/*.mdx", + "sites/ja/docs/**/*.md", + "sites/ja/docs/**/*.mdx", + "sites/es/docs/**/*.md", + "sites/es/docs/**/*.mdx", + ]; + + const files = await fg(patterns, { absolute: true, dot: false }); + let changedCount = 0; + + for (const f of files) { + const r = normalizeFile(f, { checkOnly }); + if (r.changed) changedCount++; + } + + if (checkOnly) { + if (changedCount > 0) { + console.log(`[CHECK] ${changedCount} files would be modified.`); + process.exitCode = 1; + } else { + console.log("[CHECK] All good. No changes needed."); + } + } else { + console.log(`[DONE] Updated ${changedCount} files.`); + } +} + +main().catch((e) => { + console.error(e); + process.exit(2); +}); \ No newline at end of file diff --git a/sites/en/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/Real_Time_Subtitle_Recoder_on_Jetson.md b/sites/en/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/Real_Time_Subtitle_Recoder_on_Jetson.md index 131a6b4e6f09d..1db204057d147 100644 --- a/sites/en/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/Real_Time_Subtitle_Recoder_on_Jetson.md +++ b/sites/en/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/Real_Time_Subtitle_Recoder_on_Jetson.md @@ -6,7 +6,7 @@ keywords: - reComputer - Jetson image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Real Time Subtitle Recoder on Nvidia Jetson +slug: /Real_Time_Subtitle_Recoder_on_Nvidia_Jetson last_update: date: 02/23/2024 author: Jiahao diff --git a/sites/en/docs/Seeed_Elderly/Discrete_Product/Grove_Maker_Kit_for_Intel_Joule.md b/sites/en/docs/Seeed_Elderly/Discrete_Product/Grove_Maker_Kit_for_Intel_Joule.md index 91582e952c465..f3dea7c025cf6 100644 --- a/sites/en/docs/Seeed_Elderly/Discrete_Product/Grove_Maker_Kit_for_Intel_Joule.md +++ b/sites/en/docs/Seeed_Elderly/Discrete_Product/Grove_Maker_Kit_for_Intel_Joule.md @@ -4,7 +4,7 @@ title: Grove Maker Kit for Intel Joule keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Grove Maker Kit for Intel Joule +slug: /Grove_Maker_Kit_for_Intel_Joule last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/en/docs/Seeed_Elderly/Discrete_Product/IoT_Fast_Prototyping_Kit_S5D9.md b/sites/en/docs/Seeed_Elderly/Discrete_Product/IoT_Fast_Prototyping_Kit_S5D9.md index f91adac96ab97..bb8cc3b0a8a81 100644 --- a/sites/en/docs/Seeed_Elderly/Discrete_Product/IoT_Fast_Prototyping_Kit_S5D9.md +++ b/sites/en/docs/Seeed_Elderly/Discrete_Product/IoT_Fast_Prototyping_Kit_S5D9.md @@ -4,7 +4,7 @@ title: IoT Fast Prototyping Kit S5D9 keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /IoT_Fast_Prototyping_Kit S5D9 +slug: /IoT_Fast_Prototyping_Kit_S5D9 last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/en/docs/Seeed_Elderly/rePhone/Retro_Phone_Kit.md b/sites/en/docs/Seeed_Elderly/rePhone/Retro_Phone_Kit.md index 85dabb1029722..7b67fe5a7ab5d 100644 --- a/sites/en/docs/Seeed_Elderly/rePhone/Retro_Phone_Kit.md +++ b/sites/en/docs/Seeed_Elderly/rePhone/Retro_Phone_Kit.md @@ -4,7 +4,7 @@ title: Retro Phone Kit keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Retro Phone Kit +slug: /Retro_Phone_Kit last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/es/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/es_Real_Time_Subtitle_Recoder_on_Jetson.md b/sites/es/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/es_Real_Time_Subtitle_Recoder_on_Jetson.md index afa770956aed3..b3532a7b1bd98 100644 --- a/sites/es/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/es_Real_Time_Subtitle_Recoder_on_Jetson.md +++ b/sites/es/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/es_Real_Time_Subtitle_Recoder_on_Jetson.md @@ -6,7 +6,7 @@ keywords: - reComputer - Jetson image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Real Time Subtitle Recoder on Nvidia Jetson +slug: /Real_Time_Subtitle_Recoder_on_Nvidia_Jetson last_update: date: 02/23/2024 author: Jiahao diff --git a/sites/es/docs/Seeed_Elderly/Discrete_Product/es_Grove_Maker_Kit_for_Intel_Joule.md b/sites/es/docs/Seeed_Elderly/Discrete_Product/es_Grove_Maker_Kit_for_Intel_Joule.md index 3e9bdcd6288b0..409fca2cc1988 100644 --- a/sites/es/docs/Seeed_Elderly/Discrete_Product/es_Grove_Maker_Kit_for_Intel_Joule.md +++ b/sites/es/docs/Seeed_Elderly/Discrete_Product/es_Grove_Maker_Kit_for_Intel_Joule.md @@ -4,7 +4,7 @@ title: Grove Maker Kit para Intel Joule keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Grove Maker Kit for Intel Joule +slug: /Grove_Maker_Kit_for_Intel_Joule last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/es/docs/Seeed_Elderly/Discrete_Product/es_IoT_Fast_Prototyping_Kit_S5D9.md b/sites/es/docs/Seeed_Elderly/Discrete_Product/es_IoT_Fast_Prototyping_Kit_S5D9.md index 6af38657b74fd..5eb9b9793810d 100644 --- a/sites/es/docs/Seeed_Elderly/Discrete_Product/es_IoT_Fast_Prototyping_Kit_S5D9.md +++ b/sites/es/docs/Seeed_Elderly/Discrete_Product/es_IoT_Fast_Prototyping_Kit_S5D9.md @@ -4,7 +4,7 @@ title: Kit de Prototipado Rápido IoT S5D9 keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /IoT_Fast_Prototyping_Kit S5D9 +slug: /IoT_Fast_Prototyping_Kit_S5D9 last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/es/docs/Seeed_Elderly/rePhone/es_Retro_Phone_Kit.md b/sites/es/docs/Seeed_Elderly/rePhone/es_Retro_Phone_Kit.md index 8caad55f8e2c0..94fcf86b9e7b7 100644 --- a/sites/es/docs/Seeed_Elderly/rePhone/es_Retro_Phone_Kit.md +++ b/sites/es/docs/Seeed_Elderly/rePhone/es_Retro_Phone_Kit.md @@ -4,7 +4,7 @@ title: Kit de Teléfono Retro keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Retro Phone Kit +slug: /Retro_Phone_Kit last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/ja/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/ja_Real_Time_Subtitle_Recoder_on_Jetson.md b/sites/ja/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/ja_Real_Time_Subtitle_Recoder_on_Jetson.md index f8c9642abdb0f..15614b693219d 100644 --- a/sites/ja/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/ja_Real_Time_Subtitle_Recoder_on_Jetson.md +++ b/sites/ja/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/ja_Real_Time_Subtitle_Recoder_on_Jetson.md @@ -6,7 +6,7 @@ keywords: - reComputer - Jetson image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Real Time Subtitle Recoder on Nvidia Jetson +slug: /Real_Time_Subtitle_Recoder_on_Nvidia_Jetson last_update: date: 02/23/2024 author: Jiahao diff --git a/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_Grove_Maker_Kit_for_Intel_Joule.md b/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_Grove_Maker_Kit_for_Intel_Joule.md index 6273372cefa8a..f336b5b28a93d 100644 --- a/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_Grove_Maker_Kit_for_Intel_Joule.md +++ b/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_Grove_Maker_Kit_for_Intel_Joule.md @@ -4,7 +4,7 @@ title: Intel Joule用Grove Maker Kit keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Grove Maker Kit for Intel Joule +slug: /Grove_Maker_Kit_for_Intel_Joule last_update: date: 05/15/2025 author: shuxu hu diff --git a/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_IoT_Fast_Prototyping_Kit_S5D9.md b/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_IoT_Fast_Prototyping_Kit_S5D9.md index 4be14b8525eec..6bad75f4d42cf 100644 --- a/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_IoT_Fast_Prototyping_Kit_S5D9.md +++ b/sites/ja/docs/Seeed_Elderly/Discrete_Product/ja_IoT_Fast_Prototyping_Kit_S5D9.md @@ -4,7 +4,7 @@ title: IoT高速プロトタイピングキット S5D9 keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /IoT_Fast_Prototyping_Kit S5D9 +slug: /IoT_Fast_Prototyping_Kit_S5D9 last_update: date: 05/15/2025 author: shuxu hu diff --git a/sites/ja/docs/Seeed_Elderly/rePhone/ja_Retro_Phone_Kit.md b/sites/ja/docs/Seeed_Elderly/rePhone/ja_Retro_Phone_Kit.md index 21667ae8245fc..0c1ea505e0148 100644 --- a/sites/ja/docs/Seeed_Elderly/rePhone/ja_Retro_Phone_Kit.md +++ b/sites/ja/docs/Seeed_Elderly/rePhone/ja_Retro_Phone_Kit.md @@ -4,7 +4,7 @@ title: レトロフォンキット keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Retro Phone Kit +slug: /Retro_Phone_Kit last_update: date: 05/15/2025 author: shuxu hu diff --git a/sites/zh-CN/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/cn_Real_Time_Subtitle_Recoder_on_Jetson.md b/sites/zh-CN/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/cn_Real_Time_Subtitle_Recoder_on_Jetson.md index 75b96ff1ced27..0e3fd5b64b39d 100644 --- a/sites/zh-CN/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/cn_Real_Time_Subtitle_Recoder_on_Jetson.md +++ b/sites/zh-CN/docs/Edge/NVIDIA_Jetson/Application/Generative_AI/cn_Real_Time_Subtitle_Recoder_on_Jetson.md @@ -6,7 +6,7 @@ keywords: - reComputer - Jetson image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Real Time Subtitle Recoder on Nvidia Jetson +slug: /Real_Time_Subtitle_Recoder_on_Nvidia_Jetson last_update: date: 02/23/2024 author: Jiahao diff --git a/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_Grove_Maker_Kit_for_Intel_Joule.md b/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_Grove_Maker_Kit_for_Intel_Joule.md index b7a38940a7c78..e21340743ba8f 100644 --- a/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_Grove_Maker_Kit_for_Intel_Joule.md +++ b/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_Grove_Maker_Kit_for_Intel_Joule.md @@ -4,7 +4,7 @@ title: Grove 制造者套件适用于 Intel Joule keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Grove Maker Kit for Intel Joule +slug: /Grove_Maker_Kit_for_Intel_Joule last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_IoT_Fast_Prototyping_Kit_S5D9.md b/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_IoT_Fast_Prototyping_Kit_S5D9.md index f377b87160b87..ac280e15b7744 100644 --- a/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_IoT_Fast_Prototyping_Kit_S5D9.md +++ b/sites/zh-CN/docs/Seeed_Elderly/Discrete_Product/cn_IoT_Fast_Prototyping_Kit_S5D9.md @@ -4,7 +4,7 @@ title: IoT快速原型开发套件S5D9 keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /IoT_Fast_Prototyping_Kit S5D9 +slug: /IoT_Fast_Prototyping_Kit_S5D9 last_update: date: 1/13/2023 author: shuxu hu diff --git a/sites/zh-CN/docs/Seeed_Elderly/rePhone/cn_Retro_Phone_Kit.md b/sites/zh-CN/docs/Seeed_Elderly/rePhone/cn_Retro_Phone_Kit.md index d3379b9442593..540c6fa760332 100644 --- a/sites/zh-CN/docs/Seeed_Elderly/rePhone/cn_Retro_Phone_Kit.md +++ b/sites/zh-CN/docs/Seeed_Elderly/rePhone/cn_Retro_Phone_Kit.md @@ -4,7 +4,7 @@ title: 复古电话套件 keywords: - Seeed_Elderly image: https://files.seeedstudio.com/wiki/wiki-platform/S-tempor.png -slug: /Retro Phone Kit +slug: /Retro_Phone_Kit last_update: date: 1/13/2023 author: shuxu hu diff --git a/yarn.lock b/yarn.lock index 89d27efed5f35..f18296e2b45ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5460,7 +5460,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -6634,7 +6634,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.1.0, js-yaml@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==