mirror of
https://github.com/XTLS/Xray-docs-next.git
synced 2026-09-29 02:18:06 +03:00
Prettified Code!
This commit is contained in:
@@ -1,94 +1,101 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { execSync } from "node:child_process"
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import crypto from "node:crypto"
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const DOCS_DIR = path.resolve(ROOT, "docs");
|
||||
const OUT_DIR = path.resolve(ROOT, ".vitepress/.generated");
|
||||
const OUT_FILE = path.join(OUT_DIR, "contributors.json");
|
||||
const ROOT = process.cwd()
|
||||
const DOCS_DIR = path.resolve(ROOT, "docs")
|
||||
const OUT_DIR = path.resolve(ROOT, ".vitepress/.generated")
|
||||
const OUT_FILE = path.join(OUT_DIR, "contributors.json")
|
||||
|
||||
function walk(dir) {
|
||||
const out = [];
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name);
|
||||
const st = fs.statSync(p);
|
||||
if (st.isDirectory()) out.push(...walk(p));
|
||||
else if (p.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
const out = []
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name)
|
||||
const st = fs.statSync(p)
|
||||
if (st.isDirectory()) out.push(...walk(p))
|
||||
else if (p.endsWith(".md")) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function md5(s) {
|
||||
return crypto.createHash("md5").update(s.trim().toLowerCase()).digest("hex");
|
||||
return crypto
|
||||
.createHash("md5")
|
||||
.update(s.trim().toLowerCase())
|
||||
.digest("hex")
|
||||
}
|
||||
|
||||
function parseGithubUsernameFromNoreply(email = "") {
|
||||
// 1) 12345+username@users.noreply.github.com
|
||||
// 2) username@users.noreply.github.com
|
||||
const m1 = email.match(/^[^+]+\+([^@]+)@users\.noreply\.github\.com$/i);
|
||||
if (m1?.[1]) return m1[1];
|
||||
const m2 = email.match(/^([^@]+)@users\.noreply\.github\.com$/i);
|
||||
if (m2?.[1]) return m2[1];
|
||||
return null;
|
||||
// 1) 12345+username@users.noreply.github.com
|
||||
// 2) username@users.noreply.github.com
|
||||
const m1 = email.match(/^[^+]+\+([^@]+)@users\.noreply\.github\.com$/i)
|
||||
if (m1?.[1]) return m1[1]
|
||||
const m2 = email.match(/^([^@]+)@users\.noreply\.github\.com$/i)
|
||||
if (m2?.[1]) return m2[1]
|
||||
return null
|
||||
}
|
||||
|
||||
function avatarUrlFor(email = "") {
|
||||
const gh = parseGithubUsernameFromNoreply(email);
|
||||
if (gh) {
|
||||
return `https://unavatar.io/github/${encodeURIComponent(gh)}`;
|
||||
}
|
||||
if (email) {
|
||||
return `https://www.gravatar.com/avatar/${md5(email)}?d=identicon&s=64`;
|
||||
}
|
||||
return `https://www.gravatar.com/avatar/?d=identicon&s=64`;
|
||||
const gh = parseGithubUsernameFromNoreply(email)
|
||||
if (gh) {
|
||||
return `https://unavatar.io/github/${encodeURIComponent(gh)}`
|
||||
}
|
||||
if (email) {
|
||||
return `https://www.gravatar.com/avatar/${md5(email)}?d=identicon&s=64`
|
||||
}
|
||||
return `https://www.gravatar.com/avatar/?d=identicon&s=64`
|
||||
}
|
||||
|
||||
function gitContributors(fileAbsPath) {
|
||||
const rel = path.relative(process.cwd(), fileAbsPath).replaceAll("\\", "/");
|
||||
let raw = "";
|
||||
try {
|
||||
raw = execSync(`git log --follow --format="%aN|%aE" -- "${rel}"`, {
|
||||
encoding: "utf8",
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
const rel = path
|
||||
.relative(process.cwd(), fileAbsPath)
|
||||
.replaceAll("\\", "/")
|
||||
let raw = ""
|
||||
try {
|
||||
raw = execSync(`git log --follow --format="%aN|%aE" -- "${rel}"`, {
|
||||
encoding: "utf8"
|
||||
})
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
raw
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((line) => {
|
||||
const [name, email] = line.split("|");
|
||||
const key = (email || name || "").toLowerCase();
|
||||
if (!map.has(key)) {
|
||||
const github = parseGithubUsernameFromNoreply(email);
|
||||
map.set(key, {
|
||||
name,
|
||||
email,
|
||||
github,
|
||||
avatarUrl: avatarUrlFor(email),
|
||||
commits: 0,
|
||||
});
|
||||
}
|
||||
map.get(key).commits += 1;
|
||||
});
|
||||
const map = new Map()
|
||||
raw
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.forEach((line) => {
|
||||
const [name, email] = line.split("|")
|
||||
const key = (email || name || "").toLowerCase()
|
||||
if (!map.has(key)) {
|
||||
const github = parseGithubUsernameFromNoreply(email)
|
||||
map.set(key, {
|
||||
name,
|
||||
email,
|
||||
github,
|
||||
avatarUrl: avatarUrlFor(email),
|
||||
commits: 0
|
||||
})
|
||||
}
|
||||
map.get(key).commits += 1
|
||||
})
|
||||
|
||||
return [...map.values()].sort((a, b) => b.commits - a.commits);
|
||||
return [...map.values()].sort((a, b) => b.commits - a.commits)
|
||||
}
|
||||
|
||||
const files = walk(DOCS_DIR);
|
||||
const data = {};
|
||||
const files = walk(DOCS_DIR)
|
||||
const data = {}
|
||||
|
||||
for (const abs of files) {
|
||||
const relToDocs = path.relative(DOCS_DIR, abs).replaceAll("\\", "/");
|
||||
let route = "/" + relToDocs.replace(/\.md$/, "");
|
||||
route = route.replace(/\/index$/, "/"); // docs/a/index.md -> /a/
|
||||
data[route] = gitContributors(abs);
|
||||
const relToDocs = path.relative(DOCS_DIR, abs).replaceAll("\\", "/")
|
||||
let route = "/" + relToDocs.replace(/\.md$/, "")
|
||||
route = route.replace(/\/index$/, "/") // docs/a/index.md -> /a/
|
||||
data[route] = gitContributors(abs)
|
||||
}
|
||||
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(data, null, 2), "utf8");
|
||||
console.log(`Generated contributors for ${files.length} pages -> ${OUT_FILE}`);
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true })
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(data, null, 2), "utf8")
|
||||
console.log(
|
||||
`Generated contributors for ${files.length} pages -> ${OUT_FILE}`
|
||||
)
|
||||
|
||||
+79
-79
@@ -1,114 +1,114 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execSync } from "node:child_process";
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { execSync } from "node:child_process"
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const DOCS_DIR = path.resolve(ROOT, "docs");
|
||||
const OUT_DIR = path.resolve(ROOT, ".vitepress/.generated");
|
||||
const OUT_FILE = path.join(OUT_DIR, "i18n-status.json");
|
||||
const ROOT = process.cwd()
|
||||
const DOCS_DIR = path.resolve(ROOT, "docs")
|
||||
const OUT_DIR = path.resolve(ROOT, ".vitepress/.generated")
|
||||
const OUT_FILE = path.join(OUT_DIR, "i18n-status.json")
|
||||
|
||||
const LOCALES = ["en", "ru"];
|
||||
const LOCALES = ["en", "ru"]
|
||||
|
||||
function walk(dir) {
|
||||
const out = [];
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name);
|
||||
const st = fs.statSync(p);
|
||||
if (st.isDirectory()) out.push(...walk(p));
|
||||
else if (p.endsWith(".md")) out.push(p);
|
||||
}
|
||||
return out;
|
||||
const out = []
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const p = path.join(dir, name)
|
||||
const st = fs.statSync(p)
|
||||
if (st.isDirectory()) out.push(...walk(p))
|
||||
else if (p.endsWith(".md")) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function gitLastCommitISO(fileAbsPath) {
|
||||
const rel = path.relative(ROOT, fileAbsPath).replaceAll("\\", "/");
|
||||
try {
|
||||
const iso = execSync(`git log -1 --format=%cI -- "${rel}"`, {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
return iso || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const rel = path.relative(ROOT, fileAbsPath).replaceAll("\\", "/")
|
||||
try {
|
||||
const iso = execSync(`git log -1 --format=%cI -- "${rel}"`, {
|
||||
encoding: "utf8"
|
||||
}).trim()
|
||||
return iso || null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function toRouteFromDocsRel(relToDocs) {
|
||||
let route = "/" + relToDocs.replace(/\.md$/, "");
|
||||
route = route.replace(/\/index$/, "/");
|
||||
return route;
|
||||
let route = "/" + relToDocs.replace(/\.md$/, "")
|
||||
route = route.replace(/\/index$/, "/")
|
||||
return route
|
||||
}
|
||||
|
||||
function ensureEmptyPlaceholder(locale, relNoLocale) {
|
||||
const tAbs = path.join(DOCS_DIR, locale, relNoLocale); // docs/en/xxx.md
|
||||
if (fs.existsSync(tAbs)) return;
|
||||
const tAbs = path.join(DOCS_DIR, locale, relNoLocale) // docs/en/xxx.md
|
||||
if (fs.existsSync(tAbs)) return
|
||||
|
||||
fs.mkdirSync(path.dirname(tAbs), { recursive: true });
|
||||
fs.writeFileSync(tAbs, "", "utf8");
|
||||
fs.mkdirSync(path.dirname(tAbs), { recursive: true })
|
||||
fs.writeFileSync(tAbs, "", "utf8")
|
||||
}
|
||||
|
||||
const zhFiles = walk(DOCS_DIR).filter((p) => {
|
||||
const rel = path.relative(DOCS_DIR, p).replaceAll("\\", "/");
|
||||
return !LOCALES.some((l) => rel.startsWith(l + "/"));
|
||||
});
|
||||
const rel = path.relative(DOCS_DIR, p).replaceAll("\\", "/")
|
||||
return !LOCALES.some((l) => rel.startsWith(l + "/"))
|
||||
})
|
||||
|
||||
const zhByRel = new Map(); // relToDocs -> abs
|
||||
const zhByRel = new Map() // relToDocs -> abs
|
||||
for (const abs of zhFiles) {
|
||||
const relToDocs = path.relative(DOCS_DIR, abs).replaceAll("\\", "/");
|
||||
zhByRel.set(relToDocs, abs);
|
||||
const relToDocs = path.relative(DOCS_DIR, abs).replaceAll("\\", "/")
|
||||
zhByRel.set(relToDocs, abs)
|
||||
}
|
||||
|
||||
const data = {};
|
||||
const data = {}
|
||||
for (const locale of LOCALES) {
|
||||
const localeDir = path.join(DOCS_DIR, locale);
|
||||
if (!fs.existsSync(localeDir)) continue;
|
||||
const localeDir = path.join(DOCS_DIR, locale)
|
||||
if (!fs.existsSync(localeDir)) continue
|
||||
|
||||
const tFiles = walk(localeDir);
|
||||
for (const tAbs of tFiles) {
|
||||
const relToDocs = path.relative(DOCS_DIR, tAbs).replaceAll("\\", "/"); // en/config/log.md
|
||||
const relNoLocale = relToDocs.replace(new RegExp(`^${locale}/`), ""); // config/log.md
|
||||
const zhAbs = zhByRel.get(relNoLocale);
|
||||
const tFiles = walk(localeDir)
|
||||
for (const tAbs of tFiles) {
|
||||
const relToDocs = path.relative(DOCS_DIR, tAbs).replaceAll("\\", "/") // en/config/log.md
|
||||
const relNoLocale = relToDocs.replace(new RegExp(`^${locale}/`), "") // config/log.md
|
||||
const zhAbs = zhByRel.get(relNoLocale)
|
||||
|
||||
const tRoute = toRouteFromDocsRel(relToDocs); // /en/config/log
|
||||
const zhRoute = toRouteFromDocsRel(relNoLocale); // /config/log
|
||||
const tRoute = toRouteFromDocsRel(relToDocs) // /en/config/log
|
||||
const zhRoute = toRouteFromDocsRel(relNoLocale) // /config/log
|
||||
|
||||
const tISO = gitLastCommitISO(tAbs);
|
||||
const zhISO = zhAbs ? gitLastCommitISO(zhAbs) : null;
|
||||
const tISO = gitLastCommitISO(tAbs)
|
||||
const zhISO = zhAbs ? gitLastCommitISO(zhAbs) : null
|
||||
|
||||
const stale =
|
||||
Boolean(zhISO && tISO) &&
|
||||
new Date(tISO).getTime() < new Date(zhISO).getTime();
|
||||
const stale =
|
||||
Boolean(zhISO && tISO) &&
|
||||
new Date(tISO).getTime() < new Date(zhISO).getTime()
|
||||
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: true,
|
||||
stale,
|
||||
tLastUpdated: tISO,
|
||||
zhLastUpdated: zhISO,
|
||||
};
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: true,
|
||||
stale,
|
||||
tLastUpdated: tISO,
|
||||
zhLastUpdated: zhISO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [zhRel, zhAbs] of zhByRel.entries()) {
|
||||
const zhRoute = toRouteFromDocsRel(zhRel);
|
||||
const zhISO = gitLastCommitISO(zhAbs);
|
||||
const zhRoute = toRouteFromDocsRel(zhRel)
|
||||
const zhISO = gitLastCommitISO(zhAbs)
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
const tRoute = "/" + locale + (zhRoute === "/" ? "/" : zhRoute);
|
||||
if (!data[tRoute]) {
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: false,
|
||||
stale: true,
|
||||
tLastUpdated: null,
|
||||
zhLastUpdated: zhISO,
|
||||
};
|
||||
ensureEmptyPlaceholder(locale, zhRel);
|
||||
}
|
||||
for (const locale of LOCALES) {
|
||||
const tRoute = "/" + locale + (zhRoute === "/" ? "/" : zhRoute)
|
||||
if (!data[tRoute]) {
|
||||
data[tRoute] = {
|
||||
locale,
|
||||
zhRoute,
|
||||
translated: false,
|
||||
stale: true,
|
||||
tLastUpdated: null,
|
||||
zhLastUpdated: zhISO
|
||||
}
|
||||
ensureEmptyPlaceholder(locale, zhRel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(data, null, 2), "utf8");
|
||||
console.log(`Generated i18n status -> ${OUT_FILE}`);
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true })
|
||||
fs.writeFileSync(OUT_FILE, JSON.stringify(data, null, 2), "utf8")
|
||||
console.log(`Generated i18n status -> ${OUT_FILE}`)
|
||||
|
||||
Reference in New Issue
Block a user