@diaryx/fig-wasi
v7.0.0
Published
EXPERIMENTAL: the fig CLI (parse/edit/convert JSON, JSONC, JSON5, YAML, TOML, INI, dotenv, .properties, NestedText, ZON, plist and fig — while preserving comments) as an npx-able command: the real fig CLI's WASI build under Node's built-in WASI support, w
Maintainers
Readme
fig CLI via npm / npx (experimental)
Experimental. This package is new, its underlying approach (running a WASI module through Node's own — separately, officially experimental —
node:wasisupport) has needed several non-obvious compatibility shims to work at all (see Known limitations), and its interface may still change. For anything beyond a quick one-off or CI use, prefer a native install (Homebrew, a downloaded release binary,cargo install) — see the main CLI docs.
@diaryx/fig-wasi runs the real fig command-line tool — the same
get/set/replace/rename/insert/delete/comment/check/fmt/convert actions
the native binary has — with no install step, no per-platform binary, and no
native build. It ships a single WASI (WebAssembly System Interface) module
and runs it under Node's built-in WASI support, so npx works anywhere Node
22+ does: Linux, macOS, Windows, CI containers, wherever. Every format the
native binary reads is here, ZON and plist included.
If you already have fig installed natively (Homebrew, a downloaded release
binary, cargo install, ...), you don't need this package — use the real
binary. This exists for the zero-install case: a one-off in a shell script, a
CI pipeline that already has Node but not fig, or trying it out without
committing to an install.
Install
Nothing to install — just run it:
npx @diaryx/fig-wasi get config.yamlnpx fetches the package, and because it declares a single bin (fig),
runs that command directly — you don't need npx --package=... fig. If you'd
rather have fig on your PATH permanently:
npm install -g @diaryx/fig-wasi
fig get config.yamlRequires Node 22+.
Usage
Same CLI, same actions, same flags as the native binary — see fig help and
fig <action> --help for the full reference:
npx @diaryx/fig-wasi get config.yaml
npx @diaryx/fig-wasi get -o json config.toml
npx @diaryx/fig-wasi set config.yaml server.port 9090
npx @diaryx/fig-wasi check config.yaml
npx @diaryx/fig-wasi fmt --dry-run config.yamlSet FIG_WASI_DEBUG=1 to print diagnostics (fd classifications, internal
decisions, exit code) to stderr — useful if something behaves differently on
your machine than expected; see
bin/fig.mjs.
How it works
npm run build compiles the same fig CLI source (rust/fig-cli) to a WASI
preview1 module (a real _start command, not a library) with cargo, for
wasm32-wasip1. The module this package ships is built with fig-cli's
wasi-host feature, which carries no language, and bin/fig.mjs runs it under Node's built-in node:wasi, wiring argv,
env and stdio straight through so piping and redirection behave like the
native binary. It preopens the real working directory at its own path
(/cwd on Windows) and passes it as PWD, which the CLI changes into before
reading its arguments, so a relative path names the file it names natively.
The formats are @diaryx/fig's JavaScript languages, the ones the library
package's callers import, copied into this package's lib/ at build time.
The first time the CLI meets a format, by name (-i yaml), by extension or
by sniffing a file's contents, it asks bin/fig.mjs for the language, which
answers in-process through the same wire a helper process speaks. That is
also what keeps the module small: the whole CLI with no format in it, and
the languages as JavaScript Node already runs. The standalone fig-wasi.wasm
on GitHub Releases, for wasmtime and wasmer, is built the ordinary way,
with every language compiled in, and runs as
wasmtime run --dir=. fig-wasi.wasm get config.yaml.
Known limitations
Running a real filesystem CLI inside a WASI sandbox hosted by Node has some real sharp edges — several of them not merely theoretical, but bugs that took real debugging to find:
- Absolute paths on Windows are not resolved. WASI paths are POSIX-style;
there's no single filesystem root to preopen the way POSIX gets
/. Use paths relative to the current working directory on Windows. On macOS/Linux, both relative and absolute paths work. - No TTY detection — output is always treated as "not a terminal." This
is deliberate, not just a missing feature: handing
node:wasia real TTY fd directly causesfig's own terminal/color detection to fail outright (confirmed empirically — every action exited non-zero with zero output when run directly in a real terminal, before this was worked around), so this package always proxies stdin/stdout/stderr through temp regular files unless they're already a plain regular file — which as a side effect meansfigcan never see a real TTY through this host. UseCLICOLOR_FORCE=1if you want color output regardless;NO_COLOR=1is a no-op either way since color is already off by default here. - Piping was broken without a second workaround.
node:wasiimplements reads/writes via a positional fs syscall, which the OS rejects on a pipe or socket — so piping into or out of this CLI, or a test harness/CI runner capturing output, failed outright until this package started proxying those through temp files too. - Node's WASI support is officially "Experimental" (Node's own stability
index, independent of this package's own experimental status). The surface
this package relies on —
preview1,preopens,returnOnExit— has been stable across many Node majors; this package suppresses the one-timeExperimentalWarningNode prints so it doesn't clutter normal use. - No
fig-<action>handoff. A nativefighands an action it doesn't implement to afig-<action>program on your PATH, the way git does. WASI has neitherexecnorfork, so this build has nowhere to send one and reports that instead of running it. Every actionfigimplements itself is unaffected. - Formats are JavaScript, so their words are their own. A parse error
names the same place with the language's own message, which can be worded
differently from the native binary's. The compiled parsers' lints are not
here either:
fig check --strictfails a file only for an error, not for a warning such as fig's leading-zero007. The file each reads, and what each writes, is the same. - No
languages.figl. A configured language is a helper program the CLI starts, and a WASI module cannot start one, so this build does not read the file. - Slower cold start than the native binary. Every invocation compiles the
WASI module fresh (no persistent process) — fine for occasional/CI use,
not a reason to replace a natively-installed
figfor heavy scripting. - Not a security sandbox: don't rely on this to run untrusted config files from an untrusted source and expect isolation guarantees beyond "no network/thread/socket access" — Node's own WASI documentation is explicit that its filesystem sandboxing is not a hard security boundary.
Given how many of the above turned out to be real, previously-undocumented
node:wasi gaps rather than things this package could simply configure
around, treat this package itself as experimental too, not just its
dependencies — future Node versions, or WASI programs with different I/O
patterns than fig's, may surface more.
See also
- The CLI / library — install via Homebrew or a release binary.
- fig in TypeScript — the
@diaryx/figlibrary package (parse/edit/serialize as a JS API), not a CLI.
