這是針對外掛註冊表建置自有軟體的進階主題。若只想在 EmDash 站點上安裝外掛,則不需要這些——在設定中啟用註冊表並使用管理儀表板即可。
註冊表的探索側是公開的唯讀 API。@emdash-cms/registry-client 套件對其進行封裝,因此你可以在 EmDash 之外建置外掛目錄、搜尋頁或發布動態。用戶端可在任何提供 fetch 的環境執行——Node、Workers、瀏覽器或 Astro 站點。Astro 站點可使用 @emdash-cms/registry-loader,透過 live content collection 暴露相同資料。
安裝
discovery 子路徑不帶認證或 OAuth 依賴。安裝用戶端並將其固定到確切版本:
npm install @emdash-cms/registry-client@0.6.0
列出與解析套件
以下 Astro 頁面列出註冊表中的每個外掛:
---
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
const { packages } = await discovery.searchPackages({ q: "", limit: 50 });
---
<ul>
{
packages.map((pkg) => (
<li>
<a href={`/plugins/${pkg.handle ? `@${pkg.handle}` : pkg.did}/${pkg.slug}`}>
{pkg.profile?.name ?? pkg.slug}
</a>
{pkg.latestVersion && <span>v{pkg.latestVersion}</span>}
<p>{pkg.profile?.description}</p>
</li>
))
}
</ul>
當彙總器已解析時,會存在目前已驗證的 handle。將 DID 形態作為後備保留,並在後續回應提供目前 handle URL 時重新導向到它。發布者變更 handle 時,DID 與套件 slug 仍是穩定的套件身分。
精確的 q 值可以是 handle、DID,或任一身分後跟 /slug。例如,@example.com/my-gallery 選擇一個套件,example.com 回傳該發布者的套件。
使用 Astro live loader
安裝 @emdash-cms/registry-loader,然後在 src/live.config.ts 中註冊 loader:
import { registryLoader } from "@emdash-cms/registry-loader";
import { defineLiveCollection } from "astro:content";
export const collections = {
plugins: defineLiveCollection({ loader: registryLoader() }),
};
對有界結果使用 getLiveCollection("plugins", { q, limit }),對單一套件使用 getLiveEntry("plugins", { publisher, slug })。當站點有 Astro 快取提供方時,將回傳的快取提示傳給 Astro.cache.set()。Astro live collections 不回傳分頁中繼資料,因此當介面需要下一個游標時,請直接使用 DiscoveryClient.searchPackages()。
套件詳情頁按 DID 與 slug 取得外掛,然後取得最新發布:
import { DiscoveryClient } from "@emdash-cms/registry-client/discovery";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
export async function getPlugin(did: string, slug: string) {
const pkg = await discovery.getPackage({ did, slug });
const latest = await discovery.getLatestRelease({
did: pkg.did,
package: pkg.slug,
});
return { pkg, latest };
}
探索方法
用戶端為每個彙總器查詢公開一個方法:
searchPackages({ q, capability?, limit?, cursor? })— 自由文字搜尋,可選過濾為宣告給定存取類別的套件。回傳{ packages, cursor? }。resolvePackage({ handle, slug })— 從 handle 與 slug 解析套件。getPackage({ did, slug })— 按 DID 與 slug 取得套件。listReleases({ did, package, limit?, cursor? })— 按語意版本降序的發布,包括已撤回的發布。getLatestRelease({ did, package })— 彙總器選擇的最高未撤回發布。
getPackage() 與 resolvePackage() 可回傳 historicalReleaseCount 和
releaseHistoryComplete。這些欄位描述彙總器保留的營運歷史,
而非發布者簽署的中繼資料。僅當 releaseHistoryComplete 為 true 時,
才將計數 1 視為首次發布。缺失或不完整的證據不得繞過發布年齡
原則。
getPackageStatus() 與 resolvePackageStatus() 封裝其對應的套件查詢,並將
安全的 ListingUnavailable 回應對應為 { status: "unavailable" }。成功結果為
{ status: "passed", value }。在需要區分已索引但不可用的列表與缺失套件、且不轉譯
發布者控制錯誤內容的使用者介面中使用這些方法。
在顯示或選擇發布前使用撤回輔助函式:
import {
DiscoveryClient,
type ValidatedReleaseView,
} from "@emdash-cms/registry-client/discovery";
import { evaluateRegistryReleaseWithdrawal } from "@emdash-cms/registry-client/withdrawal";
const discovery = new DiscoveryClient({
aggregatorUrl: "https://registry.emdashcms.com",
});
export function canShowRelease(release: ValidatedReleaseView) {
const result = evaluateRegistryReleaseWithdrawal(release, discovery.labelerPolicy);
return release.release !== null && !result.withdrawn;
}
當適用標籤將發布從使用中移除時,withdrawn 為 true。無效的標籤資料
會 fail closed:withdrawn 與 malformed 均為 true。
處理不可信記錄
彙總器是不可信的索引,中繼其未撰寫的記錄,因此用戶端在邊界驗證每一條。由此得出兩條規則:
profile與release欄位可為null。 當中繼記錄驗證失敗時,用戶端將其表面為null而不是使整個呼叫失敗,因此一條格式錯誤的記錄不會清空搜尋頁。讀取pkg.profile?.name或latest.release?.artifacts.package前務必檢查 null。- 轉譯前自行驗證 URL 方案。 驗證檢查結構,而非 URL 安全性——
uri欄位可攜帶javascript:方案。在將任何註冊表提供的 URL 放入href或src之前,套用你自己的http/https允許列表。
非 2xx 回應會拋出 ClientResponseError(從套件中重新匯出),帶有 .error、.description、.status 與 .headers。參考彙總器僅回傳經每個所需正標籤來源核准的精確 CID 修訂。atproto-accept-labelers 標頭為請求與快取身分宣告已設定的裸 DID。彙總器驗證該宣告,但其設定的原則仍具權威性。
按主機相容性篩選
發布可在其 requires 區塊中宣告環境要求(EmDash 或 Astro 版本範圍)。@emdash-cms/registry-client/env 子路徑會評估它們,因此目錄可標記在給定主機上無法執行的發布:
import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
import type { ValidatedReleaseView } from "@emdash-cms/registry-client/discovery";
const host = hostEnvFromVersions("0.37.0", "7.0.0");
// Pass a getLatestRelease() result. The returned array is empty when the
// release runs on this host.
export function envMismatches(latest: ValidatedReleaseView) {
return checkEnvCompatibility(latest.release?.requires, host);
}