Files
vercel__eve/apps/docs/scripts/validate-channel-registry.ts
T
OwenKephart d257866e64 feat(eve): add resumable registry setup with skip flags (#1304)
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
2026-07-29 19:37:26 +00:00

151 lines
4.9 KiB
TypeScript

import { access, readFile } from "node:fs/promises";
import { join } from "node:path";
import { channelEntries } from "@vercel/eve-catalog";
interface RegistryFile {
path: string;
target?: string;
}
interface RegistryItem {
name: string;
dependencies?: string[];
files?: RegistryFile[];
meta?: {
eve?: {
setup?: {
command?: string;
package?: string;
bin?: string;
args?: string[];
};
};
};
}
interface Registry {
items: RegistryItem[];
}
const registrySlugsByCatalogSlug: Readonly<Record<string, string>> = {
eve: "web",
};
const adapterDependenciesByCatalogSlug: Readonly<Record<string, string>> = {
"chat-sdk-gchat": "@chat-adapter/gchat",
"chat-sdk-whatsapp": "@chat-adapter/whatsapp",
"chat-sdk-x": "@chat-adapter/x",
"chat-sdk-messenger": "@chat-adapter/messenger",
"chat-sdk-zernio": "@zernio/chat-sdk-adapter",
"chat-sdk-velt": "@veltdev/chat-sdk-adapter",
"chat-sdk-sendblue": "chat-adapter-sendblue",
"chat-sdk-novu": "@novu/chat-sdk-adapter",
"chat-sdk-liveblocks": "@liveblocks/chat-sdk-adapter",
"chat-sdk-linq": "@linqapp/chat-sdk-adapter",
"chat-sdk-kapso": "@kapso/chat-adapter",
"chat-sdk-photon": "@photon-ai/chat-adapter-imessage",
"chat-sdk-dial": "@getdial/chat-sdk-adapter",
"chat-sdk-agentphone": "@agentphone/chat-sdk-adapter",
"chat-sdk-lark": "@larksuite/vercel-chat-adapter",
"chat-sdk-beeper": "@beeper/chat-adapter-matrix",
"chat-sdk-resend": "@resend/chat-sdk-adapter",
};
const targetSlugsByCatalogSlug: Readonly<Record<string, string>> = {
"linear-agent": "linear",
"chat-sdk-gchat": "gchat",
"chat-sdk-whatsapp": "whatsapp",
"chat-sdk-x": "x",
"chat-sdk-messenger": "messenger",
"chat-sdk-zernio": "zernio",
"chat-sdk-velt": "velt",
"chat-sdk-sendblue": "sendblue",
"chat-sdk-novu": "novu",
"chat-sdk-liveblocks": "liveblocks",
"chat-sdk-linq": "linq",
"chat-sdk-kapso": "kapso",
"chat-sdk-photon": "imessage",
"chat-sdk-dial": "dial",
"chat-sdk-agentphone": "agentphone",
"chat-sdk-lark": "lark",
"chat-sdk-beeper": "beeper",
"chat-sdk-resend": "resend",
};
const docsRoot = join(import.meta.dirname, "..");
const registry = JSON.parse(await readFile(join(docsRoot, "registry.json"), "utf8")) as Registry;
const items = registry.items.filter((item) => item.name.startsWith("channel/"));
const galleryEntries = channelEntries().filter((entry) => entry.surfaces.gallery);
const expectedSlugs = galleryEntries.map(
(entry) => registrySlugsByCatalogSlug[entry.slug] ?? entry.slug,
);
const actualSlugs = items.map((item) => item.name.slice("channel/".length));
if (JSON.stringify(actualSlugs) !== JSON.stringify(expectedSlugs)) {
throw new Error(
`Channel registry entries do not match the gallery.\nExpected: ${expectedSlugs.join(", ")}\nActual: ${actualSlugs.join(", ")}`,
);
}
for (const [index, item] of items.entries()) {
const setup = item.meta?.eve?.setup;
if (
setup !== undefined &&
(setup.command === undefined ||
setup.package === undefined ||
setup.bin === undefined ||
setup.args === undefined)
) {
throw new Error(
`Registry item "${item.name}" setup must declare command, package, bin, and args during the migration.`,
);
}
const entry = galleryEntries[index];
if (entry === undefined) throw new Error(`Unexpected channel registry item "${item.name}".`);
const registrySlug = expectedSlugs[index];
if (entry.slug === "slack" || entry.slug === "eve") {
const expectedArgs = ["integration", "setup", registrySlug];
if (
setup?.command !== "eve" ||
setup.package !== "eve" ||
setup.bin !== "eve" ||
JSON.stringify(setup.args) !== JSON.stringify(expectedArgs)
) {
throw new Error(
`Registry item "${item.name}" must delegate setup to eve integration setup ${registrySlug}.`,
);
}
continue;
}
const expectedPath = `registry/channels/${entry.slug}.ts`;
const expectedTarget = `agent/channels/${targetSlugsByCatalogSlug[entry.slug] ?? entry.slug}.ts`;
const file = item.files?.[0];
if (item.files?.length !== 1 || file?.path !== expectedPath || file.target !== expectedTarget) {
throw new Error(
`Registry item "${item.name}" must write ${expectedPath} to ${expectedTarget}.`,
);
}
await access(join(docsRoot, expectedPath));
const adapterDependency = adapterDependenciesByCatalogSlug[entry.slug];
if (entry.slug.startsWith("chat-sdk-") && adapterDependency === undefined) {
throw new Error(`Registry item "${item.name}" has no adapter dependency mapping.`);
}
if (adapterDependency !== undefined) {
const dependencies = item.dependencies ?? [];
if (
!dependencies.includes("chat") ||
!dependencies.includes(adapterDependency) ||
!dependencies.includes("@chat-adapter/state-memory")
) {
throw new Error(
`Registry item "${item.name}" must install Chat SDK, ${adapterDependency}, and its state adapter.`,
);
}
}
}