Skip to content

CLI Reference

The @gjsify/cli package ships the gjsify binary. Run it via npx @gjsify/cli <command> or add it as a dev dependency.

Tip: npx @gjsify/cli --help lists all commands and flags.

Scaffold a new GJSify project.

npx @gjsify/cli create my-app
ArgumentDescriptionDefault
[project-name]Directory to createmy-gjs-app

Generates src/index.ts, a package.json with build/start/dev scripts, and a tsconfig.json. Internally delegates to @gjsify/create-app.

Compile and bundle with Rolldown (Vite 8’s production bundler). Automatically aliases Node.js and Web API imports to @gjsify/* equivalents for GJS.

npx @gjsify/cli build src/index.ts --outfile dist/index.js
OptionValuesDefaultDescription
[entryPoints..]pathssrc/index.tsPositional entry points
--appgjs | node | browsergjsTarget runtime
--outfile, -opathfrom package.jsonOutput file
--outdir, -dpathfrom package.jsonOutput directory (library mode)
--minifyboolfalseMinify the output
--globalsstring"auto"Globals mode (see below)
--shebangboolfalsePrepend #!/usr/bin/env -S gjs -m to the outfile and chmod it 0o755. Only with --app gjs and a single --outfile.
--verboseboolfalseShow detected globals and build details
All build options
OptionValuesDefaultDescription
--formatiife | esm | cjsautoOverride output format
--libraryboolfalseBuild as a reusable library
--reflection, -rboolfalseEnable TypeScript runtime types via Deepkit
--console-shimbooltrueInject the GJS console shim. Disable with --no-console-shim
--excludeglob[][]Glob patterns to exclude from entry points and aliases
--log-levelsilent | error | warning | info | debug | verbosewarningBundler log level
--externalname[][]Module specifiers that should NOT be bundled — they remain as runtime imports. Repeatable; comma-separated values are split. Merged with the platform’s built-in externals.
--defineKEY=VALUE[][]Bundler define pass-through. VALUE is a JS expression — string literals must be JSON-quoted (--define VERSION='"1.2.3"'). Repeatable. Merged with built-in defines like global: 'globalThis'.
--aliasFROM=TO[][]Layered on top of the built-in alias map. Useful for stubbing heavy deps (--alias typedoc=@gjsify/empty). Repeatable.

For --app gjs, the target is firefox140 (SpiderMonkey 140) and gi://*, cairo, system and gettext are externalised. For --app node, the target is node24.

Bundling third-party CLIs that read their own package.json at load time

Section titled “Bundling third-party CLIs that read their own package.json at load time”

Tools like typedoc and prettier read their own package.json via Path.join(fileURLToPath(import.meta.url), '../../../package.json') during top-level evaluation. When bundled, import.meta.url resolves to the bundle file, not the original main, so the lookup escapes the package and crashes. Combine --external (so Node’s runtime resolver finds the package in node_modules) with --define for any build-time version constants the bundled code expects:

gjsify build src/cli.entry.ts --app node --outfile dist/cli.mjs \
--define '__MY_VERSION__="1.0.0"' \
--external typedoc,prettier,@inquirer/prompts,inquirer

--external is the right answer on Node. On GJS, gjsify run does not yet have a node_modules-style runtime resolver, so externalised packages fail with ImportError: Module not found. Bundling them is the only option on GJS today; the alternative is upstream switching to a --define-injected version constant.

The default --globals auto detects which globals your code needs from the bundled output. No configuration needed for most projects.

ModeUsageDescription
auto--globals auto (default)Automatic detection from bundled output
auto,<extras>--globals auto,domAuto + explicit extras for hard-to-detect cases
explicit list--globals fetch,BufferFully explicit, no auto detection
none--globals noneDisable globals injection

Groups: node (Buffer, process, URL, …), web (fetch, streams, crypto, …), dom (document, Image, navigator, …).

How auto detection works

The CLI runs an iterative multi-pass build:

  1. Pass 1 bundles your code in memory (no globals injected). acorn parses the output and finds free identifiers (Buffer, fetch) plus host-object member expressions (globalThis.Buffer, self.Buffer).
  2. Pass N injects the discovered globals and rebuilds. Newly injected modules can reference more globals — the loop repeats until the set stabilises (typically 2–3 iterations, capped at 5).
  3. The final build uses the converged set.

Because detection runs on the tree-shaken bundle, isomorphic guards (typeof document !== 'undefined') never produce false positives.

Use --verbose to see what auto mode detected:

gjsify build src/index.ts -o dist/index.js --verbose
# [gjsify] --globals auto: converged after 2 iteration(s), 11 global(s):
# AbortSignal, Buffer, HTMLElement, document, fetch, navigator, …
When auto can't see a global

The analyser cannot follow value-flow indirection — e.g. Excalibur stores globalThis in a variable and calls methods through it, hiding the access. Keep auto on and add extras:

# Auto + the entire DOM group
gjsify build src/gjs/gjs.ts -o dist/gjs.js --globals auto,dom
# Auto + specific identifiers
gjsify build src/index.ts -o dist/index.js --globals auto,matchMedia,FontFace

Extras are seeded into pass 1 so any code reachable only through them is also visible.

Known identifiers

Any identifier below can appear in --globals (or be detected automatically). Granular subpaths mean selecting Buffer does NOT also pull in process or URL.

Node.js core globals

Identifier(s)Register subpath
Buffer, Blob, File@gjsify/buffer/register
process@gjsify/node-globals/register/process
setImmediate, clearImmediate@gjsify/node-globals/register/timers
queueMicrotask@gjsify/node-globals/register/microtask
structuredClone@gjsify/node-globals/register/structured-clone
btoa, atob@gjsify/node-globals/register/encoding
URL, URLSearchParams@gjsify/node-globals/register/url

Fetch

Identifier(s)Register subpath
fetch, Headers, Request, Responsefetch/register/fetch

Streams

Identifier(s)Register subpath
ReadableStreamweb-streams/register/readable
WritableStreamweb-streams/register/writable
TransformStreamweb-streams/register/transform
TextEncoderStream, TextDecoderStreamweb-streams/register/text-streams
ByteLengthQueuingStrategy, CountQueuingStrategyweb-streams/register/queuing
CompressionStream, DecompressionStreamcompression-streams/register

Crypto

Identifier(s)Register subpath
cryptowebcrypto/register

Abort + Events

Identifier(s)Register subpath
AbortController, AbortSignalabort-controller/register
Event, EventTargetdom-events/register/event-target
CustomEvent, MessageEvent, ErrorEvent, CloseEvent, ProgressEventdom-events/register/custom-events
UIEvent, MouseEvent, PointerEvent, KeyboardEvent, WheelEvent, FocusEventdom-events/register/ui-events
EventSourceeventsource/register
DOMExceptiondom-exception/register

Performance + FormData

Identifier(s)Register subpath
performance, PerformanceObserver@gjsify/web-globals/register/performance
FormData@gjsify/web-globals/register/formdata

XHR / DOMParser / Audio / Gamepad (GJS-only)

Identifier(s)Register subpath
XMLHttpRequest@gjsify/xmlhttprequest/register
DOMParser@gjsify/domparser/register
AudioContext, webkitAudioContext, Audio, HTMLAudioElement@gjsify/webaudio/register
GamepadEvent@gjsify/gamepad/register

WebRTC (GStreamer webrtcbin, GJS-only)

Identifier(s)Register subpath
RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, RTCPeerConnectionIceEvent@gjsify/webrtc/register/peer-connection
RTCDataChannel, RTCDataChannelEvent@gjsify/webrtc/register/data-channel
RTCError, RTCErrorEvent@gjsify/webrtc/register/error
MediaStream, MediaStreamTrack, RTCTrackEvent@gjsify/webrtc/register/media
MediaDevices (navigator.mediaDevices)@gjsify/webrtc/register/media-devices

WebAssembly Promise APIs

Identifier(s)Register subpath
WebAssembly (WebAssembly.compile, WebAssembly.instantiate, WebAssembly.validate, WebAssembly.compileStreaming, WebAssembly.instantiateStreaming)webassembly/register/promise

DOM / browser-compat (GJS/GTK only)

Identifier(s)Register subpath
document, HTMLElement@gjsify/dom-elements/register/document
HTMLCanvasElement@gjsify/dom-elements/register/canvas
Image, HTMLImageElement@gjsify/dom-elements/register/image
MutationObserver, ResizeObserver, IntersectionObserver@gjsify/dom-elements/register/observers
FontFace@gjsify/dom-elements/register/font-face
matchMedia@gjsify/dom-elements/register/match-media
location@gjsify/dom-elements/register/location
navigator@gjsify/dom-elements/register/navigator

Unknown identifiers are silently ignored. If a ReferenceError: X is not defined still happens, add X as an extra: --globals auto,X.

Run the GJS bundle of an npm-published package without persisting it in your project. Pattern follows npx / yarn dlx / pnpm dlx, but dlx here is strictly a GJS-bundle runner: it always invokes gjs -m <bundle> after resolving the package’s GJS entry. Packages without a GJS entry fail loudly.

gjsify dlx @gjsify/example-dom-canvas2d-fireworks
gjsify dlx @scope/pkg@1.2.3 # version-pinned
gjsify dlx @scope/pkg my-bin -- --opt value # pick a bin from gjsify.bin, forward args
gjsify dlx ./local/path # local dir (no install, no cache)
OptionDescription
<spec>Package spec (name, name@version, @scope/name@spec, or local path).
[binOrArg]Bin name when gjsify.bin has multiple entries; otherwise treated as the first arg forwarded to the bundle.
[extraArgs..]Extra args forwarded to gjs -m <bundle>.
--cache-max-age <minutes>Cache TTL. Defaults to 7 days. Use 0 to bypass cache.
--registry <url>npm registry override (writes a temp .npmrc in the cache dir).
--verbosePass --loglevel verbose to npm.

Cache layout: $XDG_CACHE_HOME/gjsify/dlx/<sha256>/. Cache-key is sha-256 over the sorted package specs and registries. Atomic symlink swap on first install means parallel dlx invocations of the same spec are safe.

dlx (and the future gjsify install / gjsify showcase) read the package’s GJS entry from a top-level gjsify object:

{
"name": "@gjsify/example-dom-canvas2d-fireworks",
"main": "dist/node.js", // optional Node entry
"exports": { "./browser": "..." },
"gjsify": {
"main": "dist/gjs.js", // GJS entry — used by `gjsify dlx <pkg>`
"bin": { // optional: multiple GJS entries
"fireworks": "dist/gjs.js"
},
"prebuilds": "prebuilds" // existing convention for native prebuilds
}
}

Resolution order:

  1. user-supplied bin name + gjsify.bin[name] → that path
  2. single-entry gjsify.bin → the only path (auto-pick)
  3. gjsify.main → that path
  4. fallback: top-level package.json#main → that path (advisory warning to add gjsify.main)
  5. otherwise: hard-fail with a fix hint

Multi-bin packages without a chosen bin fail with dlx: package "X" defines multiple GJS bins — pass one of: a, b.

Run a built GJS bundle. Automatically sets LD_LIBRARY_PATH and GI_TYPELIB_PATH for native prebuilds.

npx @gjsify/cli run dist/index.js
ArgumentDescription
<file>The GJS bundle to run
[args..]Extra arguments passed through to gjs
Running without gjsify run

gjsify run is a convenience wrapper. Without native prebuilds, you can run gjs directly:

gjs -m dist/index.js

With native prebuilds, generate the environment:

eval $(npx @gjsify/cli info --export)
gjs -m dist/index.js

Verify that required system dependencies are installed.

npx @gjsify/cli check

Reports an install command for your detected package manager when something is missing. Exits with code 1 if any required dependency is missing.

Required vs optional dependencies

Required — always checked:

  • Build toolchain: gjs, pkg-config, meson, blueprint-compiler
  • Foundational libraries: gtk4, libadwaita-1, libsoup-3.0, gobject-introspection-1.0

Optional — only checked if a corresponding @gjsify/* package is in your project:

Optional depRequired by
libmanette-0.2@gjsify/gamepad
gstreamer-1.0, gstreamer-app-1.0@gjsify/webaudio
gstreamer-webrtc-1.0, gstreamer-sdp-1.0, libnice@gjsify/webrtc
webkitgtk-6.0@gjsify/iframe
gdk-pixbuf-2.0@gjsify/dom-elements, @gjsify/canvas2d
pango, pangocairo, cairo@gjsify/canvas2d
gwebgl (npm package)@gjsify/webgl
JSON output
npx @gjsify/cli check --json
{
"packageManager": "dnf",
"deps": [
{
"id": "gjs",
"name": "GJS",
"found": true,
"version": "1.86.0",
"severity": "required"
},
{
"id": "manette",
"name": "libmanette",
"found": false,
"severity": "optional",
"requiredBy": ["@gjsify/gamepad"]
}
]
}

List native GJSify packages and show the LD_LIBRARY_PATH / GI_TYPELIB_PATH needed for gjs.

npx @gjsify/cli info dist/index.js
eval $(npx @gjsify/cli info --export)
Argument / OptionDescription
[file]Bundle path used in the generated example command
--exportEmit only shell export statements, suitable for eval

List or run the curated showcase applications bundled with the CLI.

npx @gjsify/cli showcase # list all
npx @gjsify/cli showcase three-geometry-teapot
OptionDefaultDescription
[name]Showcase name to run. Omit to list available showcases
--listfalseForce list mode
--jsonfalseOutput as JSON (list mode only)

Compile a GResource XML descriptor into a binary .gresource bundle. Thin wrapper around glib-compile-resources — useful when you want to package UI templates and assets into the app bundle without pulling in meson/autotools.

npx @gjsify/cli gresource data/org.example.App.data.gresource.xml \
--sourcedir data \
--target dist/org.example.App.data.gresource
OptionDefaultDescription
<xml>Path to the .gresource.xml descriptor (required)
--sourcedirdirname(<xml>)Directory containing the resource files referenced by <xml>
--target, -t<xml-without-.xml> next to <xml>Output .gresource file
--verbosefalsePrint the underlying glib-compile-resources invocation

Requires glib-compile-resources (package: glib2-devel on Fedora, libglib2.0-dev-bin on Debian/Ubuntu).

See it in action: adwaita-package-builder showcase embeds style.css this way.

Compile gettext .po files for GNOME apps. Wraps msgfmt with the common output shapes — per-language locale tree (.mo), metainfo template substitution (.xml), and two less-common formats (.desktop, .json).

# Compile to runtime .mo locale tree
npx @gjsify/cli gettext translations dist/locale --domain org.example.App
# Substitute a metainfo template via msgfmt --xml --template
npx @gjsify/cli gettext translations dist/metainfo \
--domain org.example.App \
--format xml \
--metainfo data/metainfo/org.example.App.metainfo.xml.in
OptionDefaultDescription
<poDir>Directory containing <lang>.po files (required)
<outDir>Output directory (locale tree for --format=mo, plain dir otherwise) (required)
--domainText domain / application ID (required)
--formatmoOne of mo, xml, desktop, json
--metainfoFor --format=xml: path to the template (.metainfo.xml.in) used as msgfmt --template
--filename<domain>.<ext>Override the output filename
--remove-xml-commentstrueFor --format=xml: strip XML comments from the compiled output
--verbosefalsePrint each msgfmt invocation

Requires msgfmt (package: gettext).

See it in action: adwaita-package-builder showcase uses both --format mo (runtime .mo tree) and --format xml --metainfo (AppStream substitution).

Before running, gjsify showcase calls gjsify check to verify system dependencies.