Files
suaveplan/openspec/archive/2026-05-25-enterprise-electrobun/proposal.md
2026-05-27 23:29:53 +01:00

7.1 KiB

Proposal — Migrate @suaveplan/electrobun to genesis

  • Design: ./design.md
  • Spec delta: ./specs/electrobun/spec.md

Why

Upstream @suaveplan/electrobun (/mnt/s/web/suaveplan/packages/universal/electrobun/) is a thin scaffolder: it prompts the user for an app name and a feature list, runs an iconz-backed icon pipeline, and emits a fixed-template Electrobun project to disk. The genesis enterprise/electrobun.md audit highlights five structural gaps:

  1. every failure path is either an Error literal or a console.log, so consumers cannot discriminate between "icon source missing" and "rollup chain blew up";
  2. no OpenTelemetry surface for what is the entry point new contributors hit first;
  3. the generator is hard-wired to node:fs with no seam for tests or for embedding in CI;
  4. only the create step is covered — operators have nothing for dev (watch + restart), build (cross-platform packaging), package (release-bundle assembly), sign (notarization + entitlements), or the auto-update pipeline; and
  5. the renderXxxFeature() template functions are inlined-string blobs with no validation of the render input, so a stale ProjectOptions shape silently produces a broken project.

Genesis cleans up the contract: every failure raises a typed ElectrobunError carrying a stable ELECTROBUN.* code; every command and every render-helper takes an injectable seam (FsAdapter, CommandRunner, IconRendererAdapter, Prompter); every public option is Zod-validated; the CLI grows from one command (create) to five (create, dev, build, package, sign); and an OTel surface (5 counters + 2 histograms) makes the package observable in CI.

What

Land @suaveplan/electrobun@0.1.0 at packages/devtools/tooling/electrobun/ with the following feature modules (one folder per feature, sibling .md per implementation file; no electrobun- prefix inside the package):

  • error-codes/ — ELECTROBUN_CODES = defineCodes("ELECTROBUN", [...]) and ElectrobunError.
  • telemetry/ — OTel counters + histograms (projects_generated_total, icons_generated_total, runner_invocations_total, runner_failures_total, prompts_total, generate_duration_ms, runner_duration_ms).
  • types/ — shared interfaces (ProjectOptions, ElectrobunConfig, GeneratedFile, FsAdapter, CommandRunner, CommandInput, CommandResult, IconRendererAdapter, Prompter, every *Report / *Options).
  • schemas/ — Zod schemas for every public type; both schema and inferred type exported.
  • fs/ — nodeFsAdapter and seam-driven ensureDirectory, directoryExists, directoryIsEmpty, writeFiles, findConfigFile.
  • runner/ — CommandRunner contract + nodeCommandRunner default (spawn-based).
  • config/ — loadConfig(cliArgs, opts?) consuming the @suaveplan/config 5-layer pipeline (defaults / file / CLI) with the FsAdapter injected for file discovery.
  • templates/ — render helpers for every project file (electrobun-config, main-process, package-json, tsconfig, readme, mainview-html, mainview-css, mainview-entry, shared-types) plus templates/features/ for every feature renderer (i18n, network, network-full, api, api-schemas, storage, auth, form-builder, r3f).
  • generator/ — collectFiles(options), generate(options, opts?) — orchestrates the renderer table, writes via FsAdapter, returns a GenerateReport.
  • icons/ — generateIcons(opts, deps?) — wraps iconz via the IconRendererAdapter seam.
  • prompts/ — runPrompts(config, outputDir, prompter?).
  • cli/ — five pure functions (createCommand, devCommand, buildCommand, packageCommand, signCommand) and runCli(argv, opts?). Each returns a *Report; none call process.exit.
  • bin/ — suaveplan-electrobun binary wiring runCli through process.argv.slice(2).

Parity Checklist

Capability Upstream Migrated Notes
ProjectOptions / ElectrobunConfig types upstream yes Preserved; now Zod-validated.
FEATURE_LABELS, R3F_MODULE_LABELS upstream yes Preserved.
FeatureModule, NetworkingComplexity, R3fModule unions upstream yes Preserved verbatim.
loadConfig(cliArgs) layered loader upstream yes Now consumes @suaveplan/config's 5-layer pipeline; findConfigFile is FsAdapter-driven.
directoryExists / directoryIsEmpty / ensureDirectory / writeFiles upstream yes Preserved; injectable FsAdapter.
collectFiles(options) core file list upstream yes Preserved with deterministic ordering.
generate(options) orchestrator upstream enhanced Returns GenerateReport; emits OTel histogram.
generateIcons(input, outputDir) upstream enhanced Takes an IconRendererAdapter seam; default = real iconz. Returns IconReport.
Project file renderers (9) upstream yes Same outputs, deterministic.
Feature renderers (8) upstream yes Same outputs.
Interactive create command upstream yes Preserved; Zod-validated; --skip-prompts honoured.
dev / build / package / sign commands none added Orchestrate the upstream electrobun binary via CommandRunner; report shape + error classification.
Auto-update bundling none added Surfaced via buildOptions.update → electrobun build --update argv.
Code-signing options none added signCommand exposes identity / entitlements / notarization; the wrapper shapes args + classifies errors.
Error-code registry none added ELECTROBUN.* namespace, eleven codes.
OTel self-telemetry none added 5 counters + 2 histograms.
Zod-validated options none added Every public option is Zod-validated.
Subpath exports per feature none added One subpath export per feature module.

Impact

Affected dependents

Run bunx turbo ls --filter=...@suaveplan/electrobun to enumerate. Net-new package on genesis — no current dependents.

  • No dependents (verified by bunx turbo ls --filter=...@suaveplan/electrobun).

Risks

  • iconz ESM resolution. Upstream loads iconz/presets dynamically. The genesis port keeps the dynamic-import path behind the IconRendererAdapter seam so tests can swap in a fake renderer without paying the iconz cost.
  • electrobun binary availability. dev/build/package/sign shell out to the user's local electrobun binary (installed in the generated project's node_modules). The wrapper probes for it via the CommandRunner seam and raises ELECTROBUN.BINARY_UNAVAILABLE with a remediation hint when missing.
  • Signing requires real certs. The sign command's test surface asserts only the argument-shape and error-classification contract; the real signing pipeline runs against the user's keychain / Apple-developer-id / Windows-PFX and is exercised by an opt-in integration scenario that probes the runner.
  • Cross-platform packaging. electrobun build produces platform-specific outputs; the wrapper records the requested target triple (mac / linux / windows) in the PackageReport so CI can branch on it.