搜索 K
主题
@vaultkit/repo-platform-kit 是代码托管平台(GitHub / CNB)的统一取数内核:URL 解析、raw 内容与仓库元数据的请求构造、认证策略、带韧性的 TTL 磁盘缓存、平台徽标常量,全部收敛在一个零依赖的 Node 库里。它本身不依赖 VitePress,是 repo-card、remote-file、npm-card 三个插件的共享底座,任何 Node 构建期工具都可直接消费。
本文是 API 文档型文章:全部导出函数、类型、常量逐一详解,附鉴权对照表、缓存机制剖析,以及如何基于 Provider 五方法扩展新平台。
repo-card 要抓仓库元数据,remote-file 要抓文件 raw 内容——两者都要解析 GitHub / CNB 的各种 URL 形态、都要处理认证、都要缓存。这些「平台知识」若在每个插件里各写一份,GitHub 改个 URL 形态就要修三处,CNB 的认证坑(下文详述)也要踩三遍。
repo-platform-kit 把平台知识收敛到单点:每个平台一份 Provider 实现,五个方法覆盖「解析 → 构造请求 → 回链」全链路;缓存与认证策略与平台实现正交复用。效果可以直接看——下面这张卡片和这段代码,就是站在这个底座上渲染出来的:
{
"name": "vitepress",
"version": "2.0.0-alpha.18",
"description": "Vite & Vue powered static site generator",
"keywords": [pnpm add -D @vaultkit/repo-platform-kit零运行时依赖——只用 Node 内置模块(node:fs / node:crypto / node:path)与全局 fetch。注意它是 Node 侧库,仅供构建期消费,不可打进客户端产物。
三十秒抓一个 GitHub 文件(带磁盘缓存):
// build.ts
import { defaultCacheDir, fetchCached, parseFileUrl, PROVIDERS } from '@vaultkit/repo-platform-kit'
const ref = parseFileUrl('https://github.com/vuejs/core/blob/main/README.md#L1-L20')
if (ref) {
const request = PROVIDERS[ref.platform].fileRawRequest(ref, {})
const content = await fetchCached(request, {
ttl: 3_600_000,
cacheDir: defaultCacheDir('my-tool'),
tag: 'my-tool',
})
console.log(content)
}type Platform = 'github' | 'cnb'平台字面量联合,全库的判别键。
平台文件坐标——parseFileUrl 的产物、文件类请求构造的输入:
interface FileRef {
platform: Platform
repo: string // 仓库路径;CNB 可含多级组织 org/sub/repo
ref: string // 分支名或 commit SHA
path: string // 仓库内文件路径
start?: number // 行区间起点(来自 URL 的 #L10-L40 片段)
end?: number // 行区间终点;只给 #L10 时 start 与 end 同值
}仓库坐标,parseRepoUrl 的产物:{ platform, repo }。
认证选项,三个字段全部可缺省(回退环境变量 / 官方基址):
interface AuthOptions {
githubToken?: string // 缺省读 GITHUB_TOKEN,可选,仅提额
cnbToken?: string // 缺省读 CNB_TOKEN,CNB 场景必需
cnbApiBase?: string // 缺省 https://api.cnb.cool
}请求描述 { url, headers }——Provider 只构造请求不发请求,发与缓存交给 fetchCached。构造与执行分离,消费方也可以拿 HttpRequest 自己发。
缓存上下文 { ttl, cacheDir, tag }:ttl 单位 ms;cacheDir 缓存目录;tag 用于日志与报错归属(如 'repo-card')。
Record<Platform, Provider>——平台知识的本体,每份 Provider 实现五个方法(见下节)。
{key="value" key2=bare} 花括号属性的解析正则,三个消费插件共用同一口径。parseAttrs 是它的便捷包装。
Record<Platform, string>——GitHub / CNB 的 simple-icons 形 SVG 徽标字符串。填色用 currentColor,品牌色(CNB 橙 #FF6200 等)由消费方 CSS 上色,同一份 SVG 可用于任意配色场景。
(url: string) => FileRef | null——全平台逐一尝试解析文件 URL,支持的形态:
github.com/<o>/<r>/blob/<ref>/<path>(raw 路径、refs/heads/ 前缀、permalink SHA 均可)raw.githubusercontent.com/<o>/<r>/<ref>/<path>cnb.cool/<repo>/-/blob/<ref>/<path>(/-/raw/、/-/git/raw/ 亦可)#L10-L40 片段解析进 start / end。不匹配返回 null(不抛错,交消费方定错误文案)。
(url: string) => RepoRef | null——解析仓库主页 URL。刻意排除文件页:GitHub 的 /blob/、/raw/ 形态与 CNB 的 /-/ 形态返回 null,避免「把文件链接当仓库」的静默误判。
(request: HttpRequest, ctx: CacheContext) => Promise<string>——带韧性的抓取,行为分四层:
cacheDir/sha1(url).json,存 { content, fetchedAt })在 TTL 内 → 直接返回,不发请求console.warn,构建照常第 3 层是弱网 / 限额韧性的关键:已经构建成功过的站点,上游抖动不会让它突然构建失败。
(name: string) => string——约定缓存目录 <cwd>/node_modules/.cache/<name>。CI 上建议把这个目录持久化,配合陈缓存回退可把外部 API 请求压到近乎为零。
(auth: AuthOptions, tag: string) => string——解析 CNB 令牌(auth.cnbToken ?? env.CNB_TOKEN),缺失时抛出带配置指引的错误,tag 标注报错归属插件。单独导出是因为 CNB 令牌是「必需项」,值得一个语义明确的入口。
(text: string) => Record<string, string>——解析花括号属性文本为键值表,接受引号值与裸值。
Provider 是平台知识的接口契约(内部接口,经 PROVIDERS 暴露实例):
| 方法 | 签名 | 职责 |
|---|---|---|
parseFileUrl | (url) => FileRef | null | 识别本平台的文件 URL 形态 |
parseRepoUrl | (url) => RepoRef | null | 识别仓库主页 URL(排除文件页) |
fileRawRequest | (ref, auth) => HttpRequest | 构造文件 raw 内容请求(URL + 认证头) |
fileHtmlUrl | (ref) => string | FileRef → 平台网页版地址(出处回链用) |
repoMetaRequest | (ref, auth) => HttpRequest | 构造仓库元数据 API 请求 |
两平台的落点对照:
| 职责 | GitHub | CNB |
|---|---|---|
| raw 内容 | raw.githubusercontent.com/... | api.cnb.cool/<repo>/-/git/raw/<ref>/<path> |
| 仓库元数据 | api.github.com/repos/<repo> | api.cnb.cool/<repo> |
| 认证头 | 有 token 才带 Authorization | 恒带 Authorization(无 token 即抛错) |
| 平台 | 令牌来源 | 必要性 |
|---|---|---|
| GitHub | AuthOptions.githubToken ?? 环境变量 GITHUB_TOKEN | 可选。匿名限额 60 次/时,带令牌提至 5000 次/时 |
| CNB | AuthOptions.cnbToken ?? 环境变量 CNB_TOKEN | 必需。CNB API 连公开仓库也要求认证(实测 401),令牌权限需含 repo-code:r |
配套的错误提示也内置在 fetchCached 里:GitHub 403 时提示「设置 GITHUB_TOKEN 提额」,CNB 401 时提示「令牌需含 repo-code:r 权限」——报错即文档。
三个消费插件各取所需,恰好画出这个库的分层:
| 消费方 | 平台知识 | 属性解析 | 缓存 | 徽标 |
|---|---|---|---|---|
| repo-card | parseRepoUrl + requireCnbToken | ATTR_PATTERN | fetchCached + defaultCacheDir | PLATFORM_MARKS |
| remote-file | parseFileUrl + PROVIDERS(raw 请求 / 回链) | parseAttrs | 同上 | PLATFORM_MARKS |
| npm-card | 无(npm 不是代码托管平台) | ATTR_PATTERN | 同上 | 自带 npm 徽标 |
npm-card 一行平台代码都不用,仍复用缓存与属性解析——说明「平台层」与「基础设施层」在这个库里是正交的:前者是 PROVIDERS,后者是 fetchCached / parseAttrs / defaultCacheDir。
想支持 Gitee / GitLab?新增平台 = 补一份 Provider 实现,五个方法照抄结构:
const gitee: Provider = {
parseFileUrl(url) {
// 识别 gitee.com/<o>/<r>/blob/<ref>/<path>(含 #L 片段)
const m = url.match(GITEE_BLOB)
return m ? fileRefOf('gitee', `${m[1]}/${m[2]}`, m[3], m[4], m[5], m[6]) : null
},
parseRepoUrl(url) {
// 仓库主页形态,记得排除 /blob/ 文件页
},
fileRawRequest(ref, auth) {
return { url: `https://gitee.com/${ref.repo}/raw/${ref.ref}/${ref.path}`, headers: {} }
},
fileHtmlUrl(ref) {
return `https://gitee.com/${ref.repo}/blob/${ref.ref}/${ref.path}`
},
repoMetaRequest(ref, auth) {
return { url: `https://gitee.com/api/v5/repos/${ref.repo}`, headers: {} }
},
}再把 'gitee' 加进 Platform 联合、把实例挂进 PROVIDERS——顶层的 parseFileUrl / parseRepoUrl 是对全平台的逐一尝试循环,新平台自动被纳入;上层插件零改动获得新平台支持(当前 Platform 是封闭联合,扩展经 PR / fork 进行,这是刻意的:平台知识必须经过测试收编,不开运行时注册口子)。
设计要点:Provider 方法只做纯函数式的解析与构造,不发请求、不读缓存——副作用全部集中在 fetchCached。这让新平台的实现无需关心缓存与韧性,也让五个方法都可以独立单测。
为什么不用 octokit? 需求只有「一个 GET + 认证头」,octokit 及其依赖树对构建期工具是过度工程;零依赖意味着安装快、供应链面小。
缓存会无限膨胀吗? 缓存按 URL 数量线性增长,站点引用的 URL 集合有限;node_modules/.cache 随依赖重装自然清理,也可随时手动删。
能在浏览器里用吗? 不能,它用了 node:fs / node:crypto。浏览器场景请在服务端取好数再下发。