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:
- every failure path is either an
Errorliteral or aconsole.log, so consumers cannot discriminate between "icon source missing" and "rollup chain blew up"; - no OpenTelemetry surface for what is the entry point new contributors hit first;
- the generator is hard-wired to
node:fswith no seam for tests or for embedding in CI; - only the
createstep is covered — operators have nothing fordev(watch + restart),build(cross-platform packaging),package(release-bundle assembly),sign(notarization + entitlements), or the auto-update pipeline; and - the
renderXxxFeature()template functions are inlined-string blobs with no validation of the render input, so a staleProjectOptionsshape 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", [...])andElectrobunError.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/—nodeFsAdapterand seam-drivenensureDirectory,directoryExists,directoryIsEmpty,writeFiles,findConfigFile.runner/—CommandRunnercontract +nodeCommandRunnerdefault (spawn-based).config/—loadConfig(cliArgs, opts?)consuming the@suaveplan/config5-layer pipeline (defaults / file / CLI) with theFsAdapterinjected 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) plustemplates/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 viaFsAdapter, returns aGenerateReport.icons/—generateIcons(opts, deps?)— wrapsiconzvia theIconRendererAdapterseam.prompts/—runPrompts(config, outputDir, prompter?).cli/— five pure functions (createCommand,devCommand,buildCommand,packageCommand,signCommand) andrunCli(argv, opts?). Each returns a*Report; none callprocess.exit.bin/—suaveplan-electrobunbinary wiringrunClithroughprocess.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
iconzESM resolution. Upstream loadsiconz/presetsdynamically. The genesis port keeps the dynamic-import path behind theIconRendererAdapterseam so tests can swap in a fake renderer without paying theiconzcost.electrobunbinary availability.dev/build/package/signshell out to the user's localelectrobunbinary (installed in the generated project'snode_modules). The wrapper probes for it via theCommandRunnerseam and raisesELECTROBUN.BINARY_UNAVAILABLEwith a remediation hint when missing.- Signing requires real certs. The
signcommand'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 buildproduces platform-specific outputs; the wrapper records the requested target triple (mac/linux/windows) in thePackageReportso CI can branch on it.