Files
dev-puppeteer/scripts/seed.ts
2026-05-09 23:56:10 +09:00

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),
),
);
}
}