Command line
fabr [command] [options] <targets…>fabr works on the project that contains the current directory: the nearest PROJECT.fabr, in
this directory or one above it. Progress and errors are written to stderr, and a command’s own
output, such as the listing from ls or the file contents from cat, to stdout, so that output
can be piped or redirected on its own.
Commands at a glance
Section titled “Commands at a glance”| Command | What it does |
|---|---|
build |
Build targets. |
test |
Build and run targets’ tests. |
| An operation’s name | Build targets under an operation a rule declares, such as lint. |
run |
Build a program and run it. |
ls, cat, cp |
List, print or copy the files a target produces. |
deps |
Print the dependency tree of targets or packages. |
audit |
Check the dependencies of targets or packages for known security advisories. |
sbom |
Write the software bill of materials of a target or package. |
shell |
Open a shell in the directory a build step runs in, for debugging. |
sync |
Publish packages and files. |
build-server |
Serve the project’s targets to an editor. |
list-targets, list-targetdefs, list-operations, list-properties, list-all |
Describe the project and what a build file can contain. |
help [command] |
Print the commands, or one command’s options. fabr <command> --help does the same. |
Leaving the command out
Section titled “Leaving the command out”With no command, each target gets the first of build, test and run that its type supports:
fabr mylib # a js_package: builds itfabr e2e # a js_test: runs its testsfabr devserver --port 3000 # a serve target: runs itA target that can only be run ends fabr’s own arguments: everything after it goes to the program,
as --port 3000 does above. To pass arguments to a target that can also be built, name the
command: fabr run mylib --flag.
Without a command, fabr takes only the options -D, -w, -q, --no-progress, --refresh and
--trace. Any other option, such as -u, needs the command written out
(fabr test -u mylib); otherwise fabr stops with Option '-u' is not valid without a command.
Commands
Section titled “Commands”Builds the targets. The results go into fabr’s cache; nothing is written into your project
directory. Use ls, cat and cp to look at them or take a copy.
fabr build mylibfabr build -DBUILD_TYPE=release mylib appEach name must be one the project declares, such as a target, optionally with a
constraint. A path or a pattern (mylib:build/*.js, ./src) is an
error: to get at particular files, use ls, cat or cp. fabr checks every name before it starts,
so if any of them is wrong, nothing is built.
A target that can only be run, such as a serve target, builds everything its program needs
without starting it, so a later fabr run starts at once. A target that can only be tested is an
error that points at fabr test.
When nothing has changed since the last build, fabr prints Already up to date. This line and the Built … line end with how long the build took.
Compiles and runs the targets’ tests, and prints a summary for each name you give. A name that
lists several test targets, such as a property naming them (TESTED = a b c;), gets one summary
of all their tests. The tests run in a copy of their declared inputs, so they can’t change your
project directory. Targets are named as for build.
fabr test mylibfabr test -u mylib # also record new and changed snapshotsfabr test -t 'dates parse' mylib # run only the tests whose names matchfabr test --coverage mylib # also measure code coveragefabr test --retry-failed mylib # run failed tests again rather than report the cached failurefabr test --report results.xml --format junit mylib # also write the results as JUnit XMLEvery target’s tests run to the end whether or not some fail. Once all of them have finished,
fabr prints each summary, with the failed tests listed under it, writes any reports you asked
for, and then exits with status 1 if any test failed. A failing test doesn’t make its target
fail: a target that uses another’s test results, such as its ctrf-report.json, still builds.
A run is cached, passing or failing, and isn’t repeated until something the tests depend on
changes: a failing run’s failures are reported again, and the command fails again. With
--retry-failed, a target whose cached run failed runs its tests again; it can’t be combined
with -w.
| Option | Effect |
|---|---|
-u |
New and changed snapshots are written back into your source tree, after a run in which no test failed. Each file is named as it is written. |
-t <pattern> |
Only the tests whose names match the regular expression are run. If no test in any of the targets matches, the command fails with No test's name matches the pattern '<pattern>'. |
--coverage |
The tests are measured for code coverage: a summary is printed and an lcov report written to coverage/lcov.info, or to <dir>/lcov.info with --coverage=<dir>. |
--report <file> |
The results of every target tested are written to one file, as a CTRF report, or as JUnit XML with --format junit, for a CI system’s test results view. See Continuous integration. |
Paths are relative to the current directory. The coverage report and --report file are written
whether or not the tests pass, but not when a build error stops the run before the tests finish.
See testing for JavaScript and Java.
An operation’s name
Section titled “An operation’s name”A rule can give a target type an operation of its own, such as lint (see
Writing your own rules). The operation’s name is
then a command that builds targets under it:
fabr lint app libfabr lint -w appfabr lint app builds app as fabr build 'app<BUILD_OPERATION=lint>' does. It takes the
options a command left out takes: -D, -w, -q, --no-progress,
--refresh and --trace. Any operation a rule’s guard names (<BUILD_OPERATION=lint>) can be given this
way, and fabr list-operations lists them. A word no rule names as an operation is read as a
target name.
An operation’s name is a command even where the project also declares a target of that name, as
build and test are: write fabr build lint to build the target, or put it after --.
Two such operations come with fabr’s plugins, for target types that have a rule for them
(fabr list-operations shows which):
fabr sources <target>: the target’s sources, such as ajs_package’s or ajar_package’ssrcsandresources.fabr doc <target>: its documentation, such as ajar_package’s javadoc.
A package reference from a registry can be read under them too, for what the registry publishes
beside the package: fabr ls '@mvn:org.slf4j:slf4j-api:1.7.36<BUILD_OPERATION=sources>' lists
the contents of its -sources.jar. A registry that publishes nothing for an operation, as npm
publishes no separate sources or documentation, refuses the reference.
Builds a target as a program and runs it. The program has your terminal: its input, output and exit status are its own. It runs in your current directory, with your environment.
fabr run mytool --flag arg # --flag arg go to the programfabr run -DBUILD_TYPE=release mytool # options for fabr go before the targetfabr run @npm:typescript:5.6.3:tsc --version # a package's command-line programEverything after the target is passed to the program unchanged, including a --. Options meant
for fabr go before the target.
A JavaScript program, such as a js_script, a js_package with a program, or an npm package’s
command, runs on the Node.js release the
NODE setting names, not on the node on your
PATH; process.execPath in the program is that binary. Because the program gets your
environment, a NODE_OPTIONS you have set applies to it, and a release older than your own
Node.js may reject an option in it. fabr run @node:24.22.1:bin/node runs a Node.js release
itself.
fabr installs the program, with everything it needs, into a directory in its cache, and keeps that
directory for the next run of the same program from the same project with the same -D settings:
running it again updates only the files that changed. Two copies of one program running at the same
time each get a directory of their own. An installation that hasn’t been used for 30 days is removed
when fabr trims its cache.
A serve target is the exception to “runs in your current
directory”: it runs in a directory containing the files it serves. fabr run -w restarts the
program, or updates the files it serves, as sources change; see
Watch mode & dev servers.
Builds what the references name and lists the files. -l adds each file’s content hash and size.
fabr ls mylibfabr ls -l 'mylib:*.js'A target’s files are what it builds. A target that can only be run, such as a serve target, has
its program’s installation as its files, and a target that can only be tested has its results:
fabr cat my_test:ctrf-report.json runs the tests and prints the report. The same goes for cat
and cp.
A reference to a package or image with no version, or to a repository, lists what it contains.
Nothing is downloaded or built to list them, and -l makes no difference.
| Reference | Lists |
|---|---|
An npm or Maven package (@npm:esbuild, @mvn:com.google.guava:guava) |
Its published versions, newest first. |
A container image (@docker:alpine) |
Its tags, in name order. |
A JDK distribution (@jdk) |
The releases published for the target platform, newest first. |
A catalog (@pkg) |
Its members. |
A repository_group’s package |
Its versions, as the repository it is routed to lists them. |
A scope of @fabr-home (@fabr-home:@fabr-build) |
The packages installed in it. |
| An AWS account, an S3 bucket, or a prefix in one | The account’s services; the bucket’s or prefix’s objects and prefixes. See Amazon S3. |
fabr ls @npm:esbuildfabr ls @docker:alpinefabr ls @pkgA registry can’t list every package or image it holds, so fabr ls @npm, fabr ls @mvn and
fabr ls @docker are errors, as are an npm scope (@npm:@types) and a Maven group
(@mvn:com.google.guava); name a package or image instead, as in fabr ls @npm:@types/node.
Likewise fabr ls @fabr-home is an error, and fabr ls @fabr-home:@fabr-build lists the scope.
Builds what the references name and writes the contents of the files to stdout.
fabr cat mylib/package.jsonfabr cat 'mylib:*.d.ts' > all-types.d.tsA reference that matches no files is an error.
Builds what the references name and copies the files into a directory, which is the last argument and an ordinary path. Existing files in the directory are kept unless a copied file has the same name.
Where the files land follows the rules of cp -R, applied to the reference as you wrote it:
fabr cp mylib out # a whole target -> out/mylib/…fabr cp 'mylib:index.js' out # one file -> out/index.jsfabr cp 'mylib:build/*.js' out # a pattern -> the matching files, directly in out/fabr cp 'mylib:build/**' out # everything under it -> out/…, keeping the structure below build/Writing the reference with / or : makes no difference to cp: mylib/index.js and
mylib:index.js both copy to out/index.js. A reference with a
rename (-> template) is the one case where the
names come from the reference: the files are copied under the names the rename gives them.
Prints the packages each reference depends on, as a tree: one block per reference, with a section for each property that brings in packages. On a terminal, packages built in your project are shown in colour.
fabr deps mylibmylibdeps├── mycore│ └── undici:^6.19.0 -> 6.19.8├── chai-as-promised:7.1.1│ ├── check-error:^1.0.2 -> 1.0.2│ └── chai:>= 2.1.2 < 5 -> 4.3.6 (p)└── semver:7.6.3
(p) - provided requirement, bound by the consumerEach line is a package under the name it is installed as. requested -> selected shows the
version or range the package above it asks for and the version chosen for the whole tree; a line
with one version asked for exactly that, or, where its reference states none, shows the version chosen. A package built in your project is shown by name alone:
it has no version until it is built.
| Mark | Meaning |
|---|---|
(*) |
This package’s dependencies are shown where it first appears. |
(p) |
A provided requirement (an npm peer dependency): the package is supplied by whatever depends on the one requiring it. |
(n) |
Nothing is installed for this requirement, for example an optional peer dependency that no package in the tree supplies. |
(!) |
A version that conflicts with another version of the same package in the tree, so building the target fails until you allow both or pin one. The conflicting versions are listed under the tree. |
The tree is the one the target’s own build resolves, so it depends on the configuration. A
reference is read under the build operation; to see what another operation resolves, such as a
package’s test_deps, require it on the reference:
fabr deps 'mylib<BUILD_OPERATION=test>'A target type that has no build operation and only one of its own (a js_script runs, a
js_test tests) is read under that one.
A package reference needs no declared target, which answers “what does this package bring with it” before you add it:
fabr deps @npm:glob:10.0.0fabr deps @dep:chai # a catalog member, at the versions the catalog pinsdeps builds none of the targets it lists and downloads no packages from a registry: it reads
what each target declares and resolves versions exactly as a build would, which needs only each
package’s registry metadata. Only the properties that take packages are shown: a target named in
one is listed by its name, whether it builds a package or plain files, and what a property that
takes files names (srcs, resources) is left out. So are the tools a target runs, such as the
TypeScript compiler or a generate target’s run command, which are installed apart from the
target’s own packages. A target with no packages prints No dependencies. A reference that selects
files (mylib:build/*.js) is an error: name the target.
Two things are still computed, because the dependencies cannot be known without them:
- A
repository_grouproute that serves a package from files (amperize = ./vendor/amperize;) reads that package’spackage.jsonfor its version and dependencies. When those files are a downloaded archive it is downloaded, and when they are a target’s output that target is built. This happens only when something in the tree requires that package. - A command substitution (
`…`) in a value that names a dependency is run.
Checks the packages each reference depends on against a database of known security advisories, and prints each advisory found with the packages it affects.
fabr audit myappGHSA-35jh-r3h4-6jhm (CVE-2021-23337) HIGH Command Injection in lodash lodash@4.17.15, fixed in 4.17.21 myapp > express@4.17.1 > lodash@4.17.15 https://nvd.nist.gov/vuln/detail/CVE-2021-23337
1 advisory affects 1 of 57 packages checkedThe packages checked are the ones deps prints for the same references, read the same way: the
tree a target’s build resolves, under build unless the reference requires another operation
('myapp<BUILD_OPERATION=test>' checks its test_deps too), with nothing built and nothing
downloaded from a registry. For a Java library that is its compile classpath; audit the program,
war or installation you deploy to check what runs. See
Auditing dependencies for what to do about an advisory. Every package a registry publishes is checked at the exact version the
tree selects. A package built in your project has no published version, so it is not checked
itself, though what it depends on is. A patched package is the exception: it is checked at the
version it was made from, and an advisory its fixes names is reported as fixed by it. Each advisory lists the affected package with the versions
the advisory says fix it and one path by which each reference reaches it; the remedy is to require
a fixed version at the root, for instance @npm:lodash:4.17.21! or a catalog entry, since a root
requirement decides the version for the whole tree.
The command exits with status 1 when any advisory affects a package, one a patched package fixes
aside, and 0 otherwise. --format sarif writes the report as SARIF 2.1.0 instead
of text, the format code-scanning services such as GitHub code scanning take and most analysers
emit. Each advisory is a rule and each affected package a result, with the fixed versions and the
paths among its properties, located at the line of your build file whose reference brings the
package in, so the service shows it there; the run records how many packages it checked.
The database is the one the ADVISORIES property names: the URL of a server answering the
OSV API, https://api.osv.dev by default. Packages are
named to it by their package URL (pkg:npm/lodash@4.17.15,
pkg:maven/org.slf4j/slf4j-api@1.7.36); a package in an ecosystem with no package URL type, such
as a Node.js release, is not checked. To use a mirror, or an internal database speaking the same
API, set the property in your project or for one run:
fabr -DADVISORIES=https://osv.example.com audit myappAdvisories are read as they stand each time: nothing about them is cached, and the result is no part of any build.
Writes the software bill of materials of one target or package to stdout, as CycloneDX 1.6 JSON: the target as the component described, every package among what its build requires as a component, and the dependency graph between them.
fabr sbom myapp > myapp.cdx.jsonfabr sbom @npm:express:4.19.2The packages are the ones deps prints, resolved the same way and with nothing built. Each package
a registry publishes is named by its package URL and
carries the licence its registry metadata declares, as an SPDX id, an expression or a name, and the
digest of its content the registry publishes: npm’s dist.integrity and Maven’s .sha1 beside the
artifact. Those are read from each registry’s metadata for the exact version, one small document
per package the first time, and cached forever, since a published version’s metadata never changes.
No package is downloaded. A package built in your project is a component by name, and by version
where its target declares one.
The document is a function of the resolution alone: it carries no timestamp or serial number, so two runs over the same sources write the same bytes.
Sets up the directory that a target’s build step runs in, with its inputs and tools in place,
prints the command the step would run, and opens a shell there. Use it to reproduce a failing step
by hand. The shell has the environment the step has (empty, apart from what the target sets, such
as a test’s test_env), and the directory is removed when you exit.
fabr shell docs_siteA test run is a build step too: name the target under the test operation to get its compiled
tests and test runner, with the shell opened in the compiled tree. The step runs the runner once
per test file, so the first few commands are printed; run one of them to repeat a single file’s
run.
fabr shell 'app<BUILD_OPERATION=test>'The staged files are read-only links into fabr’s cache. To try a change, copy the file to a new name and edit the copy; don’t make the original writable, because that would change the cached copy too.
Builds what a sync target lists and publishes each entry to its
destination, each package after the packages it depends on. fabr build on the same target
produces the content without uploading it, which is a way to check a release first.
fabr build release # produce what would be publishedfabr sync release # publish itbuild-server
Section titled “build-server”Describes every target of the project as each of its build and test rules would build it, and
serves the descriptions over the Build Server
Protocol (BSP) on standard input and
output, watching the project and sending buildTarget/didChange as descriptions change. Describing
compiles no package. It is what an editor runs; see VS Code support.
fabr build-serverA target is identified as fabr:<name>, and its test run as fabr:<name>?operation=test; a
package that declares no tests has no test run. buildTarget/compile and buildTarget/test build
and test targets in the same process, and a build’s errors are sent as
build/publishDiagnostics.
The same connection also carries the Language Server
Protocol (LSP) for the project’s build
files: outline, go-to-definition, hover, completion and code lenses. Each LSP message carries the
header Fabr-Protocol: lsp beside its Content-Length; a message without it is BSP. Each protocol
is initialized separately, and sends no notifications until its own initialize is answered.
Diagnostics go to standard error.
list-targets
Section titled “list-targets”Lists the targets the project declares, with each one’s type and the operations its type has rules for.
$ fabr list-targetsapp js_package [build, run, sources, test]docs serve [run]e2e_tests js_test [test]The operations are those of the target’s type. A rule that applies only to some targets of a type,
such as one for a particular test_framework, counts for all of them.
Arguments limit the listing. Each is a target’s name, or a glob over target names, in which *
matches within one level of a /-separated name and ** across levels. Quote a glob so the shell
doesn’t expand it.
fabr list-targets 'packages/**'A name that no target has is an error, but a glob that matches none lists nothing.
| Option | Effect |
|---|---|
-l |
Adds where each target is declared. |
--all |
Adds the targets fabr and its plugins declare themselves, which are normally hidden. A target you name is listed without it, but a glob matches these targets only with --all. |
--operation OP |
Lists only the targets whose type has a rule for OP. Give it more than once to list the targets of any of them. |
--json |
Output as JSON. |
list-targetdefs
Section titled “list-targetdefs”Lists the target types a build file can use, with the properties each takes. A name limits it to that type.
fabr list-targetdefs js_package-l adds where each is declared, and --json gives the output as JSON.
list-operations
Section titled “list-operations”Lists every operation the project’s rules declare, the standard ones included, with the target
types that have a rule for each, in the order they are declared. --json gives the output as JSON.
build generate, patched, zip, sync, js_package, fabr_plugin, js_bundle, js_compile, js_installation, sass_compile, css_postcsslint js_package, fabr_pluginrun script, serve, js_package, fabr_plugin, js_scriptsources js_package, fabr_plugintest js_package, fabr_plugin, js_testlist-properties
Section titled “list-properties”Lists the configuration settings with their current values, and the flags. -l adds where each
is declared, and --json gives the output as JSON.
list-all
Section titled “list-all”Writes everything the other list- commands report, as one JSON document. The
core and JavaScript reference pages are
generated from it. --include-optional also reads every
optional .fabr file of the plugins the project uses, as though
the project included each one, so that what they declare is listed too.
Options
Section titled “Options”| Option | Applies to | Meaning |
|---|---|---|
-DNAME=VALUE |
all | Set a configuration property for this run, overriding the build file and any default. For run, put it before the target. |
-w |
build, test, run, an operation’s name |
Keep running, and rebuild, retest or restart when sources change. See Watch mode. |
-u, --update |
test |
Record new and changed snapshots, and write them into your source tree. |
-t PATTERN |
test |
Run only the tests whose names match the regular expression PATTERN: for JavaScript, suite and test names joined by spaces; for Java, Class#method. Sets TEST_NAME_PATTERN. |
--coverage[=DIR] |
test |
Measure code coverage, print a summary and write DIR/lcov.info (coverage/ by default). |
--retry-failed |
test |
Run again the tests of a target whose cached run failed, rather than report the cached failure. Can’t be combined with -w. |
--report FILE |
test |
Write the results of every target tested to FILE, failures included, as CTRF (the default) or as --format says. |
-q, --quiet |
all but the list- commands |
Don’t show the progress display or the output of build steps. A step that fails still shows its output in the error. |
--no-progress |
all but build-server and the list- commands |
Don’t show the progress display. Output from build steps is then printed as it arrives, as it is when stderr isn’t a terminal. |
--trace[=FILE] |
build, test, run |
Write a trace of the run’s work to FILE (fabr-trace.json by default). See Tracing a run. |
--refresh |
all but build-server and the list- commands |
Check again for anything a server can change under the same address, such as a registry’s version lists or an S3 object, however recently fabr last checked. Without it, fabr checks each at most once a day. |
-l |
ls, list-targets, list-targetdefs, list-properties |
Long listing: hash and size for ls, source location for the list- commands. |
--json |
list-* |
Output as JSON. |
--format FORMAT |
audit, test |
The report’s format: sarif for audit, instead of text; ctrf or junit for test, which needs --report with it. |
--all |
list-targets |
Include the targets fabr and its plugins declare. |
--operation OP |
list-targets |
List only the targets whose type has a rule for the operation OP. Give it more than once for any of several operations. |
--include-optional |
list-all |
Also read the optional .fabr files of the plugins the project uses. |
-- |
all | End of options. What follows is a target, even if it starts with - or is the name of a command. |
-v, --version |
Print fabr’s version. | |
-h, --help |
Print the commands, or with a command (fabr test --help), that command’s options. |
When fabr can’t read a command line, such as one with an option the command doesn’t take or with no target, it says why and prints the command’s help, both to stderr, and exits with status 1.
On a terminal, fabr shows the steps in progress at the bottom of the screen, and holds each step’s output until the step finishes, so that the output of steps running in parallel isn’t mixed together.
Exit status
Section titled “Exit status”| Status | Meaning |
|---|---|
| 0 | Everything named was built, all tests passed, and audit found no advisory. |
| 1 | Anything else: a build error, a failing test, an advisory found by audit, a dependency that couldn’t be resolved, an unknown target or option, a command with no target, a failed sync. |
| 2 | An internal error in fabr. |
| 130, 143, 129 | Interrupted by SIGINT, SIGTERM or SIGHUP. Running steps are stopped first. |
fabr run exits with the status of the program it ran, and fabr shell with the status of the
shell. The output says what failed.
Tracing a run
Section titled “Tracing a run”--trace records what the run did and when, in the trace-event format that
Perfetto and Chrome’s chrome://tracing open: drop the file onto either.
The trace holds one span per unit of work fabr performed — a target’s build step, a test run, a
resolution, a download — from when it started to when it ended, on a track per concurrent task, so
the timeline shows what ran alongside what and where the run waited on one chain. Within a task,
waiting spans mark the stretches it queued for an execution slot; within a test run, each test
file’s run is an async span placed by the runner’s own clock, with its outcomes. Cycle boundaries
are instants, so a watch run’s cycles read apart. A span’s args carry the task’s constraints and
the chain of targets that required it, as the start line does.
Work served from the cache runs no step and appears nowhere, which is the point: the trace is of
what the run had to do. Each cycle’s work is added to the file at the end of the cycle, and the file
is finished when the run ends, however it ends, with any task then still running marked
unfinished. A trace read before then lacks its closing ], which Perfetto and chrome://tracing
accept.
fabr test --trace=traces/full.json allNaming targets and files
Section titled “Naming targets and files”An argument that names a target is a reference, written as it would be in a build file. The language reference describes references in full. On the command line:
- A target name means the target:
mylib. - A path into a target selects files from what it builds:
mylib/package.json,'mylib:build/*.js'. Quote a pattern so that your shell doesn’t expand it. - A path in your project names source files, relative to the current directory:
fabr ls ./src. - A package reference needs no declared target:
fabr ls @npm:esbuild:0.28.1,fabr run @npm:prettier:3.3.3 --check src. - A constraint builds that one reference with a different setting:
'mylib<BUILD_TYPE=release>'.
Selecting files matters to ls, cat and cp, and to run, where it picks which of a package’s
programs to run. build, test and sync work on a whole target: naming files, or a path in your
project, is an error.
A constraint is the per-reference form of -D. The difference shows when you name more than one
target:
fabr build 'a<BUILD_TYPE=release>' b # a and what it depends on as release; b as usualfabr build -DBUILD_TYPE=release a b # both as release