Project Configuration
Every Nebra project is a directory with a nebra.toml at its root. That file is what turns a folder of
.neb files into something the compiler, the package manager and the language server all agree on.
Project layout
nebra init produces the conventional shape:
my-app/
├── nebra.toml # project config
├── src/ # .neb sources, compiled recursively
│ └── main.neb
├── out/ # generated .lua, safe to delete and gitignore
└── nebra_modules/ # installed dependencies, created by nebra install
Nothing here is hardcoded. source and output are configurable, and the compiler mirrors the
directory structure of source into output, so src/animals/cat.neb becomes
out/animals/cat.lua.
The fields you will actually set
name = "my-app" # package name, used by the package manager
version = "0.1.0" # semver, used when this package is a dependency
target = "5.4" # 5.1, 5.2, 5.3, 5.4, jit
source = "src" # root of your .neb files
output = "out" # where the .lua goes
entry = "src/main.neb" # what `nebra run` and `nebra compile` start from
minify = false # strip whitespace from the output
generate_declarations = true # also emit .d.neb type definitions
name, source and output are the only ones most projects need. The rest have sensible defaults.
entry
entry names the file that is the program. nebra run and nebra compile need it. If you leave it
out, nebra run falls back to looking for main.lua, index.lua or init.lua in the output
directory.
Libraries usually have no entry at all, because they are imported rather than executed.
generate_declarations
When true, the compiler writes a .d.neb file alongside the compiled Lua describing the public
types of the project. Other Nebra projects that depend on this one read that file to type-check calls
into it. Turn it on for libraries, and leave it off for applications that nobody imports.
See declaration files for the format.
Building
nebra build
Reads nebra.toml, compiles every .neb under source, and writes the result under output. If any
file fails to compile, nothing is written. A partially compiled project is never left on disk.
You can also compile specific files without a full project:
nebra build src/foo.neb src/bar.neb
In this mode the compiler still picks up a nebra.toml if one is next to the files, so your target
and code options still apply.
Running
nebra run # compile the project and execute `entry`
nebra run src/scratch.neb # compile and run one file, output goes to a temp dir
nebra run -- alpha beta # forward arguments to the script via the global `arg` table
nebra run uses the Lua 5.4 interpreter embedded in the nebra binary, so it works on a machine with
no Lua installed. Imports resolve through Lua's normal require, with the compiled output directory
prepended to package.path before the entry script runs.
In single-file mode the compiled Lua is written to a temporary directory and cleaned up afterwards.
Use nebra build when you want the output to stick around.
The embedded interpreter is always Lua 5.4, regardless of your target. If you target 5.1 or
jit, nebra run is a convenience for iterating, not a substitute for testing on the real runtime.
Compile with nebra build and run the output under the actual interpreter before shipping.
Watching
nebra watch
nebra watch --debounce 150
Recompiles the whole project on every change under source, debouncing bursts of saves. Compile
errors print and the watcher keeps going. Each rebuild re-reads nebra.toml, so changing the target
or the output path takes effect without a restart.
Unlike nebra build, the watcher does not run [scripts] hooks, so side-effecting pre- and
post-build commands do not fire on every keystroke.
Config inheritance and presets
Projects in a monorepo can share a base config:
extends = ["./base.toml"]
preset = "strict" # "strict" or "relaxed"
globals = ["lib/engine.d.neb"] # declaration files loaded into every compilation
extends merges parent files in order, with the local file winning on conflicts. It chains
transitively, up to a depth of ten.
preset = "strict" turns on the stricter rules in one line: no implicit any, strict nil checking,
immutable-by-default variables and explicit exhaustive matching. You can still override individual
rules underneath it.
globals points at .d.neb files whose declarations are visible everywhere without an import. This
is how you type a host environment, such as a game engine that injects globals into every script.
Everything else
nebra.toml also has [code], [rules], [mangle], [scripts], [install], [test], [sides]
and [reflection] sections. They control indexing base, operator aliases, nil strictness,
minification, build hooks and more.
Those are documented key by key in the configuration reference.