mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
feat: include listing metadata in catalog feeds (#3135)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
Reference in New Issue
Block a user