166 lines
4.8 KiB
TypeScript
166 lines
4.8 KiB
TypeScript
import "dotenv/config";
|
|
|
|
import { existsSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { and, eq, or } from "drizzle-orm";
|
|
|
|
import {
|
|
type Persona,
|
|
type PublishedRegistryRow,
|
|
type RegistryEntry,
|
|
type Template,
|
|
assertNoReferencedRegistryDeletions,
|
|
buildRegistrySeedPlan,
|
|
loadPersonaFiles,
|
|
loadTemplateFiles,
|
|
} from "../packages/core/src/index.js";
|
|
import { createDbClient } from "../packages/db/src/client.js";
|
|
import {
|
|
agentPersonas,
|
|
runBindings,
|
|
runs,
|
|
workflowTemplates,
|
|
} from "../packages/db/src/schema/index.js";
|
|
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
|
|
if (!databaseUrl) {
|
|
throw new Error("DATABASE_URL is required to seed registries");
|
|
}
|
|
|
|
const personasDirectory = fileURLToPath(new URL("../docs/schemas/personas", import.meta.url));
|
|
const templatesDirectory = fileURLToPath(new URL("../docs/schemas/templates", import.meta.url));
|
|
const client = createDbClient(databaseUrl);
|
|
|
|
try {
|
|
const personas = loadRegistryDirectory(personasDirectory, loadPersonaFiles);
|
|
const templates = loadRegistryDirectory(templatesDirectory, loadTemplateFiles);
|
|
const result = {
|
|
personas: await seedPersonas(personas),
|
|
templates: await seedTemplates(templates),
|
|
};
|
|
|
|
console.log(JSON.stringify(result, null, 2));
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
|
|
function loadRegistryDirectory<TDefinition>(
|
|
directory: string,
|
|
load: (directory: string) => RegistryEntry<TDefinition>[],
|
|
) {
|
|
if (!existsSync(directory)) {
|
|
throw new Error(`Registry directory does not exist: ${directory}`);
|
|
}
|
|
|
|
return load(directory);
|
|
}
|
|
|
|
async function seedPersonas(entries: RegistryEntry<Persona>[]) {
|
|
const existing = await client.db
|
|
.select({
|
|
id: agentPersonas.id,
|
|
name: agentPersonas.name,
|
|
version: agentPersonas.version,
|
|
hash: agentPersonas.hash,
|
|
})
|
|
.from(agentPersonas);
|
|
const publishedRows: PublishedRegistryRow[] = [];
|
|
|
|
for (const row of existing) {
|
|
const reference = await client.db
|
|
.select({ id: runBindings.id })
|
|
.from(runBindings)
|
|
.where(or(eq(runBindings.personaId, row.id), eq(runBindings.personaHash, row.hash)))
|
|
.limit(1);
|
|
publishedRows.push({ ...row, referencedByRun: reference.length > 0 });
|
|
}
|
|
|
|
const plan = buildRegistrySeedPlan(entries, publishedRows);
|
|
assertNoReferencedRegistryDeletions("persona", plan);
|
|
await deleteUnreferencedPersonas(plan.missingUnreferenced);
|
|
for (const entry of plan.inserts) {
|
|
await client.db.insert(agentPersonas).values({
|
|
name: entry.name,
|
|
version: entry.version,
|
|
hash: entry.hash,
|
|
definition: entry.definition,
|
|
});
|
|
}
|
|
|
|
return summarizePlan(plan);
|
|
}
|
|
|
|
async function seedTemplates(entries: RegistryEntry<Template>[]) {
|
|
const existing = await client.db
|
|
.select({
|
|
id: workflowTemplates.id,
|
|
name: workflowTemplates.name,
|
|
version: workflowTemplates.version,
|
|
hash: workflowTemplates.hash,
|
|
})
|
|
.from(workflowTemplates);
|
|
const publishedRows: PublishedRegistryRow[] = [];
|
|
|
|
for (const row of existing) {
|
|
const reference = await client.db
|
|
.select({ id: runs.id })
|
|
.from(runs)
|
|
.where(or(eq(runs.templateId, row.id), eq(runs.templateHash, row.hash)))
|
|
.limit(1);
|
|
publishedRows.push({ ...row, referencedByRun: reference.length > 0 });
|
|
}
|
|
|
|
const plan = buildRegistrySeedPlan(entries, publishedRows);
|
|
assertNoReferencedRegistryDeletions("template", plan);
|
|
await deleteUnreferencedTemplates(plan.missingUnreferenced);
|
|
for (const entry of plan.inserts) {
|
|
await client.db.insert(workflowTemplates).values({
|
|
name: entry.name,
|
|
version: entry.version,
|
|
hash: entry.hash,
|
|
definition: entry.definition,
|
|
});
|
|
}
|
|
|
|
return summarizePlan(plan);
|
|
}
|
|
|
|
function summarizePlan<TDefinition>(plan: ReturnType<typeof buildRegistrySeedPlan<TDefinition>>) {
|
|
return {
|
|
deleted: plan.missingUnreferenced.length,
|
|
inserted: plan.inserts.length,
|
|
unchanged: plan.unchanged.length,
|
|
missingReferenced: plan.missingReferenced.map((entry) => `${entry.name}@${entry.version}`),
|
|
missingUnreferenced: plan.missingUnreferenced.map((entry) => `${entry.name}@${entry.version}`),
|
|
};
|
|
}
|
|
|
|
async function deleteUnreferencedPersonas(entries: PublishedRegistryRow[]) {
|
|
for (const entry of entries) {
|
|
await client.db
|
|
.delete(agentPersonas)
|
|
.where(
|
|
and(
|
|
eq(agentPersonas.name, entry.name),
|
|
eq(agentPersonas.version, entry.version),
|
|
eq(agentPersonas.hash, entry.hash),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
async function deleteUnreferencedTemplates(entries: PublishedRegistryRow[]) {
|
|
for (const entry of entries) {
|
|
await client.db
|
|
.delete(workflowTemplates)
|
|
.where(
|
|
and(
|
|
eq(workflowTemplates.name, entry.name),
|
|
eq(workflowTemplates.version, entry.version),
|
|
eq(workflowTemplates.hash, entry.hash),
|
|
),
|
|
);
|
|
}
|
|
}
|