3.7 KiB
Generator — generator.ts
(no submodules)
Purpose
Owns the two-call surface that scaffolds an Electrobun project on disk.
collectFiles(options, overrides?) is pure — it returns the
GeneratedFile[] list for a given ProjectOptions without touching the
filesystem. generate(options, deps?) writes the file list through the
FsAdapter seam, snapshots the .suaveplan-electrobun.json config, and
optionally runs the icon pipeline when options.iconSource is set.
The generator is the only place that knows the canonical file order: the
nine core files come first (electrobun.config.ts, package.json,
tsconfig.json, the main-process Bun entry, three mainview files, the
shared types module, the README), then one file per selected feature in
the FEATURE_ORDER (i18n, networking, api, api-schemas,
storage, auth, form-builder, r3f). Input order does not matter
— the iterator walks FEATURE_ORDER and includes the feature only when
it is present in the input set.
The overrides argument lets tests substitute a faulty renderer to
exercise the ELECTROBUN.TEMPLATE_RENDER_FAILED wrapper without forcing
the production renderer table to throw. Production callers never set
overrides.
Features
- Deterministic file ordering across runs.
- Zod-validated input via
ProjectOptionsSchema. - Wraps every renderer throw as
ELECTROBUN.TEMPLATE_RENDER_FAILED. - Optional icon pipeline gated by
options.iconSource. - OTel:
recordProject(featuresCount)increment +recordGenerateDuration(ms, featuresCount)observation per call. CollectOverridesseam for test-only renderer substitution.
Usage: Basic
import { generate } from "@suaveplan/electrobun/generator";
const report = await generate({
name: "my-app",
identifier: "com.example.my-app",
description: "demo",
version: "0.1.0",
outputDir: "/tmp/my-app",
features: ["i18n", "api"],
networkingComplexity: "basic",
r3fModules: [],
});
console.log(`Wrote ${report.filesCreated.length} files in ${report.durationMs}ms`);
Usage: Advanced
import { collectFiles } from "@suaveplan/electrobun/generator";
import type { FsAdapter } from "@suaveplan/electrobun/types";
import { writeFiles } from "@suaveplan/electrobun/fs";
const fs: FsAdapter = /* in-memory adapter */;
const files = collectFiles(opts);
await writeFiles(opts.outputDir, files, fs);
API Reference
collectFiles(options: ProjectOptions, overrides?: CollectOverrides): GeneratedFile[].generate(options: ProjectOptions, deps?: GenerateDeps): Promise<GenerateReport>.GenerateDeps = { fs?, renderer?, fileExists?, now?, overrides? }.CollectOverrides = { renderProjectFile?, renderFeatureFile? }— the test seam.
Implementation Notes
collectFiles validates with Zod first, then iterates the file list.
Each renderer is wrapped in a try/catch that converts native errors into
ElectrobunError(ELECTROBUN.TEMPLATE_RENDER_FAILED) carrying the
faulty path (or feature name) in details. The wrap is essential
because a renderer failure surfaces as a string-typed crash at the call
site that consumed collectFiles; the typed code lets the caller branch
on "template bug" vs "Zod bug" vs "fs bug" without parsing messages.
The icon-pipeline integration is gated by a truthy
parsed.iconSource rather than != undefined, so an empty-string
iconSource (a common prompt default) skips icon generation entirely.
This matches the upstream behaviour: the prompt layer collapses an
empty answer to undefined before passing it down, but the generator
defends against the empty-string case too in case a config file
materialises one.