mirror of
https://github.com/XTLS/Xray-docs-next.git
synced 2026-09-22 22:38:05 +03:00
Auto Cleanup Stale Xray-core Version Badges
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
name: Cleanup Stale Xray-core Version Badges
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: cleanup-version-badges
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ secrets.ACTION_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Resolve latest Xray-core release
|
||||
id: latest
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.ACTION_PERSONAL_TOKEN }}
|
||||
script: |
|
||||
const release = await github.request(
|
||||
"GET /repos/{owner}/{repo}/releases/latest",
|
||||
{
|
||||
"owner": "XTLS",
|
||||
"repo": "Xray-core",
|
||||
"headers": {
|
||||
"accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2026-03-10"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
core.setOutput("tag", release.data.tag_name)
|
||||
core.info(`Latest Xray-core release: ${release.data.tag_name}`)
|
||||
|
||||
- name: Remove stale version badges
|
||||
run: node scripts/cleanup-version-badges.mjs --latest "${{ steps.latest.outputs.tag }}"
|
||||
|
||||
- name: Commit cleaned docs
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No stale version badges to clean."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git add docs
|
||||
git commit -m "chore: clear stale Xray-core badges for ${{ steps.latest.outputs.tag }}"
|
||||
git push
|
||||
@@ -0,0 +1,138 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const ROOT = process.cwd()
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
function readArg(name) {
|
||||
const index = args.indexOf(name)
|
||||
if (index === -1) return null
|
||||
return args[index + 1] ?? null
|
||||
}
|
||||
|
||||
function hasFlag(name) {
|
||||
return args.includes(name)
|
||||
}
|
||||
|
||||
const docsDir = path.resolve(ROOT, readArg("--docs") ?? "docs")
|
||||
const latestTag = readArg("--latest") ?? process.env.XRAY_CORE_LATEST_TAG
|
||||
const dryRun = hasFlag("--dry-run")
|
||||
|
||||
if (!latestTag) {
|
||||
console.error(
|
||||
"Missing latest tag. Use --latest v26.2.26 or set XRAY_CORE_LATEST_TAG."
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const latestVersion = parseVersion(latestTag)
|
||||
if (!latestVersion) {
|
||||
console.error(`Unsupported latest tag format: ${latestTag}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
console.error(`Docs directory does not exist: ${docsDir}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function parseVersion(input) {
|
||||
const value = String(input).trim()
|
||||
if (!/^v\d+(?:\.\d+)+$/i.test(value)) return null
|
||||
return value
|
||||
.replace(/^v/i, "")
|
||||
.split(".")
|
||||
.map((part) => Number.parseInt(part, 10))
|
||||
}
|
||||
|
||||
function compareVersions(left, right) {
|
||||
const maxLen = Math.max(left.length, right.length)
|
||||
for (let i = 0; i < maxLen; i += 1) {
|
||||
const a = left[i] ?? 0
|
||||
const b = right[i] ?? 0
|
||||
if (a > b) return 1
|
||||
if (a < b) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function walk(dir) {
|
||||
const files = []
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const absPath = path.join(dir, name)
|
||||
const stat = fs.statSync(absPath)
|
||||
if (stat.isDirectory()) {
|
||||
files.push(...walk(absPath))
|
||||
continue
|
||||
}
|
||||
if (absPath.endsWith(".md")) files.push(absPath)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
function cleanupLine(line, removedCount) {
|
||||
if (!removedCount) return line
|
||||
|
||||
let next = line
|
||||
next = next.replace(/[ \t]{2,}/g, " ")
|
||||
next = next.replace(/\s+([,.;:!?])/g, "$1")
|
||||
next = next.replace(/[ \t]+$/g, "")
|
||||
if (!next.trim()) return ""
|
||||
return next
|
||||
}
|
||||
|
||||
function shouldRemoveBadge(textValue) {
|
||||
const badgeVersion = parseVersion(textValue)
|
||||
if (!badgeVersion) return false
|
||||
return compareVersions(badgeVersion, latestVersion) < 0
|
||||
}
|
||||
|
||||
function cleanupFile(filePath) {
|
||||
const source = fs.readFileSync(filePath, "utf8")
|
||||
const eol = source.includes("\r\n") ? "\r\n" : "\n"
|
||||
const lines = source.split(/\r?\n/)
|
||||
let removed = 0
|
||||
|
||||
const nextLines = lines.map((line) => {
|
||||
let removedOnLine = 0
|
||||
const nextLine = line.replace(/<badge\b([^>]*?)\/>/gi, (full, attrs) => {
|
||||
const match = attrs.match(/\btext\s*=\s*(['"])(.*?)\1/i)
|
||||
const textValue = match?.[2]?.trim()
|
||||
if (!textValue || !shouldRemoveBadge(textValue)) return full
|
||||
removed += 1
|
||||
removedOnLine += 1
|
||||
return ""
|
||||
})
|
||||
return cleanupLine(nextLine, removedOnLine)
|
||||
})
|
||||
|
||||
if (!removed) {
|
||||
return { changed: false, removed: 0 }
|
||||
}
|
||||
|
||||
const nextSource = nextLines.join(eol)
|
||||
if (!dryRun) {
|
||||
fs.writeFileSync(filePath, nextSource, "utf8")
|
||||
}
|
||||
|
||||
return { changed: nextSource !== source, removed }
|
||||
}
|
||||
|
||||
const files = walk(docsDir)
|
||||
const changedFiles = []
|
||||
let removedBadges = 0
|
||||
|
||||
for (const filePath of files) {
|
||||
const result = cleanupFile(filePath)
|
||||
if (!result.changed) continue
|
||||
removedBadges += result.removed
|
||||
changedFiles.push(path.relative(ROOT, filePath).replaceAll("\\", "/"))
|
||||
}
|
||||
|
||||
console.log(`Latest Xray-core tag: ${latestTag}`)
|
||||
console.log(`Removed stale version badges: ${removedBadges}`)
|
||||
console.log(`Changed files: ${changedFiles.length}`)
|
||||
|
||||
for (const file of changedFiles) {
|
||||
console.log(`- ${file}`)
|
||||
}
|
||||
Reference in New Issue
Block a user