rebar3_asobi_console (asobi v0.84.0)

View Source

rebar3 asobi console - compose an operator console out of core's screens and every installed extension's.

rebar3 asobi console          # build the composed bundle
rebar3 asobi console --dev    # Vite dev server, proxied at a running node

An extension owns its operator UI the way it owns its migrations and its RPC handlers: as source in its own application, under priv/console, with index.jsx as the entry. Nothing is registered and nothing is declared - discovery is the file being there, the same way migrations and schemas are discovered rather than declared.

Why this is a build step and not a runtime load

The console's CSP is script-src 'nonce-<per-response>' with no host source (asobi_console_csp). A nonce does not propagate to a module's static imports, so a second chunk fetched at runtime is refused by the browser - which is why console/vite.config.js pins a single chunk in the first place. Every runtime plugin mechanism there is (dynamic import(), module federation, an import map) needs either 'strict-dynamic' or a host source in script-src, and the console's strongest property is that even an injected <script src> pointing at its own origin is refused.

So the composition happens where it costs nothing: at build time, producing one chunk exactly as before, just a larger one. The policy is not touched.

What it does

  1. Runs asobi_extensions:check/0 - the same validation rebar3 asobi check runs - so a broken extension set fails here rather than after an npm install.
  2. Copies asobi's console/ into _build/asobi_console, which is where everything generated lives. Neither the asobi dependency nor, when asobi is the project itself, its source tree is written to.
  3. Symlinks each extension's priv/console under extensions/<name>, and generates src/registry.generated.js importing each one.
  4. npm ci from asobi's own committed lockfile. Extensions have no package.json, no node_modules and no React of their own: they ship JSX source, so the whole composed bundle resolves one copy of everything.
  5. vite build into the console_bundle_app's priv/console.

Symlinks rather than absolute paths in the generated imports, with resolve.preserveSymlinks on: it keeps every source file under Vite's root, which is what makes the dev server able to serve and hot-reload an extension's JSX. Without preserveSymlinks Vite resolves through to the real path, the file is outside the root again, and the dev server refuses it.

What the host has to set

In rebar.config, the application the built bundle is written into:

{asobi, [{console_bundle_app, game_console}]}.

and the same name in sys.config, which is what makes the node serve it:

{asobi, [{console, true}, {console_bundle_app, game_console}]}.

Two places because they answer two questions - where the build writes, and what the node reads - and a release can be built on one machine and configured on another. asobi_console refuses a console_bundle_app that is not in the release rather than falling back to asobi's own bundle, so the two disagreeing is loud.

See guides/console-extensions.md.

Summary

Functions

do(State)

-spec do(term()) -> {ok, term()} | {error, string()}.

format_error/1

-spec format_error(term()) -> string().

init(State)

-spec init(term()) -> {ok, term()}.