Standalone Node module that generates production-ready NextJS applications from JSON schema definitions. Eliminates boilerplate development by automatically generating database models, UI components, pages, navigation, and API routes directly into standard NextJS project structure.
- Languages: TypeScript, JavaScript
- Build Tools: tsup (esbuild wrapper), Turborepo with pnpm workspaces
- CLI Framework: oclif with @clack/prompts for interactive UI
- Template Engine: Handlebars for code generation
- Testing: Vitest with coverage reporting
- Code Quality: Biome (linter + formatter), changesets for versioning
- DRY principles and avoiding unnecessary code duplication, but with AHA in mind
- Separation of concerns and component responsibility boundaries
- DOTADIW (Do One Thing And Do It Well) and single responsibility principle application
- UNIX philosophy and modular design patterns
- KISS principles and simplicity-first approaches
- React composition patterns and state management strategies
- TypeScript type system design and constraint modelling
- Modular code organisation and reusable component design
- Maintainability assessment and technical debt identification
// 1. Node.js built-ins
import { readFile } from "node:fs/promises";
import { join } from "node:path";
// 2. External libraries (alphabetical)
import { Command, Flags } from "@oclif/core";
import Handlebars from "handlebars";
// 3. Internal packages (@zeno/*)
import { SchemaLoader } from "@zeno/core";
import { Generator } from "@zeno/generators";
// 4. Relative imports
import { validateSchema } from "./validation";
// 5. Types (always last)
import type { EntitySchema, GeneratorContext } from "@zeno/types";- Class methods: Use method syntax
- Standalone utilities: Function declarations
- Event handlers/callbacks: Arrow functions
// ✅ Class methods
export class SchemaLoader {
async load(path: string): Promise<SchemaSet> {}
}
// ✅ Standalone utilities as function declarations
export function validateSchema(schema: EntitySchema): ValidationResult {}
// ✅ Event handlers and callbacks as arrows
const onSchemaChange = (changes: SchemaChange[]) => {};- Utilities: camelCase (
schemaLoader.ts) - Main Classes/Generators: PascalCase (
TemplateEngine.ts,ModelGenerator.ts) - Commands: Simple names (
generate.ts) - Types: Simple names (
entity.ts,generator.ts) - Use British spelling - ie standardise not standardize
- Inferred types where clear, explicit for interfaces
- NO
anytype allowed - use proper typing - Minimal use of
unknown- prefer specific types - Explicit return types for public API functions
- Zod as single source of truth - infer TypeScript types from Zod schemas (ref Architecture 4.1)
- Nested partial schemas - define nested schemas separately when using
.partial()with defaults - Handlebar helpers - Use
(...args: unknown[]) => unknownsignature to handle options object properly - Structured outputs (JSON) - Use
SafeStringto prevent HTML escaping
// ✅ Good - inferred where clear, explicit for public APIs
export function loadSchema(path: string): Promise<EntitySchema> {
const config = { validateOnLoad: true }; // inferred
return processSchema(config);
}
// ❌ Bad - using any
function processData(data: any): any {}Use custom error classes with context:
export class SchemaValidationError extends Error {
constructor(
message: string,
public readonly filePath: string,
public readonly lineNumber?: number
) {
super(message);
this.name = "SchemaValidationError";
}
}
// Usage
throw new SchemaValidationError("Invalid email validation", "users.json", 15);Named exports with explicit index files:
// src/SchemaLoader.ts
export class SchemaLoader {}
export function createSchemaLoader(): SchemaLoader {}
// src/index.ts
export { SchemaLoader, createSchemaLoader } from "./SchemaLoader";
export { TemplateEngine } from "./TemplateEngine";
// Usage
import { SchemaLoader, ModelGenerator } from "@zeno/core";Use @clack/prompts for consistent user experience:
import { intro, outro, spinner, log } from "@clack/prompts";
export async function runGeneration() {
intro("Zeno Framework");
const s = spinner();
s.start("Loading schemas...");
try {
await generateFiles();
s.stop("Generated 12 files in 1.2s");
outro("Generation complete!");
} catch (error) {
s.stop("Generation failed");
log.error(error.message);
}
}Mixed approach based on dependencies:
// Parallel loading where possible
const [schemas, templates] = await Promise.all([
loadSchemas(context.schemaDir),
loadTemplates(context.templateDir),
]);
// Sequential for dependent operations
for (const schema of schemas.entities) {
const content = await renderTemplate(templates.entity, schema);
await writeFile(getOutputPath(schema), content);
}
// Error-tolerant parallel operations with Promise.allSettled
const results = await Promise.allSettled([
generator1.run(context),
generator2.run(context),
generator3.run(context),
]);
const successes: GeneratedFile[] = [];
const errors: Error[] = [];
for (const result of results) {
if (result.status === "fulfilled") {
successes.push(...result.value);
} else {
errors.push(result.reason);
}
}- No inline comments except for complex business logic
- JSDoc for all public APIs but nothing else
- Use British spelling - ie standardise not standardize
/**
* Loads and validates entity schemas from the specified directory.
* @param schemaDir - Path to directory containing schema files
* @returns Validated schema set ready for generation
* @throws {SchemaValidationError} When schema files are invalid
*/
export async function loadSchemas(schemaDir: string): Promise<SchemaSet> {
// Implementation without comments
}- Branch Strategy: Feature branches from main
- Commit Messages: Conventional commits with scope (
feat(core):,fix(cli):,docs(generators):) - Monorepo: pnpm workspaces with Turborepo for build orchestration
- Use British spelling - ie standardise not standardize
From architecture specification:
- Unit Tests: Schema validation, generator output, template rendering, utilities
- Integration Tests: Full generation pipeline, plugin integration
- E2E Tests: Complete project generation and build verification
Coverage target: >90% with Vitest
# Development
pnpm dev # Start development with watch mode
pnpm build # Build all packages with Turborepo
pnpm test # Run test suite with coverage
pnpm lint # Run Biome linter (check only)
pnpm format # Format code with Biome (auto-fix)
# Package-specific commands (using --filter)
pnpm --filter "@zeno/core" test # Test specific package
pnpm --filter "@zeno/core" test:coverage # Test with coverage
pnpm --filter "@zeno/core" type-check # TypeScript compilation check
pnpm --filter "@zeno/core" format # Format specific package
# Package management
pnpm changeset # Create changeset for release
pnpm version # Version packages with changesets
pnpm release # Build and publish to NPM
pnpm sync # Sync with GitHub issues
# CLI testing (when CLI package exists)
pnpm start init my-app # Test init command
pnpm start generate # Test generate command
pnpm start validate # Test schema validation/packages/@zeno/core- Core framework engine (schema loading, validation, pipeline)/packages/@zeno/cli- CLI implementation with oclif/packages/@zeno/generators/*- Output generators (models, components, pages, api)/packages/@zeno/create- Project scaffolding utilities/examples/- Example projects and configurations/docs/- Documentation site and specifications
Before submitting any code, ensure the following steps are completed:
-
- Check that code, file names and comments use British spelling - ie standardise not standardize
-
Run all validation commands:
npx tsc --noEmit pnpm lint pnpm build pnpm test -
Test CLI commands if changes affect user interface:
cd packages/cli pnpm start generate --help pnpm start init test-project -
Assess compliance: For each standard, explicitly state ✅ or ❌ and explain why:
- Import Order: Node built-ins → External → Internal (@zeno/*) → Relative → Types
- Function Patterns: Class methods, function declarations for utilities, arrows for callbacks
- File Naming: camelCase utilities, PascalCase classes, simple command/type names
- TypeScript: No
anytypes, minimalunknown, explicit public API returns - Zod Type Inference: Types inferred from Zod schemas with clean re-exports
- Error Handling: Custom error classes with context information
- Export Patterns: Named exports with explicit index file re-exports
- CLI Output: @clack/prompts for consistent user experience
- Documentation: JSDoc for public APIs only, no inline comments
-
Self-review checklist:
- Function declarations used for standalone utilities
- Imports properly ordered with types last
- No
anytypes used anywhere in codebase - Types inferred from Zod schemas where validation exists
- Custom error classes provide sufficient context
- CLI output uses @clack/prompts consistently
- Public APIs have JSDoc documentation
- File names follow role-based conventions
- Async operations optimised for performance where possible
- Mark the task as done in
PLAN.md - If the implementation touched other tasks or was unusual - update the task itself or other tasks as relevant, note any important decisions if applicable (OPTIONAL)
- If applicable, update
CLAUDE.mdwith any learned standards or patterns picked up from the review process - these must be abstracted into concise documentation (OPTIONAL) - If there have been significant changes, update
REQUIREMENTS.mdorARCHITECTURE.mdas required (OPTIONAL)
IMPORTANT: Be concise, don't repeat yourself, double check and remove duplication/reduce where possible
- Format code before commiting - run command
pnpm format - Commit with descriptive message:
- Use British spelling - ie standardise not standardize
- Granular commits - do not commit all in single commit, break them up for optimal traceability
- Informative and concise commits - multiline is encouraged but try to keep it less than 3 lines
- Follow commit guidance outlined above
- You may use gh cli - it is installed and functioning
- Sync with github issues - run
pnpm sync
- Template generation can be slow for large schema sets - use incremental generation in development
- File watching on some systems requires polling mode for proper change detection
- CLI progress indicators may not render correctly in some terminal environments
- < 50ms per table generation
- < 10MB memory per table
- < 2s complete application generation (50 entities)
- Parallel generation support with worker threads