# 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. - `CollectOverrides` seam for test-only renderer substitution. ## Usage: Basic ```ts 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 ```ts 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`. - `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.