@genzai/core
v0.2.0
Published
Core code generation framework with template composition and middleware pipeline
Maintainers
Readme
@genzai/core
Minimal code generation framework with composable templates and middleware pipeline.
Installation
npm install @genzai/core
# or
pnpm add @genzai/coreQuick Start
import { generate, file, folder } from "@genzai/core";
import type { Node } from "@genzai/core";
// Define your generator - a function that takes context and returns nodes
const myGenerator = (ctx: { projectName: string }): Node[] => [
folder("src", [
file("index.ts", `export const name = "${ctx.projectName}";`),
file("config.ts", () => `export const config = { name: "${ctx.projectName}" };`),
]),
file("README.md", `# ${ctx.projectName}\n\nGenerated with Genzai!`),
];
// Generate to filesystem
await generate(myGenerator, "./output", { projectName: "my-app" });Core Concepts
Generator
A generator is simply a function that takes context and returns an array of nodes (sync or async):
type Generator<C, Meta = {}> = (ctx: C) => Node<Meta>[] | Promise<Node<Meta>[]>;Nodes
Two types of nodes:
FileNode- Represents a file with lazy-loaded contentFolderNode- Represents a folder with child nodes
API
file(name, content, meta?)
Creates a file node.
// Static content
file("hello.txt", "Hello, World!")
// Dynamic content (function)
file("config.json", () => JSON.stringify({ version: "1.0.0" }))
// Async content
file("data.json", async () => {
const data = await fetchData();
return JSON.stringify(data);
})
// With metadata
file("output.txt", "content", { priority: 10 })Parameters:
name:string | (() => string)- File name or function returning namecontent:string | (() => string | Promise<string>)- Content or function returning contentmeta?:Meta- Optional metadata object
Returns: FileNode<Meta>
folder(name, children, meta?)
Creates a folder node containing children.
folder("src", [
file("index.ts", "export {}"),
file("utils.ts", "export const helper = () => {}"),
])
// With dynamic name
folder(() => "dist", [...])
// With metadata
folder("build", [...], { temporary: true })Parameters:
name:string | (() => string)- Folder name or function returning namechildren:Node<Meta>[]- Array of child nodesmeta?:Meta- Optional metadata object
Returns: FolderNode<Meta>
generate(generator, destRoot, context, config?)
Executes generator and writes to filesystem.
await generate(
myGenerator,
"./output",
{ projectName: "my-app" },
{ middlewares: [loggingMiddleware] }
);Parameters:
generator:Generator<C, Meta>- Generator functiondestRoot:string- Output directorycontext:C- Context passed to generatorconfig?:MiddlewareConfig<Meta>- Optional middleware configuration
Patterns
Iteration
Use native JavaScript for iteration:
const generator = (ctx: { users: User[] }): Node[] => [
folder("users",
ctx.users.map(user =>
file(`${user.name}.json`, JSON.stringify(user))
)
),
];Conditionals
Use ternaries or conditionals:
const generator = (ctx: { includeTests: boolean }): Node[] => [
file("index.ts", "export {}"),
...(ctx.includeTests ? [file("test.ts", "// tests")] : []),
];Nested Structures
const generator = (ctx: { screens: Screen[] }): Node[] =>
ctx.screens.map(screen =>
folder(screen.name.toLowerCase(), [
file("page.tsx", () => makePageTemplate(screen, ctx)),
file("types.ts", () => makeTypesTemplate(screen)),
])
);Default Middleware
The defaultMiddleware array provides file markers and regeneration strategy handling out of the box:
import { generate, file, folder, defaultMiddleware } from "@genzai/core";
await generate(myGenerator, "./output", ctx, {
middlewares: defaultMiddleware,
});Generated files will include metadata markers:
/**
* @generated genzai
* @regenerationStrategy overwrite
*/
export const name = "my-app";Regeneration Strategies
Control how files are handled on regeneration:
"overwrite"(default) - File is regenerated every time"preserve-edits"- File is skipped if it already exists with this strategy
import { file } from "@genzai/core";
import type { RegenerationStrategyMeta } from "@genzai/core";
const generator = (): Node<RegenerationStrategyMeta>[] => [
// Always regenerated
file("generated.ts", () => `// generated code`),
// Preserved after first generation
file<RegenerationStrategyMeta>("config.ts", () => `export default {}`, {
regenerationStrategy: "preserve-edits",
}),
];Custom Middleware
Middleware intercepts the write process for transformations, logging, etc.
import { Middleware } from "@genzai/core";
const loggingMiddleware: Middleware = async (context, next) => {
console.log(`Writing: ${context.path}`);
return next(context);
};
const uppercaseMiddleware: Middleware = async (context, next) => {
if (context.nodeType === "file") {
return next({
...context,
content: context.content.toUpperCase(),
});
}
return next(context);
};
await generate(myGenerator, "./output", ctx, {
middlewares: [loggingMiddleware, uppercaseMiddleware],
});Metadata
Attach custom metadata to nodes for middleware processing:
type MyMeta = {
file: {
overwrite?: boolean;
format?: string;
}
folder: Record<string, any>;
};
const generator = (): Node<MyMeta>[] => [
file("config.json", "{}", { overwrite: false, format: "json" }),
];
// Middleware can access metadata via node.meta
const formatMiddleware: Middleware<MyMeta> = async (ctx, next) => {
if (ctx.nodeType === "file" && ctx.node.meta?.format === "json") {
const formatted = JSON.stringify(JSON.parse(ctx.content), null, 2);
return next({ ...ctx, content: formatted });
}
return next(ctx);
};Related Packages
@genzai/templates- Load generators from template directories@genzai/slots- Slot-based content preservation@genzai/logging- Logging middleware@genzai/prettier- Code formatting middleware
License
MIT
