feat: include listing metadata in catalog feeds (#3135)

This commit is contained in:
Patrick Erichsen
2026-07-16 22:01:45 -07:00
committed by GitHub
parent e29d1c6005
commit b5890d3d9a
4 changed files with 54 additions and 4 deletions
+16 -4
View File
@@ -191,9 +191,17 @@ describe("catalog feed projection", () => {
it("projects official releases into ClawHub install candidates", async () => {
const result = await listOfficialEntriesHandler(
makeCtx([makePackage()], {
"packageReleases:1": makeRelease(),
}),
makeCtx(
[
makePackage({
summary: "Search flights, stays, and travel options.",
icon: "https://cdn.example.test/expedia.png",
}),
],
{
"packageReleases:1": makeRelease(),
},
),
{ family: "code-plugin" },
);
@@ -202,6 +210,8 @@ describe("catalog feed projection", () => {
type: "plugin",
id: "@openclaw/demo",
title: "Demo",
description: "Search flights, stays, and travel options.",
icon: "https://cdn.example.test/expedia.png",
version: "1.2.3",
state: "available",
featured: false,
@@ -296,7 +306,7 @@ describe("catalog feed projection", () => {
it("projects only published skills from verified organization publishers", async () => {
const result = (await listOfficialSkillEntriesHandler(
makeCtx([makeSkill()], {
makeCtx([makeSkill({ summary: "Deploy AIQ services.", icon: "lucide:rocket" })], {
"publishers:1": { _id: "publishers:1", kind: "org", handle: "openclaw" },
"skillVersions:1": makeSkillVersion(),
}),
@@ -309,6 +319,8 @@ describe("catalog feed projection", () => {
type: "skill",
id: "@openclaw/demo",
title: "Demo skill",
description: "Deploy AIQ services.",
icon: "lucide:rocket",
version: "1.2.3",
state: "available",
featured: false,
+12
View File
@@ -54,6 +54,8 @@ function appendEntriesWithinFeedLimit<T>(target: T[], entries: T[]) {
const catalogFeedEntryFields = {
id: v.string(),
title: v.string(),
description: v.optional(v.string()),
icon: v.optional(v.string()),
version: v.string(),
state: v.union(
v.literal("available"),
@@ -117,6 +119,8 @@ async function buildEntry(
const packageName = pkg.name.trim();
const id = pkg.normalizedName.trim();
const title = pkg.displayName.trim() || packageName;
const description = pkg.summary?.trim();
const icon = pkg.icon?.trim();
const version = release.version.trim();
if (!packageName || !id || !title || !version) return null;
const highlighted = await ctx.db
@@ -128,6 +132,8 @@ async function buildEntry(
type: "plugin",
id,
title,
...(description ? { description } : {}),
...(icon ? { icon } : {}),
version,
state: "available",
featured: Boolean(highlighted),
@@ -202,6 +208,8 @@ async function buildSkillEntry(
const publisherId = owner.handle?.trim();
const slug = skill.slug.trim();
const title = skill.displayName.trim() || slug;
const description = skill.summary?.trim();
const icon = skill.icon?.trim();
const packageName = `@${publisherId}/${slug}`;
if (!publisherId || !slug || !title) return null;
@@ -230,6 +238,8 @@ async function buildSkillEntry(
type: "skill",
id: packageName,
title,
...(description ? { description } : {}),
...(icon ? { icon } : {}),
version: commit,
state: "available",
featured: isSkillHighlighted(skill),
@@ -275,6 +285,8 @@ async function buildSkillEntry(
type: "skill",
id: packageName,
title,
...(description ? { description } : {}),
...(icon ? { icon } : {}),
version: versionName,
state: "available",
featured: isSkillHighlighted(skill),
+22
View File
@@ -162,6 +162,28 @@ describe("catalog feed schema", () => {
expect(parseCatalogFeed(makeFeed()).entries[0]).not.toHaveProperty("featured");
});
it("round-trips optional listing metadata without changing schema version 1", () => {
const feed = makeFeed({
entries: [
{
...makeFeed().entries[0],
description: "Search flights, stays, and travel options.",
icon: "https://cdn.example.test/expedia.png",
},
],
});
const parsed = parseCatalogFeed(JSON.parse(serializeCatalogFeed(feed)));
expect(parsed.schemaVersion).toBe(1);
expect(parsed.entries[0]).toMatchObject({
description: "Search flights, stays, and travel options.",
icon: "https://cdn.example.test/expedia.png",
});
expect(parseCatalogFeed(makeFeed()).entries[0]).not.toHaveProperty("description");
expect(parseCatalogFeed(makeFeed()).entries[0]).not.toHaveProperty("icon");
});
it("preserves public GitHub source identity on skill candidates", () => {
const feed = makeFeed({
id: CATALOG_SKILLS_FEED_ID,
+4
View File
@@ -31,6 +31,8 @@ const CatalogFeedEntryBaseSchema = {
"+": "reject",
id: "string",
title: "string",
description: "string?",
icon: "string?",
version: "string",
state: CatalogFeedStateSchema,
// Additive v1 metadata: existing hosted-feed consumers ignore unknown entry fields.
@@ -116,6 +118,8 @@ export function serializeCatalogFeed(feed: CatalogFeed): string {
type: entry.type,
id: entry.id,
title: entry.title,
...(entry.description === undefined ? {} : { description: entry.description }),
...(entry.icon === undefined ? {} : { icon: entry.icon }),
version: entry.version,
state: entry.state,
...(entry.featured === undefined ? {} : { featured: entry.featured }),