Files
skills/docs/devtools/tooling/electrobun/src/generator/generator.md
2026-05-29 08:20:56 +01:00

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.
  • CollectOverrides seam 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.