Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 155 additions & 9 deletions .github/scripts/build_git_over_cdn_eo_esa.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ import yazl from "yazl";
const env = process.env;
const MAIN_SITE_URL = "https://alas.nanoda.work/";
const MAIN_SITE_HOST = "alas.nanoda.work";
const CDN_SITE_URLS = [
"https://ap-update-cdn-cloudflare.3463343.xyz/",
"https://ap-update-cdn-cloudflare-a3.haiteluo.com/",
"https://ap-update-cdn-cloudflare-a1.3463343.xyz/",
"https://ap-update-cdn-cloudflare.nanoda.work/",
"https://ap-update-cdn-cloudflare-a2.3463343.xyz/",
"https://ap-update-cdn-cloudflare-a1.haiteluo.com/",
"https://ap-update-cdn-cloudflare-a2.haiteluo.com/",
"https://ap-update-cdn-cloudflare-a4.haiteluo.com/",
"https://ap-update-cdn-cloudflare-a3.3463343.xyz/",
"https://ap.update.cdn.cloudflare.3463343.xyz/",
];
const DEFAULT_SITE_URL = CDN_SITE_URLS[0];
const SEO_TITLE = "AzurPilot 更新 CDN - 碧蓝航线自动化更新镜像";
const SEO_DESCRIPTION = "AzurPilot 更新 CDN 提供 Git over CDN 静态更新文件、latest.json、更新包状态与最近提交信息,主站为 https://alas.nanoda.work/。";

Expand All @@ -22,14 +35,15 @@ function printHelp() {
--ref <ref> 构建提交或引用,优先级高于 --branch
--history <number> 生成多少个旧提交的更新包,默认 15
--output <path> 输出目录,默认 dist/git-over-cdn
--site-url <url> canonical/sitemap 默认站点 URL,默认 ${DEFAULT_SITE_URL}
--remote <name> 拉取历史时使用的 remote,默认 origin
--no-fetch 跳过 git fetch
--fetch-full 浅克隆时执行 git fetch --unshallow
--help 显示帮助

环境变量:
GOC_BRANCH, GOC_REF, GOC_HISTORY, GOC_OUTPUT, GOC_REMOTE,
GOC_FETCH=0, GOC_FETCH_FULL=1
GOC_SITE_URL, GOC_MIRROR_URLS, GOC_FETCH=0, GOC_FETCH_FULL=1
`);
}

Expand All @@ -42,12 +56,50 @@ function envFirst(...names) {
return "";
}

function normalizeSiteUrl(value) {
const raw = String(value || DEFAULT_SITE_URL).trim() || DEFAULT_SITE_URL;
const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
const url = new URL(withProtocol);
if (!url.pathname.endsWith("/")) {
url.pathname = `${url.pathname}/`;
}
url.search = "";
url.hash = "";
return url.toString();
}

function parseUrlList(value) {
return String(value || "")
.split(/[\s,]+/)
.map((item) => item.trim())
.filter(Boolean);
}

function uniqueUrls(values) {
const urls = [];
const seen = new Set();
for (const value of values) {
const url = normalizeSiteUrl(value);
if (seen.has(url)) {
continue;
}
seen.add(url);
urls.push(url);
}
return urls;
}

function resolveAssetUrl(siteUrl, filename) {
return new URL(filename, siteUrl).toString();
}

function parseArgs(argv) {
const options = {
branch: envFirst("GOC_BRANCH", "CF_PAGES_BRANCH", "BRANCH", "GITHUB_REF_NAME") || "master",
ref: envFirst("GOC_REF", "CF_PAGES_COMMIT_SHA", "COMMIT_SHA", "GITHUB_SHA"),
history: env.GOC_HISTORY || "15",
output: env.GOC_OUTPUT || "dist/git-over-cdn",
siteUrl: normalizeSiteUrl(env.GOC_SITE_URL || DEFAULT_SITE_URL),
remote: env.GOC_REMOTE || "origin",
fetch: env.GOC_FETCH !== "0",
fetchFull: env.GOC_FETCH_FULL === "1",
Expand All @@ -68,6 +120,9 @@ function parseArgs(argv) {
case "--output":
options.output = requireValue(argv, ++i, arg);
break;
case "--site-url":
options.siteUrl = normalizeSiteUrl(requireValue(argv, ++i, arg));
break;
case "--remote":
options.remote = requireValue(argv, ++i, arg);
break;
Expand All @@ -91,6 +146,11 @@ function parseArgs(argv) {
if (!Number.isInteger(options.history) || options.history < 1) {
throw new Error(`--history 必须是正整数:${options.history}`);
}
options.mirrorUrls = uniqueUrls([
options.siteUrl,
...CDN_SITE_URLS,
...parseUrlList(env.GOC_MIRROR_URLS),
Comment on lines +149 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 需要防止 GOC_MIRROR_URLS 中的非法 URL 导致构建崩溃。

uniqueUrls 当前会调用 normalizeSiteUrl,而后者内部执行 new URL(...)。任何在 GOC_MIRROR_URLS 中的非法值都会抛出异常并终止构建。由于这一配置来自用户/环境变量,建议在这里做防御性处理(例如在 uniqueUrls 中用 try/catch 记录日志并跳过非法条目,或者在 parseUrlList 中先做合法性校验再归一化),以避免单个错误 URL 就导致整个构建失败。

Original comment in English

issue (bug_risk): Guard against invalid URLs in GOC_MIRROR_URLS crashing the build.

uniqueUrls currently calls normalizeSiteUrl, which does new URL(...). Any malformed value in GOC_MIRROR_URLS will throw and terminate the build. Since this comes from user/env config, consider handling this defensively (e.g. try/catch in uniqueUrls with logging + skipping invalid entries, or validating in parseUrlList before normalization) so a single bad URL doesn’t fail the entire build.

]);

return options;
}
Expand Down Expand Up @@ -305,6 +365,15 @@ function escapeHtml(value) {
.replaceAll("'", "&#39;");
}

function escapeXml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;");
}

function escapeScriptJson(value) {
return String(value).replaceAll("<", "\\u003c");
}
Expand Down Expand Up @@ -347,27 +416,57 @@ function formatDuration(milliseconds) {
return `${sign}${days}天 ${hours}时 ${minutes}分 ${seconds}秒 ${millis}毫秒`;
}

function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
function writeSitemapXml(outputDir, mirrorUrls, generatedAt) {
const urlEntries = mirrorUrls.map((siteUrl) => ` <url>
<loc>${escapeXml(siteUrl)}</loc>
<lastmod>${escapeXml(generatedAt)}</lastmod>
<changefreq>hourly</changefreq>
<priority>0.7</priority>
</url>`).join("\n");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urlEntries}
</urlset>
`;

fs.writeFileSync(path.join(outputDir, "sitemap.xml"), xml, "utf8");
}

function writeRobotsTxt(outputDir, mirrorUrls) {
const sitemapLines = mirrorUrls.map((siteUrl) => `Sitemap: ${resolveAssetUrl(siteUrl, "sitemap.xml")}`);
const text = [
"User-agent: *",
"Allow: /",
"",
...sitemapLines,
"",
].join("\n");

fs.writeFileSync(path.join(outputDir, "robots.txt"), text, "utf8");
}

function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos, generatedAtTimestamp) {
const latestCommitInfo = commitInfos[0];
if (!latestCommitInfo) {
throw new Error("无法读取最新提交信息");
}

const generatedAtTimestamp = Date.now();
const generatedAt = new Date(generatedAtTimestamp).toISOString();
const commitAge = formatDuration(generatedAtTimestamp - latestCommitInfo.committedAtTimestamp);
const structuredData = escapeScriptJson(JSON.stringify({
"@context": "https://schema.org",
"@type": "WebPage",
name: SEO_TITLE,
description: SEO_DESCRIPTION,
url: options.siteUrl,
isPartOf: {
"@type": "WebSite",
name: "AzurPilot",
url: MAIN_SITE_URL,
},
about: "AzurPilot Git over CDN 更新镜像",
relatedLink: MAIN_SITE_URL,
relatedLink: [MAIN_SITE_URL, ...options.mirrorUrls],
sameAs: options.mirrorUrls,
dateModified: generatedAt,
}, null, 2));
const packRows = oldCommits.map((commit) => {
Expand All @@ -389,6 +488,25 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<td>${escapeHtml(info.authorName)}</td>
<td>${escapeHtml(info.subject)}</td>
</tr>`).join("");
const mirrorRows = options.mirrorUrls.map((siteUrl, index) => {
const url = new URL(siteUrl);
const latestJsonUrl = resolveAssetUrl(siteUrl, "latest.json");
const sitemapUrl = resolveAssetUrl(siteUrl, "sitemap.xml");
return `
<tr>
<td>${index === 0 ? "默认" : index + 1}</td>
<td><a href="${escapeHtml(siteUrl)}">${escapeHtml(url.host)}</a></td>
<td><a href="${escapeHtml(latestJsonUrl)}">latest.json</a></td>
<td><a href="${escapeHtml(sitemapUrl)}">sitemap.xml</a></td>
</tr>`;
}).join("");
const alternateLinks = options.mirrorUrls
.filter((siteUrl) => siteUrl !== options.siteUrl)
.map((siteUrl) => `<link rel="alternate" href="${escapeHtml(siteUrl)}">`)
.join("\n ");
const seeAlsoMeta = [MAIN_SITE_URL, ...options.mirrorUrls.filter((siteUrl) => siteUrl !== options.siteUrl)]
.map((siteUrl) => `<meta property="og:see_also" content="${escapeHtml(siteUrl)}">`)
.join("\n ");

const html = `<!doctype html>
<html lang="zh-CN">
Expand All @@ -398,14 +516,16 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<title>${escapeHtml(SEO_TITLE)}</title>
<meta name="description" content="${escapeHtml(SEO_DESCRIPTION)}">
<meta name="robots" content="index,follow">
<link rel="canonical" href="${escapeHtml(MAIN_SITE_URL)}">
<link rel="canonical" href="${escapeHtml(options.siteUrl)}">
<link rel="home" href="${escapeHtml(MAIN_SITE_URL)}">
<link rel="sitemap" type="application/xml" href="${escapeHtml(resolveAssetUrl(options.siteUrl, "sitemap.xml"))}">
${alternateLinks}
<meta property="og:type" content="website">
<meta property="og:site_name" content="AzurPilot">
<meta property="og:title" content="${escapeHtml(SEO_TITLE)}">
<meta property="og:description" content="${escapeHtml(SEO_DESCRIPTION)}">
<meta property="og:url" content="${escapeHtml(MAIN_SITE_URL)}">
<meta property="og:see_also" content="${escapeHtml(MAIN_SITE_URL)}">
<meta property="og:url" content="${escapeHtml(options.siteUrl)}">
${seeAlsoMeta}
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="${escapeHtml(SEO_TITLE)}">
<meta name="twitter:description" content="${escapeHtml(SEO_DESCRIPTION)}">
Expand Down Expand Up @@ -524,6 +644,8 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<dd><code>${escapeHtml(options.branch)}</code></dd>
<dt>项目主站</dt>
<dd><a href="${escapeHtml(MAIN_SITE_URL)}">${escapeHtml(MAIN_SITE_URL)}</a></dd>
<dt>CDN 首页</dt>
<dd><a href="${escapeHtml(options.siteUrl)}">${escapeHtml(options.siteUrl)}</a></dd>
<dt>更新包数量</dt>
<dd>${oldCommits.length}</dd>
<dt>生成时间</dt>
Expand All @@ -549,9 +671,27 @@ function writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos) {
<dd><span id="commit-age" data-timestamp="${latestCommitInfo.committedAtTimestamp}">${escapeHtml(commitAge)}</span></dd>
<dt>版本接口</dt>
<dd><a href="latest.json">latest.json</a></dd>
<dt>站点地图</dt>
<dd><a href="sitemap.xml">sitemap.xml</a></dd>
</dl>
</section>

<section>
<h2>CDN 镜像</h2>
<table>
<thead>
<tr>
<th>序号</th>
<th>首页</th>
<th>版本接口</th>
<th>站点地图</th>
</tr>
</thead>
<tbody>${mirrorRows}
</tbody>
</table>
</section>

<section>
<h2>更新包</h2>
${oldCommits.length ? `<table>
Expand Down Expand Up @@ -702,14 +842,20 @@ async function main() {
await buildPack(latest, old, latestDir, repoRoot);
}
cleanupPackArtifacts(latestDir);
writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos);
const generatedAtTimestamp = Date.now();
const generatedAt = new Date(generatedAtTimestamp).toISOString();
writeIndexHtml(outputDir, options, latest, oldCommits, commitInfos, generatedAtTimestamp);
writeSitemapXml(outputDir, options.mirrorUrls, generatedAt);
writeRobotsTxt(outputDir, options.mirrorUrls);

console.log("Build git-over-cdn files");
console.log(` branch : ${options.branch}`);
console.log(` ref : ${latest}`);
console.log(` history: ${options.history}`);
console.log(` output : ${path.relative(repoRoot, outputDir).replaceAll(path.sep, "/")}`);
console.log(`Generated index.html, latest.json and ${oldCommits.length} update pack(s)`);
console.log(` site : ${options.siteUrl}`);
console.log(` mirrors: ${options.mirrorUrls.length}`);
console.log(`Generated index.html, robots.txt, sitemap.xml, latest.json and ${oldCommits.length} update pack(s)`);
}

main().catch((error) => {
Expand Down
Loading
Loading