mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
2fe80ebc2d
Shell (Next.js): landing page, integrations explorer, docs viewer, AG-UI docs, feature matrix, search, demo drawer with Frontend/Backend tabs, profile pages with Live Demo / Code / Docs starter tabs. 17 integration packages with Dockerfiles, QA, tests, manifests. Build scripts for registry, demo content, starter content, search index. E2E smoke test suite (52 tests across 4 levels). Purple color scheme, independent sidebar scrolling, integration ordering by importance, category grouping, placeholder logos. Docs: 500+ MDX pages with snippet inlining, remark-gfm, rehype-highlight, AG-UI sidebar navigation, LFS images.
183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
// Registry Generator
|
|
//
|
|
// Scans showcase/packages/*/manifest.yaml, validates each against the
|
|
// manifest JSON schema, and produces showcase/shell/src/data/registry.json.
|
|
//
|
|
// Usage: npx tsx showcase/scripts/generate-registry.ts
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import yaml from "yaml";
|
|
import Ajv from "ajv";
|
|
import addFormats from "ajv-formats";
|
|
import { validateManifestConstraints } from "./validate-constraints.js";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const ROOT = path.resolve(__dirname, "..");
|
|
const PACKAGES_DIR = path.join(ROOT, "packages");
|
|
const SCHEMA_PATH = path.join(ROOT, "shared", "manifest.schema.json");
|
|
const FEATURE_REGISTRY_PATH = path.join(ROOT, "shared", "feature-registry.json");
|
|
const OUTPUT_DIR = path.join(ROOT, "shell", "src", "data");
|
|
const OUTPUT_PATH = path.join(OUTPUT_DIR, "registry.json");
|
|
const CONSTRAINTS_PATH = path.join(ROOT, "shared", "constraints.yaml");
|
|
const CONSTRAINTS_OUTPUT_PATH = path.join(OUTPUT_DIR, "constraints.json");
|
|
|
|
function loadSchema() {
|
|
const raw = fs.readFileSync(SCHEMA_PATH, "utf-8");
|
|
return JSON.parse(raw);
|
|
}
|
|
|
|
function loadFeatureRegistry() {
|
|
const raw = fs.readFileSync(FEATURE_REGISTRY_PATH, "utf-8");
|
|
return JSON.parse(raw);
|
|
}
|
|
|
|
function findManifests(): string[] {
|
|
if (!fs.existsSync(PACKAGES_DIR)) {
|
|
return [];
|
|
}
|
|
|
|
const dirs = fs
|
|
.readdirSync(PACKAGES_DIR, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name);
|
|
|
|
const manifests: string[] = [];
|
|
for (const dir of dirs) {
|
|
const manifestPath = path.join(PACKAGES_DIR, dir, "manifest.yaml");
|
|
if (fs.existsSync(manifestPath)) {
|
|
manifests.push(manifestPath);
|
|
}
|
|
}
|
|
return manifests;
|
|
}
|
|
|
|
function validateManifest(
|
|
manifest: Record<string, unknown>,
|
|
validate: ReturnType<Ajv["compile"]>,
|
|
featureIds: Set<string>,
|
|
filePath: string
|
|
): string[] {
|
|
const errors: string[] = [];
|
|
|
|
if (!validate(manifest)) {
|
|
for (const err of validate.errors || []) {
|
|
errors.push(`${filePath}: Schema error at ${err.instancePath}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
// Validate feature IDs reference the registry
|
|
const features = (manifest.features as string[]) || [];
|
|
for (const featureId of features) {
|
|
if (!featureIds.has(featureId)) {
|
|
errors.push(`${filePath}: Unknown feature ID "${featureId}" not in feature registry`);
|
|
}
|
|
}
|
|
|
|
// Validate demo IDs reference declared features
|
|
const demos = (manifest.demos as Array<{ id: string }>) || [];
|
|
for (const demo of demos) {
|
|
if (!featureIds.has(demo.id)) {
|
|
errors.push(
|
|
`${filePath}: Demo "${demo.id}" references unknown feature ID not in feature registry`
|
|
);
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
function main() {
|
|
console.log("Generating integration registry...\n");
|
|
|
|
const schema = loadSchema();
|
|
const featureRegistry = loadFeatureRegistry();
|
|
const featureIds = new Set<string>(featureRegistry.features.map((f: { id: string }) => f.id));
|
|
|
|
const ajv = new Ajv({ allErrors: true });
|
|
addFormats(ajv);
|
|
const validate = ajv.compile(schema);
|
|
|
|
const manifestPaths = findManifests();
|
|
|
|
if (manifestPaths.length === 0) {
|
|
console.log("No integration packages found. Generating empty registry.");
|
|
}
|
|
|
|
const integrations: Record<string, unknown>[] = [];
|
|
const allErrors: string[] = [];
|
|
|
|
for (const manifestPath of manifestPaths) {
|
|
const raw = fs.readFileSync(manifestPath, "utf-8");
|
|
let manifest: Record<string, unknown>;
|
|
|
|
try {
|
|
manifest = yaml.parse(raw);
|
|
} catch (e) {
|
|
allErrors.push(`${manifestPath}: Failed to parse YAML: ${e}`);
|
|
continue;
|
|
}
|
|
|
|
const errors = validateManifest(manifest, validate, featureIds, manifestPath);
|
|
if (errors.length > 0) {
|
|
allErrors.push(...errors);
|
|
continue;
|
|
}
|
|
|
|
integrations.push(manifest);
|
|
console.log(` OK: ${manifest.name} (${manifest.slug})`);
|
|
}
|
|
|
|
// Constraint validation
|
|
const constraintsRaw = fs.readFileSync(CONSTRAINTS_PATH, "utf-8");
|
|
const constraints = yaml.parse(constraintsRaw);
|
|
|
|
for (const manifest of integrations) {
|
|
const constraintErrors = validateManifestConstraints(
|
|
manifest as { slug: string; generative_ui?: string[]; interaction_modalities?: string[]; demos: Array<{ id: string; name: string }> },
|
|
constraints
|
|
);
|
|
if (constraintErrors.length > 0) {
|
|
allErrors.push(...constraintErrors);
|
|
}
|
|
}
|
|
|
|
if (allErrors.length > 0) {
|
|
console.error("\nValidation errors:");
|
|
for (const err of allErrors) {
|
|
console.error(` ERROR: ${err}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
// Sort by sort_order (lower = higher priority), then name as tiebreaker
|
|
integrations.sort((a, b) => {
|
|
const orderA = (a.sort_order as number) ?? 999;
|
|
const orderB = (b.sort_order as number) ?? 999;
|
|
if (orderA !== orderB) return orderA - orderB;
|
|
return String(a.name).localeCompare(String(b.name));
|
|
});
|
|
|
|
const registry = {
|
|
generated_at: new Date().toISOString(),
|
|
feature_registry: featureRegistry,
|
|
integrations,
|
|
};
|
|
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(registry, null, 2) + "\n");
|
|
|
|
console.log(
|
|
`\nRegistry generated: ${OUTPUT_PATH} (${integrations.length} integrations)\n`
|
|
);
|
|
|
|
// Write constraints.json for the shell's client-side filtering
|
|
fs.writeFileSync(CONSTRAINTS_OUTPUT_PATH, JSON.stringify(constraints, null, 2) + "\n");
|
|
console.log(`Constraints written: ${CONSTRAINTS_OUTPUT_PATH}`);
|
|
}
|
|
|
|
main();
|