import fs from "node:fs" ;
import os from "node:os" ;
import path from "node:path" ;
import { describe, expect, it } from "vitest" ;
import {
getChannelPluginCatalogEntry,
listChannelPluginCatalogEntries,
} from "../../../src/channels/plugins/catalog.js" ;
type CatalogEntryMeta = {
id: string;
label: string;
selectionLabel: string;
docsPath: string;
blurb: string;
detailLabel?: string;
aliases?: string[];
};
export function describeChannelCatalogEntryContract(params: {
channelId: string;
npmSpec: string;
alias?: string;
}) {
describe(`${params.channelId} channel catalog contract`, () => {
it("keeps the shipped catalog entry aligned" , () => {
const entry = getChannelPluginCatalogEntry(params.channelId);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
if (params.alias) {
expect(entry?.meta.aliases).toContain(params.alias);
}
});
it("appears in the channel catalog listing" , () => {
const ids = listChannelPluginCatalogEntries().map((entry) => entry.id);
expect(ids).toContain(params.channelId);
});
});
}
export function describeBundledMetadataOnlyChannelCatalogContract(params: {
pluginId: string;
packageName: string;
npmSpec: string;
meta: CatalogEntryMeta;
defaultChoice?: string;
}) {
describe(`${params.pluginId} bundled metadata-only channel catalog contract`, () => {
it("includes the bundled metadata-only channel entry when the runtime entrypoint is omitted" , () => {
const packageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-catalog-" ));
const bundledDir = path.join(packageRoot, "dist" , "extensions" , params.pluginId);
fs.mkdirSync(bundledDir, { recursive: true });
fs.writeFileSync(
path.join(packageRoot, "package.json" ),
JSON.stringify({ name: "openclaw" }),
"utf8" ,
);
fs.writeFileSync(
path.join(bundledDir, "package.json" ),
JSON.stringify({
name: params.packageName,
openclaw: {
extensions: ["./index.js" ],
channel: params.meta,
install: {
npmSpec: params.npmSpec,
defaultChoice: params.defaultChoice,
},
},
}),
"utf8" ,
);
fs.writeFileSync(path.join(bundledDir, "index.js" ), "export default {};\n" , "utf8" );
fs.writeFileSync(
path.join(bundledDir, "openclaw.plugin.json" ),
JSON.stringify({ id: params.pluginId, channels: [params.meta.id], configSchema: {} }),
"utf8" ,
);
const entry = listChannelPluginCatalogEntries({
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist" , "extensions" ),
},
}).find((item) => item.id === params.meta.id);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
expect(entry?.pluginId).toBe(params.pluginId);
});
});
}
export function describeOfficialFallbackChannelCatalogContract(params: {
channelId: string;
npmSpec: string;
meta: CatalogEntryMeta;
packageName: string;
pluginId: string;
externalNpmSpec: string;
externalLabel: string;
}) {
describe(`${params.channelId} official fallback channel catalog contract`, () => {
it("includes shipped official channel catalog entries when bundled metadata is omitted" , () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-official-catalog-" ));
const catalogPath = path.join(dir, "channel-catalog.json" );
fs.writeFileSync(
catalogPath,
JSON.stringify({
entries: [
{
name: params.packageName,
openclaw: {
channel: params.meta,
install: {
npmSpec: params.npmSpec,
defaultChoice: "npm" ,
},
},
},
],
}),
);
const entry = listChannelPluginCatalogEntries({
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" ,
},
officialCatalogPaths: [catalogPath],
}).find((item) => item.id === params.channelId);
expect(entry?.install.npmSpec).toBe(params.npmSpec);
expect(entry?.pluginId).toBeUndefined();
});
it("lets external catalogs override shipped fallback channel metadata" , () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-fallback-catalog-" ));
const bundledDir = path.join(dir, "dist" , "extensions" , params.pluginId);
const officialCatalogPath = path.join(dir, "channel-catalog.json" );
const externalCatalogPath = path.join(dir, "catalog.json" );
fs.mkdirSync(bundledDir, { recursive: true });
fs.writeFileSync(
path.join(bundledDir, "package.json" ),
JSON.stringify({
name: params.packageName,
openclaw: {
channel: {
...params.meta,
label: `${params.meta.label} Bundled`,
selectionLabel: `${params.meta.label} Bundled`,
blurb: "bundled fallback" ,
},
install: { npmSpec: params.npmSpec },
},
}),
"utf8" ,
);
fs.writeFileSync(
officialCatalogPath,
JSON.stringify({
entries: [
{
name: params.packageName,
openclaw: {
channel: {
...params.meta,
label: `${params.meta.label} Official`,
selectionLabel: `${params.meta.label} Official`,
blurb: "official fallback" ,
},
install: { npmSpec: params.npmSpec },
},
},
],
}),
"utf8" ,
);
fs.writeFileSync(
externalCatalogPath,
JSON.stringify({
entries: [
{
name: params.externalNpmSpec,
openclaw: {
channel: {
...params.meta,
label: params.externalLabel,
selectionLabel: params.externalLabel,
blurb: "external override" ,
},
install: { npmSpec: params.externalNpmSpec },
},
},
],
}),
"utf8" ,
);
const entry = listChannelPluginCatalogEntries({
catalogPaths: [externalCatalogPath],
officialCatalogPaths: [officialCatalogPath],
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(dir, "dist" , "extensions" ),
},
}).find((item) => item.id === params.channelId);
expect(entry?.install.npmSpec).toBe(params.externalNpmSpec);
expect(entry?.meta.label).toBe(params.externalLabel);
expect(entry?.pluginId).toBeUndefined();
});
it("surfaces package-name drift in external channel catalog install metadata" , () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-drifted-catalog-" ));
const catalogPath = path.join(dir, "catalog.json" );
fs.writeFileSync(
catalogPath,
JSON.stringify({
entries: [
{
name: params.packageName,
openclaw: {
channel: params.meta,
install: {
npmSpec: `${params.packageName}-fork@1 .2 .3 `,
expectedIntegrity: "sha512-drifted" ,
},
},
},
],
}),
"utf8" ,
);
const entry = listChannelPluginCatalogEntries({
catalogPaths: [catalogPath],
officialCatalogPaths: [],
env: {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins" ,
},
}).find((item) => item.id === params.channelId);
expect(entry?.installSource?.npm).toMatchObject({
packageName: `${params.packageName}-fork`,
expectedPackageName: params.packageName,
});
expect(entry?.installSource?.warnings).toEqual(["npm-spec-package-name-mismatch" ]);
});
});
}
Messung V0.5 in Prozent C=100 H=92 G=95
¤ Dauer der Verarbeitung: 0.3 Sekunden
¤
*© Formatika GbR, Deutschland