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. The output ends with a Running on … line showing the active runtime — Running on Node.js v24.x.y when invoked via the Node bin, or Running on GJS 1.x.y (SpiderMonkey) when running the GJS bundle.
Prepend a target-appropriate shebang to the outfile and chmod it 0o755: #!/usr/bin/env -S gjs -m for --app gjs, #!/usr/bin/env node for --app node. Applies to GJS and Node app builds with a single --outfile.
--watch, -w
bool
false
Watch source files and rebuild on change. Logs each rebuild with duration; SIGINT cleanly stops the watcher. Rejected with --library. Requires the npm rolldown engine — run under Node.
--verbose
bool
false
Show detected globals and build details
All build options
Option
Values
Default
Description
--format
iife | esm | cjs
auto
Override output format
--library
bool
false
Build as a reusable library
--reflection, -r
bool
false
Enable TypeScript runtime types via Deepkit
--console-shim
bool
true
Inject the GJS console shim. Disable with --no-console-shim
--exclude
glob[]
[]
Glob patterns to exclude from entry points and aliases
--log-level
silent | error | warning | info | debug | verbose
warning
Bundler log level
--external
name[]
[]
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.
--define
KEY=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'.
--alias
FROM=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
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:
--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.
bundler is a RolldownOptions subset. The orchestrator applies platform-specific defaults on top.
define placement:define belongs under bundler.transform.define, not at the top level of bundler. If you write bundler.define (a common mistake when flat-renaming the old esbuild key), gjsify auto-maps it to bundler.transform.define and emits a build-time warning. To suppress the warning, move it:
// ❌ silently wrong without the alias — now warned + auto-fixed
Map of file extension (with leading .) to a loader kind. Rolldown does not classify unknown extensions natively; without a loader it tries to parse them as JS and fails.
{
"gjsify": {
"loaders": {
".glsl": "text",
".ui": "text",
".asm": "text",
".png": "dataurl"
}
}
}
Kind
Output
Use case
'text'
export default "<file contents>"
GLSL shaders, GtkBuilder .ui XML, assembly source, etc.
'dataurl'
export default "data:<mime>;base64,<b64>"
Images for Excalibur ImageSource, any API that accepts a data: URL string
The default --globals auto detects which globals your code needs from the bundled output. No configuration needed for most projects.
Mode
Usage
Description
auto
--globals auto (default)
Automatic detection from bundled output
auto,<extras>
--globals auto,dom
Auto + explicit extras for hard-to-detect cases
explicit list
--globals fetch,Buffer
Fully explicit, no auto detection
none
--globals none
Disable 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:
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).
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).
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:
gjsifybuildsrc/index.ts-odist/index.js--verbose
# [gjsify] --globals auto: converged after 2 iteration(s), 11 global(s):
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:
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.
Install npm dependencies for a project (or globally with -g). Drop-in for npm install / yarn install. Uses a native install backend (@gjsify/semver + @gjsify/npm-registry + @gjsify/tar) — no Node or npm CLI at runtime.
gjsifyinstall# full project install
gjsifyinstall--immutable# CI mode — install strictly from gjsify-lock.json
gjsifyinstalllodash# add lodash, save to dependencies
gjsifyinstall-Dvitest# save to devDependencies
gjsifyinstall-g@gjsify/cli# global install under ~/.local/share/gjsify/global/
Option
Default
Description
[packages..]
—
Optional package specs. Omit for full project install.
-g, --global
false
Install into ~/.local/share/gjsify/global/ and symlink bins into ~/.local/bin/.
-D, --save-dev
false
Save to devDependencies.
--save-peer
false
Save to peerDependencies.
-O, --save-optional
false
Save to optionalDependencies.
--immutable
false
Refuse to update gjsify-lock.json; fail if it’s missing or stale. Equivalent to yarn --immutable / npm ci --frozen-lockfile.
--verbose
false
Per-package install log.
Resolver mirrors npm v3+ semantics: each (requester → dep → range) edge is checked against the ancestor node_modules chain; compatible placements are reused, conflicting ones are nested. Supports npm-style overrides and yarn-style resolutions in package.json. Lockfile schema is v2 (path-keyed packages map).
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.
gjsifydlx@gjsify/example-dom-canvas2d-fireworks
gjsifydlx@scope/pkg@1.2.3# version-pinned
gjsifydlx@scope/pkgmy-bin----optvalue# pick a bin from gjsify.bin, forward args
gjsifydlx./local/path# local dir (no install, no cache)
Option
Description
<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).
--verbose
Pass --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.
Build + run the package’s src/test.mts aggregator on GJS and Node and aggregate the results. Replaces the per-package build:test:{gjs,node} + test:{gjs,node} + test script boilerplate.
# Default: build + run on both runtimes, aggregate exit codes
npx@gjsify/clitest
# Only one runtime
npx@gjsify/clitest--runtimegjs
npx@gjsify/clitest--runtimenode
# Skip building (assume bundles already exist)
npx@gjsify/clitest--no-build
# Force rebuild even if bundles look fresh
npx@gjsify/clitest--rebuild
Option
Default
Description
--runtime <gjs|node|all>
all
Which runtime(s) to build + run.
--entry <path>
gjsify.test.entry or src/test.mts
Test entry.
--outdir <path>
gjsify.test.outdir or dist/
Where to write test.{gjs,node}.mjs.
--rebuild
false
Always rebuild, even when outputs look fresh (mtime-based check).
--build
true
Build before running. --no-build skips when bundles already exist.
npx@gjsify/clicheck--include'@gjsify/process'# filter to one package
npx@gjsify/clicheck--no-parallel--verbose# sequential with per-workspace output
Behaviour:
In a workspace root: walks every package whose package.json#scripts.check is defined (@girs/* always excluded), runs npm run check in each, aggregates exit codes. Parallel by default with os.cpus().length workers; --jobs N caps it, --no-parallel switches to sequential.
In a single package (cwd inside a workspace package with a check script): runs that package’s check script directly. Useful for npm run check ergonomics from gjsify.
Exits with code 1 if any workspace’s check fails; 0 if all pass. With --no-parallel, exits with the first non-zero code; with --parallel (default), prints a summary of failed workspaces.
Option
Default
Description
--include <glob>
(all)
Repeatable. Only run in workspaces matching these glob patterns.
--exclude <glob>
@girs/*
Repeatable. Skip workspaces matching these glob patterns.
--parallel, -p
true
Run workspace checks in parallel. --no-parallel for sequential.
Discover every *.story.ts in the project and launch the GTK/Adwaita component browser from @gjsify/storybook — a sidebar grouped by category, a live preview pane, and an auto-generated controls panel. No per-project storybook application to maintain.
gjsifystorybook# discover src/**/*.story.ts and launch
gjsifystorybook--storiespackages# scan a different directory
gjsifystorybook--watch# rebuild + relaunch on story change
Option
Default
Description
--stories <dir>
src or gjsify.storybook.stories
Directory scanned recursively for *.story.ts.
--app-id <id>
gjsify.storybook.applicationId or derived from the package name
GApplication id.
--title <text>
—
Window title.
--globals <value>
auto
Value for gjsify build --globals (use auto,dom for canvas/DOM stories).
--out <path>
node_modules/.cache/gjsify-storybook
Output bundle path.
--watch
false
Rebuild and relaunch when a story file changes.
--build-only
false
Build the bundle but do not launch it.
Configure defaults in package.json#gjsify.storybook (applicationId, title, stories, globals). With GJSIFY_DEVTOOLS=1, the storybook host also exposes the devtools control plane, so an agent can drive it via gjsify debug --profile storybook. See the Debugging & remote control guide.
Launch an MCP↔devtools bridge for a running, devtools-enabled gjsify app (its org.gjsify.Devtools DBus control plane). An MCP client (e.g. Claude Code) uses this as its server command; the bridge speaks JSON-RPC on stdio and translates each tool call to the app’s DBus interface. Provided by @gjsify/devtools-mcp.
gjsifydebug--build-only--outdist/bridge.gjs.mjs# build once; point .mcp.json at the bundle
Option
Default
Description
--bus-name <name>
gjsify.devtools.busNameBase, or the storybook/browser app-id
App DBus base name.
--profile <kind>
auto
generic | storybook | browser | cdp. Auto: storybook if @gjsify/storybook is a dep, browser if @gjsify/devtools-browser is, cdp if @gjsify/devtools-cdp is, else generic.
--globals <value>
auto
Value for gjsify build --globals.
--out <path>
node_modules/.cache/gjsify-debug
Output bundle path.
--build-only
false
Build the bridge bundle but do not launch it (for a .mcp.json command pointing at the bundle).
MCP cleanliness:gjsify debug logs only to stderr — stdout is the JSON-RPC channel. The bridge resolves @gjsify/devtools-mcp from the consumer’snode_modules. Full workflow: Debugging & remote control guide.
Launch the minimalist Adwaita web browser from @gjsify/devtools-browser, optionally pointed at a URL. With --devtools it exposes the same org.gjsify.Devtools control plane (browser profile), so an agent can navigate, screenshot the rendered page, eval JS, inspect elements, and read the DOM/network/accessibility trees over MCP — purpose-built for debugging gjsify-built web apps.
gjsifybrowse# open page:welcome
gjsifybrowsehttps://gnome.org# open a URL
gjsifybrowsehttps://localhost:8080--devtools# + MCP control plane (drive via `gjsify debug --profile browser`)
Option
Default
Description
[url]
page:welcome
Initial URL (a page:* built-in page or https://…).
--app-id <id>
gjsify.browse.applicationId or derived from the package name
GApplication id.
--title <text>
—
Window title.
--globals <value>
auto,dom
Value for gjsify build --globals (WebKit/iframe need DOM globals).
--out <path>
node_modules/.cache/gjsify-browse
Output bundle path.
--devtools
false
Enable the MCP devtools control plane (sets GJSIFY_DEVTOOLS=1).
--inspector-port <n>
—
Enable WebKit’s remote inspector protocol on this port + the Cdp* deep-protocol methods (implies --devtools).
--build-only
false
Build the bundle but do not launch it.
The browser is built on @gjsify/iframe (a WebKit.WebView postMessage bridge). With --inspector-port, it also sets WEBKIT_INSPECTOR_HTTP_SERVER and exposes the @gjsify/devtools-cdpCdp* methods (CdpDiscoverTargets / CdpConnect / CdpSend / CdpDrainEvents) over the control plane — the deep Runtime/DOM/CSS/Network/Console/Debugger protocol. See the Debugging & remote control guide for the agent workflow.
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.
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).
Non-destructive contract: each output (manifest / metainfo / desktop / flathub.json) is independently checked for existence; existing files are skipped with a log line. Re-run with --force to overwrite. User edits to one file (e.g. a hand-tuned .desktop) don’t block re-running init to refresh the others.
Config gaps degrade gracefully: missing MetaInfo fields are reported with per-field hints pointing at the exact gjsify.flatpak.<key> to set. The manifest still writes; MetaInfo + .desktop are skipped until you fill the gaps and re-run. Full field list:
gjsify.flatpak.<key>
Required for
Notes
appId
both
Reverse-DNS.
kind
both
"app" (default) or "cli".
name
optional
Human-readable display name for <name> + .desktopName=. Defaults to a friendly derivation of package.json#name — set explicitly when the npm name doesn’t match the display name (e.g. npm name learn6502 vs display name "Learn 6502 Assembly").
developer.id / developer.name
metainfo
AppStream OARS 1.1+ requires <developer id="…">.
developer.email
optional
Emits <email> inside <developer>.
developer.nameTranslatable
optional
Default false → emits translate="no". Set true for descriptive names.
summary
metainfo
≤80 chars, no trailing period.
summaryTranslatorHint
optional
Emits <!-- TRANSLATORS: ... --> before <summary>.
description
metainfo
String form (blank-line-split into <p>) or DescriptionBlock[] ({p, translatorHint?} paragraphs and {ul:[...], translatorHint?} bullet lists).
Minimum display length in pixels. Phone-portrait min ≈ 360, tablet recommendation ≈ 480.
requires.controls / recommends.controls
optional
Hard / soft control requirements.
For multilingual apps, every translatable string (summary, description paragraphs + list items, screenshot captions, release notes) supports a parallel translatorHint field that emits a <!-- TRANSLATORS: ... --> comment in the generated .metainfo.xml.in — xgettext / msgfmt --xml --template picks these up and forwards them to the .po files so contributors on Weblate / Crowdin see the context. See the flatpak-app guide → Rich AppStream features for a worked example.
npx@gjsify/cliflatpakcheck--reporepo# also lint a built repo
Option
Default
Description
[manifest]
auto
Manifest path; defaults to <app-id>.json or the single .json matching a manifest shape.
--metainfo <path>
data/<app-id>.metainfo.xml.in
MetaInfo to validate; skipped if missing.
--repo <path>
—
If set, also runs flatpak-builder-lint repo <path> (post-build).
--appstream
true
Toggle appstreamcli validate --strict. --no-appstream to skip.
--builder-lint
true
Toggle flatpak-builder-lint. --no-builder-lint to skip.
--verbose
false
Stream linter stdout/stderr through.
Requires appstreamcli and flatpak-builder-lint on PATH. Both ship inside the org.flatpak.Builder Flatpak — install via flatpak install -y flathub org.flatpak.Builder; check prints this hint on ENOENT. Exit code aggregates: non-zero if any linter fails or any required binary is missing.
Sync the per-app flathub/<app-id> tracking-repo manifest to a new git tag + commit. Flathub publishes each app from a separate repo whose manifest pins the upstream tag + commit; after cutting a release in your source repo, this command updates that pin and (optionally) opens the PR.
# Use the latest local git tag + its commit; auto-resolves flathub-repo from appId
Report resolution + intended branch + commit, touch no files.
--verbose
false
Echo every git / gh invocation.
Workflow:
Clones (or updates) flathub/<app-id> into $XDG_CACHE_HOME/gjsify/flathub-sync/.
Surgically edits modules[0].sources[<i>] — sets tag + commit, injects an x-checker-data block if missing (so Flathub’s auto-update bot can pick up future releases).
Preserves the manifest’s original 2-space indent + key ordering.
Creates branch update-to-<version>, commits, pushes, opens a PR via gh pr create.
Requires git (always) and gh (unless --no-pr). Both surface install hints on ENOENT. Idempotent: re-running with the same --version is a no-op when the manifest is already pinned.
gjsify.flatpak.flathubRepo in package.json#gjsify.flatpak overrides the default flathub/<app-id> derivation for repos that don’t follow the convention.
Also print the full flathub manifest source entry.
--source-index <n>
first type: git source
Which modules[0].sources[] entry to inspect.
--verbose
false
Echo fetch URL + resolved values.
Exit 0 when the local tag matches the flathub-pinned tag; exit 1 on drift with a hint at the exact gjsify flatpak sync-flathub command that would fix it.
Cut a release end-to-end: chain flatpak init (regenerate assets) → flatpak check (lint locally) → git tag (+ push) → flatpak sync-flathub (open the Flathub PR). One command for the maintainer flow that otherwise spans four manual invocations.
# Full chain
npx@gjsify/cliflatpakreleasev0.6.6
# Show the plan without running anything
npx@gjsify/cliflatpakreleasev0.6.6--dry-run
# Tag was already created
npx@gjsify/cliflatpakreleasev0.6.6--skip-tag
Option
Default
Description
<version>
—
Required. Release tag, e.g. v0.6.6.
--skip-init
false
Skip the flatpak init --force regen step.
--skip-check
false
Skip the flatpak check linter step.
--skip-tag
false
Skip git tag + git push origin <version>.
--push-tag
true
Whether to push the created tag (only relevant without --skip-tag).
--flathub-repo <owner/name>
—
Override forwarded to sync-flathub.
--dry-run
false
Print the plan, take no action.
--verbose
false
Echo every sub-command invocation.
The orchestrator runs init + checkbefore creating the tag — if either fails, no tag is created (the release is aborted, not half-released).
Refresh the installed @gjsify/cli to the latest release (or a pinned dist-tag).
gjsifyself-update# install latest
gjsifyself-update--check# diff only, exit 1 if outdated
gjsifyself-update--force# reinstall identical version
gjsifyself-update--tagnext# install a specific dist-tag (or version)
Walks import.meta.url to find the CLI’s own package.json, fetches the matching dist-tag via @gjsify/npm-registry, then re-uses the install backend that powers gjsify install -g (handles transitive native prebuilds, lockfile, bin shims).
Only works for CLIs installed under ~/.local/share/gjsify/global/ (the install.mjs bootstrap or gjsify install -g). Installs from npm install -g land outside that prefix; self-update surfaces a warning pointing at the new bootstrap.
Option
Default
Description
--check
false
Compare current vs target without installing. Exit 0 if up-to-date, 1 if outdated.
Scaffold an install.mjs for your own GJS-runnable npm package — your users get the same curl ... | gjs -m - install story as gjsify itself.
cdmy-gjs-app
gjsifygenerate-installer
# → install.mjs written. Commit it.
# Optional flags:
gjsifygenerate-installer\
--target@my-org/my-app\
--bin-namemy-app\
--bootstrap-urlhttps://example.com/cli.gjs.mjs\
--outputbin/install.mjs--force
The generated install.mjs is a verbatim copy of gjsify’s own root install.mjs with three constants substituted (DEFAULT_TARGET, DEFAULT_BIN_NAME, DEFAULT_BOOTSTRAP_URL). See the Distributing GJS apps guide for the full publication workflow.
Symmetric inverse of gjsify install -g. Removes a previously installed package tree from ~/.local/share/gjsify/global/node_modules/<pkg>/ plus any bin shims under ~/.local/bin/ that point into it.
gjsifyuninstall-g<pkg># remove pkg and its bin shim(s)
gjsifyuninstall-g<pkg>--dry-run# show what would be removed
Scoped to --global only. Project-local removal (mirror of npm uninstall <pkg> without -g) requires rewriting package.json + refreshing the lockfile, which is a separate workstream.
Produce an npm-compatible .tgz tarball for a workspace. Drop-in for npm pack. Always rewrites workspace:^/~/* deps to resolved version ranges so the published tarball is portable. Honors the files allowlist + .npmignore/.gitignore with the same precedence as npm.
gjsifypack# pack the current workspace
gjsifypackpackages/infra/cli# pack a specific workspace
gjsifypack--pack-destinationdist# write the .tgz somewhere else
Pack + upload a workspace to its npm registry. Drop-in for npm publish. Uses gjsify pack under the hood (so the workspace:^ rewrite happens automatically). Authenticates by reading process.env.NPM_CONFIG_USERCONFIG first (where actions/setup-node@v6 writes the auth-token npmrc) with ~/.npmrc as fallback.
gjsifypublish# publish current workspace
gjsifypublishpackages/infra/cli--taglatest
gjsifypublish--accesspublic# required for first publish of scoped pkg
gjsifypublish--accesspublic--otp123456# first publish with 2FA OTP (Node-free bootstrap)
gjsifypublish--tolerate-republish# treat 409 conflict as success
gjsifypublish--dry-run# pack only, don't PUT
Option
Default
Description
[path]
cwd
Workspace path to publish.
--tag <tag>
latest
npm dist-tag.
--access <kind>
—
public or restricted (required on first publish of scoped packages).
--otp <code>
—
npm 2FA one-time code; forwarded as the npm-otp HTTP header on the PUT request. Required for manual publishes from a 2FA-enabled npm account. If the registry responds with 401 OTP-required and --otp was not supplied: on an interactive TTY the CLI prompts once and retries; on a non-TTY it exits non-zero with an actionable message.
--tolerate-republish
false
Treat a 409 conflict (version already published) as success. Matches yarn --tolerate-republish.
--provenance
false
Recorded in payload but no signing happens (no sigstore signer yet).
--dry-run
false
Pack only, do not PUT.
--json
false
Emit publish metadata as JSON.
Combine with gjsify foreach to publish every workspace in one go:
Format JS/TS source files via oxfmt (oxc’s formatter). Dual-engine:
Under GJS (the gjs -m cli.gjs.mjs bundle), gjsify prefers the @gjsify/oxfmt-native GI bridge — the full oxfmt CLI runs in-process, Node-free (config resolution, ignore handling, file walking, --write/--check/--list-different all included). Override with GJSIFY_OXFMT=npm (force the Node launcher) or GJSIFY_OXFMT=native (error instead of falling back when the prebuild is unavailable).
On Node (or when the native prebuild is missing), gjsify resolves oxfmt’s npm package Node launcher from node_modules/oxfmt/bin/oxfmt and spawns it with the current Node executable. The per-platform native code ships as the @oxfmt/binding-<target> napi optionalDependency.
CSS/JSON formatting is not supported. oxfmt formats JS/TS (+TOML) only. The previous Biome toolchain formatted CSS and JSON too; that is intentionally dropped in the oxc migration and not replaced by another formatter.
Used with --init to overwrite existing config files.
--verbose
false
Echo the resolved oxfmt launcher + args before spawning.
With no --write/--check, gjsify format reports drift via oxfmt’s --list-different without modifying files.
Workspace-aware resolution — when run from inside a sub-workspace, gjsify walks up to the workspace root to find node_modules/oxfmt/bin/oxfmt. A single .oxfmtrc.json at the workspace root applies everywhere oxfmt discovers it.
Standalone projects — same shape, single .oxfmtrc.json at the project root.
Run oxlint diagnostics. Default reports findings (exit non-zero); --fix applies safe fixes in place. oxlint is spawned via its Node launcher (node_modules/oxlint/bin/oxlint) so its JS-plugin host — including gjsify’s internal gjsify/register-class-order rule — is available.
gjsifylint# lint all
gjsifylintsrc/# lint specific paths
gjsifylint--fix# apply safe fixes (skips unsafe)
Option
Default
Description
[paths..]
.
Files/directories to lint.
--fix
false
Apply safe lint fixes in place.
--config-path <path>
walks up
.oxlintrc.json path override.
--verbose
false
Echo resolved oxlint launcher + args.
Use gjsify fix for the combined format + safe-lint-fix pass.
gjsify ships an internal oxlint JS plugin (@gjsify/oxlint-plugin-gjsify, not published to npm) with one rule, wired into the workspace .oxlintrc.json via jsPlugins. It flags static GObject metadata fields (GTypeName, Properties, InternalChildren, Signals, Template, CssName, …) declared after a static { GObject.registerClass(…) } block — where registerClass runs before the field is assigned, so the metadata is silently ignored — and autofixes by hoisting those fields above the static block. (GNOME/gjs#704, gjsify/ts-for-gir#410.)
Combined fix pass: oxfmt --write (format JS/TS) followed by oxlint --fix (safe lint fixes). Different from gjsify system-check (which verifies system dependencies) and gjsify check (the tsc orchestrator).
gjsifyfix# default: format + apply all safe lint fixes
gjsifyupgrade--filter'@gjsify,vite'# narrow scope by substring
gjsifyupgrade-y# interactive-all (no prompt, select everything)
Workspace-aware: workspace:, file:, link:, git:, git+, http(s):, npm:, *, latest ranges are skipped — those are not external npm dependencies. Range prefix is preserved (^1.2.3 → ^2.0.0, ~0.4.0 → ~0.5.0).
The registry URL is resolved from ~/.npmrc → <cwd>/.npmrc, with npm_config_registry env var overriding both. Scope-specific registries + auth tokens from .npmrc are honored.
Option
Default
Description
--latest
false
Non-interactive bulk-update — allows major bumps.
--minor
false
Non-interactive — semver-minor + patch only.
--patch
false
Non-interactive — semver-patch only.
--filter <substring>
—
Comma-separated list of substrings (case-insensitive) to match against package names.
--dry-run
false
Print upgrade plan, don’t write package.json.
--yes / -y
false
In interactive mode, select all candidates without prompting.
--cwd <path>
process.cwd()
Project directory.
--verbose
false
Print resolution details.
Output is a color-coded table (red=major, yellow=minor, green=patch, cyan=prerelease) listing every dependency with a newer version available. After write-back, gjsify install must be run to actually fetch the new versions.