Initial Commit
This commit is contained in:
172
openspec/archive/2026-05-25-enterprise-electrobun/design.md
Normal file
172
openspec/archive/2026-05-25-enterprise-electrobun/design.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Design — @suaveplan/electrobun (genesis port)
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
┌───────────────────────────────────────────┐
|
||||
│ src/bin/suaveplan-electrobun │
|
||||
│ (process.argv → exit code) │
|
||||
└───────────────────┬───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────┐
|
||||
│ src/cli/cli.ts │
|
||||
│ runCli(argv) → { code } │
|
||||
│ createCommand / devCommand / buildCommand│
|
||||
│ packageCommand / signCommand │
|
||||
└───┬──────┬──────┬──────┬──────┬───────────┘
|
||||
│ │ │ │ │
|
||||
┌─────────┘ │ │ │ └──────────┐
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
generator/ runner/ runner/ runner/ runner/
|
||||
├─ collectFiles electrobun electrobun electrobun electrobun
|
||||
├─ generate dev … build … package … sign …
|
||||
│ ├─ templates/
|
||||
│ ├─ icons/
|
||||
│ ├─ fs/ (FsAdapter)
|
||||
│ └─ config/
|
||||
▼
|
||||
templates/ (project + feature renderers)
|
||||
```
|
||||
|
||||
Every IO-touching primitive accepts an injectable adapter:
|
||||
|
||||
| Adapter | Default | Used by |
|
||||
|---|---|---|
|
||||
| `FsAdapter` | `nodeFsAdapter` (real `node:fs`) | `generator`, `config`, `prompts`, `icons` |
|
||||
| `CommandRunner` | `nodeCommandRunner` (real `child_process.spawn`) | `runner`, `dev/build/package/sign` |
|
||||
| `IconRendererAdapter` | `iconzRendererAdapter` (real `iconz`) | `icons` |
|
||||
| `Prompter` | `@suaveplan/cli` prompt façade | `prompts` |
|
||||
|
||||
This is the same seam pattern `@suaveplan/linter` uses for biome / fs / glob; the
|
||||
fakes used in tests are real-behaviour fakes (temp dirs, deterministic command
|
||||
output), not `vi.mock` stubs.
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
packages/devtools/tooling/electrobun/
|
||||
package.json
|
||||
tsconfig.lib.json
|
||||
vite.config.ts
|
||||
vitest.config.ts
|
||||
README.md
|
||||
tests/
|
||||
setup/polyfills.ts
|
||||
package-shape.test.ts
|
||||
src/
|
||||
index.ts
|
||||
bin/
|
||||
suaveplan-electrobun.ts
|
||||
suaveplan-electrobun.md
|
||||
error-codes/
|
||||
error-codes.ts
|
||||
error-codes.test.ts
|
||||
error-codes.md
|
||||
index.ts
|
||||
telemetry/
|
||||
telemetry.ts
|
||||
telemetry.test.ts
|
||||
telemetry.md
|
||||
index.ts
|
||||
types/
|
||||
electrobun.types.ts
|
||||
electrobun.types.md
|
||||
index.ts
|
||||
schemas/
|
||||
schemas.ts
|
||||
schemas.test.ts
|
||||
schemas.md
|
||||
index.ts
|
||||
fs/
|
||||
fs.ts
|
||||
fs.test.ts
|
||||
fs.md
|
||||
index.ts
|
||||
runner/
|
||||
runner.ts
|
||||
runner.test.ts
|
||||
runner.md
|
||||
index.ts
|
||||
config/
|
||||
config.ts
|
||||
config.test.ts
|
||||
config.md
|
||||
index.ts
|
||||
templates/
|
||||
templates.ts
|
||||
templates.test.ts
|
||||
templates.md
|
||||
features.ts
|
||||
features.test.ts
|
||||
features.md
|
||||
index.ts
|
||||
icons/
|
||||
icons.ts
|
||||
icons.test.ts
|
||||
icons.md
|
||||
index.ts
|
||||
generator/
|
||||
generator.ts
|
||||
generator.test.ts
|
||||
generator.md
|
||||
index.ts
|
||||
prompts/
|
||||
prompts.ts
|
||||
prompts.test.ts
|
||||
prompts.md
|
||||
index.ts
|
||||
cli/
|
||||
cli.ts
|
||||
cli.test.ts
|
||||
cli.md
|
||||
index.ts
|
||||
```
|
||||
|
||||
## Error codes
|
||||
|
||||
`defineCodes("ELECTROBUN", […])` with exactly:
|
||||
|
||||
- `INVALID_OPTIONS` — Zod boundary failure.
|
||||
- `OUTPUT_NOT_EMPTY` — refusing to overwrite a non-empty target directory.
|
||||
- `CONFIG_PARSE_FAILED` — `.suaveplan-electrobun.json` exists but is malformed.
|
||||
- `ICON_SOURCE_MISSING` — `iconSource` path does not exist.
|
||||
- `ICON_RENDER_FAILED` — `iconz` raised.
|
||||
- `TEMPLATE_RENDER_FAILED` — a renderer threw.
|
||||
- `FS_WRITE_FAILED` — `writeFiles` could not commit.
|
||||
- `BINARY_UNAVAILABLE` — `bunx electrobun --version` returns non-zero.
|
||||
- `RUNNER_FAILED` — orchestrated command (dev/build/package/sign) returned non-zero.
|
||||
- `PROMPT_ABORTED` — user cancelled the interactive prompt.
|
||||
- `UNKNOWN_FLAG` — `runCli` saw an argv it does not recognise.
|
||||
|
||||
## OTel surface
|
||||
|
||||
- `electrobun_projects_generated_total` (counter; tagged `features_count`).
|
||||
- `electrobun_icons_generated_total` (counter; tagged `outcome=generated|skipped|failed`).
|
||||
- `electrobun_runner_invocations_total` (counter; tagged `command=dev|build|package|sign|probe`).
|
||||
- `electrobun_runner_failures_total` (counter; tagged `command`, `exit_code`).
|
||||
- `electrobun_prompts_total` (counter; tagged `outcome=completed|aborted`).
|
||||
- `electrobun_generate_duration_ms` (histogram; tagged `features_count`).
|
||||
- `electrobun_runner_duration_ms` (histogram; tagged `command`).
|
||||
|
||||
## Test strategy
|
||||
|
||||
Tests run under Vitest via `@suaveplan/testing/runner`. Everything IO-touching uses
|
||||
a real `mkdtemp` temp directory and the real `nodeFsAdapter` (no `vi.mock`). The
|
||||
runner surface is exercised with a real-behaviour fake `CommandRunner` that records
|
||||
the spawn args and returns a canned `CommandResult`; one integration scenario
|
||||
exercises the real `nodeCommandRunner` against `node --version` to prove the seam
|
||||
default works end-to-end. The icon renderer is exercised with a real-behaviour
|
||||
fake `IconRendererAdapter` that asserts the option shape passed to `iconz`; the
|
||||
real `iconz` path is a one-line dynamic-import call covered by the default
|
||||
adapter's smoke test.
|
||||
|
||||
## Why `runner` is the right level of abstraction
|
||||
|
||||
Electrobun's dev/build/package/sign pipeline is implemented inside the
|
||||
`electrobun` binary that ships with the user's project. Re-implementing it here
|
||||
would duplicate the upstream and silently drift. Instead the wrapper shapes the
|
||||
argv, runs the binary through the `CommandRunner` seam, parses stdout/exit code,
|
||||
and produces a structured `*Report` that callers (CI, IDE extensions) can act on.
|
||||
This keeps the package thin enough to test exhaustively while still adding real
|
||||
value over directly calling `bunx electrobun build`.
|
||||
Reference in New Issue
Block a user