Files
copilotkit__copilotkit/showcase/scripts/bundle-demo-content.ts
2026-04-08 21:22:35 -07:00

261 lines
7.6 KiB
TypeScript

// Bundle Demo Content
//
// Reads demo source files and READMEs from all integration packages
// and produces a JSON bundle for the shell's Code and Docs tabs.
//
// Usage: npx tsx showcase/scripts/bundle-demo-content.ts
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import yaml from "yaml";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT = path.resolve(__dirname, "..");
const PACKAGES_DIR = path.join(ROOT, "packages");
const OUTPUT_PATH = path.join(
ROOT,
"shell",
"src",
"data",
"demo-content.json",
);
interface DemoFile {
filename: string;
language: string;
content: string;
}
interface DemoContent {
readme: string | null;
files: DemoFile[];
backend_files: DemoFile[];
}
interface BundledContent {
generated_at: string;
demos: Record<string, DemoContent>; // key: "integration-slug::demo-id"
}
function detectLanguage(filename: string): string {
const ext = path.extname(filename).toLowerCase();
const map: Record<string, string> = {
".tsx": "typescript",
".ts": "typescript",
".jsx": "javascript",
".js": "javascript",
".py": "python",
".cs": "csharp",
".css": "css",
".json": "json",
".yaml": "yaml",
".yml": "yaml",
".md": "markdown",
".mdx": "markdown",
};
return map[ext] || "text";
}
const BACKEND_EXTENSIONS = new Set([".py", ".ts", ".js", ".cs"]);
const SKIP_FILES = new Set(["__init__.py"]);
const SKIP_EXTENSIONS = new Set([".pyc"]);
function isBackendFile(filename: string): boolean {
if (SKIP_FILES.has(filename)) return false;
const ext = path.extname(filename).toLowerCase();
if (SKIP_EXTENSIONS.has(ext)) return false;
return BACKEND_EXTENSIONS.has(ext);
}
function readFilesFromDir(dir: string, prefix: string): DemoFile[] {
if (!fs.existsSync(dir)) return [];
const results: DemoFile[] = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isFile() && isBackendFile(entry.name)) {
results.push({
filename: prefix ? `${prefix}/${entry.name}` : entry.name,
language: detectLanguage(entry.name),
content: fs.readFileSync(fullPath, "utf-8"),
});
} else if (entry.isDirectory() && entry.name !== "__pycache__") {
// Recurse one level into subdirectories
const subEntries = fs.readdirSync(fullPath, { withFileTypes: true });
for (const sub of subEntries) {
if (sub.isFile() && isBackendFile(sub.name)) {
const subPath = path.join(fullPath, sub.name);
results.push({
filename: prefix
? `${prefix}/${entry.name}/${sub.name}`
: `${entry.name}/${sub.name}`,
language: detectLanguage(sub.name),
content: fs.readFileSync(subPath, "utf-8"),
});
}
}
}
}
return results;
}
function discoverBackendFiles(pkgRoot: string): DemoFile[] {
const files: DemoFile[] = [];
const srcDir = path.join(pkgRoot, "src");
// Pattern 1: src/agent_server.py or src/agent_server.ts
for (const serverFile of ["agent_server.py", "agent_server.ts"]) {
const serverPath = path.join(srcDir, serverFile);
if (fs.existsSync(serverPath)) {
files.push({
filename: serverFile,
language: detectLanguage(serverFile),
content: fs.readFileSync(serverPath, "utf-8"),
});
}
}
// Pattern 2: src/agents/ directory
files.push(...readFilesFromDir(path.join(srcDir, "agents"), "agents"));
// Pattern 3: src/mastra/ — index.ts + agents/ + tools/
const mastraDir = path.join(srcDir, "mastra");
if (fs.existsSync(mastraDir)) {
const mastraIndex = path.join(mastraDir, "index.ts");
if (fs.existsSync(mastraIndex)) {
files.push({
filename: "mastra/index.ts",
language: "typescript",
content: fs.readFileSync(mastraIndex, "utf-8"),
});
}
files.push(
...readFilesFromDir(path.join(mastraDir, "agents"), "mastra/agents"),
);
files.push(
...readFilesFromDir(path.join(mastraDir, "tools"), "mastra/tools"),
);
}
// Pattern 4: src/agent/ directory (e.g. langgraph-typescript)
files.push(...readFilesFromDir(path.join(srcDir, "agent"), "agent"));
// Pattern 5: agent/ at package root (e.g. ms-agent-dotnet)
files.push(...readFilesFromDir(path.join(pkgRoot, "agent"), "agent"));
// Deduplicate by filename (patterns may overlap for agent/ at root vs src)
const seen = new Set<string>();
const deduped: DemoFile[] = [];
for (const f of files) {
if (!seen.has(f.filename)) {
seen.add(f.filename);
deduped.push(f);
}
}
return deduped;
}
function main() {
console.log("Bundling demo content...\n");
const bundle: BundledContent = {
generated_at: new Date().toISOString(),
demos: {},
};
if (!fs.existsSync(PACKAGES_DIR)) {
console.log("No packages directory found.");
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(bundle, null, 2) + "\n");
return;
}
const packageDirs = fs
.readdirSync(PACKAGES_DIR, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name);
for (const pkgDir of packageDirs) {
try {
const manifestPath = path.join(PACKAGES_DIR, pkgDir, "manifest.yaml");
if (!fs.existsSync(manifestPath)) continue;
const manifest = yaml.parse(fs.readFileSync(manifestPath, "utf-8"));
const slug = manifest.slug as string;
const demos = (manifest.demos || []) as Array<{
id: string;
route: string;
}>;
const pkgRoot = path.join(PACKAGES_DIR, pkgDir);
const backendFiles = discoverBackendFiles(pkgRoot);
for (const demo of demos) {
const demoDir = path.join(
PACKAGES_DIR,
pkgDir,
"src",
"app",
"demos",
demo.id,
);
if (!fs.existsSync(demoDir)) continue;
const key = `${slug}::${demo.id}`;
const content: DemoContent = {
readme: null,
files: [],
backend_files: [],
};
const entries = fs.readdirSync(demoDir);
for (const entry of entries) {
const filePath = path.join(demoDir, entry);
if (!fs.statSync(filePath).isFile()) continue;
const fileContent = fs.readFileSync(filePath, "utf-8");
if (entry === "README.md" || entry === "README.mdx") {
content.readme = fileContent;
} else {
content.files.push({
filename: entry,
language: detectLanguage(entry),
content: fileContent,
});
}
}
// Sort files: page.tsx first, then agent files, then others
content.files.sort((a, b) => {
if (a.filename.startsWith("page")) return -1;
if (b.filename.startsWith("page")) return 1;
if (a.filename.startsWith("agent")) return -1;
if (b.filename.startsWith("agent")) return 1;
return a.filename.localeCompare(b.filename);
});
content.backend_files = [...backendFiles];
bundle.demos[key] = content;
console.log(
` ${key}: ${content.files.length} files, ${content.backend_files.length} backend, readme: ${content.readme ? "yes" : "no"}`,
);
}
} catch (err) {
console.error(`[bundle] Error processing package "${pkgDir}":`, err);
}
}
fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(bundle, null, 2) + "\n");
console.log(
`\nBundled ${Object.keys(bundle.demos).length} demos to ${OUTPUT_PATH}\n`,
);
}
main();