feat(opensource): README 四级回落 + 兜底 JSON 8 仓 + 项目可见性告警
- site-content.ts:README 取数加 AbortSignal.timeout(10s)(原无超时, 慢端点可卡整条构建);四级回落矩阵逐级 console.warn 点名原因: 新鲜网络(内容有变才回写缓存)→ token 缺位/HTTP 非 2xx/空/超时/异常 时吃缓存 → 缓存亦缺回落 yaml blurb → 缓存回写失败(构建仓只读)仅告警 - README 图片加固:markdown 层给每个 img 补 loading=lazy + 缺省 alt (幂等;只动这两个属性,因为过 rehype-content-sanitize 白名单后其余 属性会被剥);相对链接不重写,限制写入 JSDoc 与文档 - projects.ts:单源失败点名、双源全失败点名回落 JSON、mode=only 的 include 未命中双向点名(双源无此仓 / 被 perSource 过滤掉) - projects-fallback.json:1 → 8 条真实仓(blog / open-earth-data-guide★58 / AI-CLI / open-earth-data-kit / pi-mesh / pi-board / pi-jobd / ai-code-style-kit,字段对齐 RepoCard) 车道:worker-one(T5);收编复验:E2E 37/37、构建 48 页 0 ERROR、 兜底 JSON 8 条断言过 验证: E2E 37/37 PASS;pnpm astro build 48 页 exit 0;fallback 8 条 Spec: 2026-11-02_blog-5g-pages (T5) Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
59eda420b3
commit
6096a9dafe
3 files changed
+244
-25
No files matched your search
+41
-2
@@ -74,6 +74,13 @@ export interface LoadProjectsOptions {
|
||||
export async function loadProjects(options: LoadProjectsOptions = {}): Promise<RepoCard[]> {
|
||||
const { pinned = PINNED_DEFAULT, mode = 'all', include = [], exclude = [], sort = 'pinned-first', perSource = 'both' } = options;
|
||||
const [gitea, github] = await Promise.allSettled([fetchJson(GITEA_API), fetchJson(GITHUB_API)]);
|
||||
// 单源失败不拦构建,但必须在日志里点名——否则“怪了,这个仓怎么没显示”要排查很久
|
||||
if (gitea.status === 'rejected') {
|
||||
console.warn(`[opensource] projects:gitea 源不可用(${(gitea.reason as Error).message}),仅用 GitHub 源`);
|
||||
}
|
||||
if (github.status === 'rejected') {
|
||||
console.warn(`[opensource] projects:GitHub 源不可用(${(github.reason as Error).message}),仅用 gitea 源`);
|
||||
}
|
||||
const byName = new Map<string, RepoCard>();
|
||||
|
||||
for (const r of gitea.status === 'fulfilled' ? gitea.value : []) {
|
||||
@@ -89,10 +96,42 @@ export async function loadProjects(options: LoadProjectsOptions = {}): Promise<R
|
||||
}
|
||||
|
||||
if (byName.size === 0) {
|
||||
console.warn('[opensource] projects:双源均无可用数据,改用仓内兜底 src/data/projects-fallback.json');
|
||||
const fb = (await import('../data/projects-fallback.json')).default as RepoCard[];
|
||||
return arrange(fb, pinned, mode, include, exclude, sort, perSource);
|
||||
const out = arrange(fb, pinned, mode, include, exclude, sort, perSource);
|
||||
warnMissedIncludes(out, fb, include, mode, perSource);
|
||||
return out;
|
||||
}
|
||||
const merged = [...byName.values()];
|
||||
const out = arrange(merged, pinned, mode, include, exclude, sort, perSource);
|
||||
warnMissedIncludes(out, merged, include, mode, perSource);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* mode=only 时点名 include 里“写了却没显示”的仓名——避开设了一个看不见的黑洞。
|
||||
* 未命中分两类,日志措词区分:①双源结果里根本没有(拼写错 / 私有仓 / 该源刚失败);
|
||||
* ②有,但被 perSource 过滤掉了(配置与仓来源不匹配)。
|
||||
*/
|
||||
function warnMissedIncludes(
|
||||
showing: RepoCard[],
|
||||
pool: RepoCard[],
|
||||
include: string[],
|
||||
mode: 'all' | 'only' | 'exclude',
|
||||
perSource: 'both' | 'github' | 'gitea',
|
||||
): void {
|
||||
if (mode !== 'only' || include.length === 0) return;
|
||||
const shown = new Set(showing.map((r) => r.name));
|
||||
const available = new Map(pool.map((r) => [r.name, r.source]));
|
||||
for (const name of include) {
|
||||
if (shown.has(name)) continue;
|
||||
const src = available.get(name);
|
||||
console.warn(
|
||||
src
|
||||
? `[opensource] projects:include 里的 「${name}」命中 ${src} 源,但被 perSource=${perSource} 过滤掉——改 perSource=both 或把它移出 include`
|
||||
: `[opensource] projects:include 里的 「${name}」在双源结果中没有命中(仓名拼写?私有仓?源刚失败?)——它不会出现在项目墙上`,
|
||||
);
|
||||
}
|
||||
return arrange([...byName.values()], pinned, mode, include, exclude, sort, perSource);
|
||||
}
|
||||
|
||||
/** 来源过滤 + 可见性过滤 + 排序(不截断:上限交给调用方) */
|
||||
|
||||
Reference in new issue
Block a user