Skip to content

Command line

Terminal window
blinkblox [CONFIG] [OPTIONS]

CONFIG is the schema to compile. The compiler reads it, follows its imports, and writes the server and client modules to the paths the schema names in ServerOutput and ClientOutput. Both options are required.

Terminal window
blinkblox net

The schema’s path is looked up in this order, and the first file that exists is used:

  1. the path exactly as given – net
  2. with .txt appended – net.txt
  3. with .blink appended – net.blink

So blinkblox net and blinkblox net.blink compile the same file. A path into another directory works the same way: blinkblox schemas/net.

Output paths are relative to the schema, not to where you run the command. option ServerOutput = "src/server/Net.luau" in schemas/net.blink writes schemas/src/server/Net.luau. Absolute paths are used as they are. Whatever extension the path has, the module is written as .luau: "out/Server.lua" and "out/Server" both write out/Server.luau.

What gets written:

  • the server module and the client module, always;
  • a shared types module, when TypesOutput is set;
  • a .d.ts declaration next to each module, when Typescript is set. A module named init gets index.d.ts.

On success it prints its progress and stops:

BlinkBlox 1.3.9
Reading source from net.blink...
Parsing source into AST...
Generating output files...
Network files generated!

If an output path points into a directory that does not exist, the compiler asks whether to create it. Answering no stops the compile. Two cases skip the question and create the directory:

  • --yes is passed.
  • No terminal is attached – the compiler runs from a script, a git hook, a build task or CI. There is nobody to answer, and naming the path in the schema is taken as the answer.

A build therefore never hangs waiting on a prompt nobody can see, with or without --yes.

Flags may come before or after the schema, in any order. The schema is the first argument that is not a flag – blinkblox --yes net and blinkblox net --yes are the same command.

Flag What it does
-h, --help Prints the usage and the list of flags. Running blinkblox with no schema does the same.
-v, --version Prints BlinkBlox and the version, then exits without compiling.
-w, --watch Compiles, then keeps watching the schema and everything it imports, recompiling on every change. See watch mode.
-q, --quiet Prints nothing on success: no banner, no progress. Errors and warnings are still printed, --version still prints the version, and watch mode still says what it is watching.
-c, --compact Prints each diagnostic as a single line instead of the full rendering. See diagnostics.
-y, --yes Accepts every prompt. The only prompt is creating a missing output directory.
--check Parses and generates everything, and writes nothing – no modules, no output directories. Reports the same diagnostics and exits with the same code as a real compile. See checking without writing.
--verify Compiles in memory as a real build would, writes nothing, and compares every file it would write with the one on disk. Exits with 1 if any is stale or missing. See verifying committed output.
--json Prints the result as one JSON document on standard output instead of rendering diagnostics, and prints nothing else. See JSON output.
-p, --profile Compiles under a profile: dev, debug, test or release. Takes a value: --profile dev. Without it the build is release.
--from Reads CONFIG as another tool’s definitions and writes a draft schema beside it, compiling nothing. Takes a value, the tool: zap, bytenet, packet, quicknet or warp. See converting another tool’s definitions.

Flags are matched whole, so short flags do not combine: write -q -y, not -qy. An argument the compiler does not recognise as a flag is taken for the schema’s path, so a mistyped flag in front of the schema shows up as a missing file named after it.

--profile and --from are the two flags that take a value, and the value is never mistaken for the schema: blinkblox --profile dev net compiles net under dev. A missing or misspelt value stops the compiler before it reads anything:

Expected a profile after "--profile": dev, debug, test or release

When a schema is wrong the compiler prints a diagnostic, writes nothing, and exits. Each diagnostic has a code, a message, the offending line with the span underlined, and often a note on how to fix it. For this schema:

net.blink
option ServerOutput = "src/server/Net.luau"
option ClientOutput = "src/shared/Net.luau"
option RequireRates = true
event Chat {
From: Client,
Type: Reliable,
Call: SingleSync,
Data: string(1..200)
}

the compiler prints:

BlinkBlox 1.3.9
Reading source from net.blink...
Parsing source into AST...
[E3020] Error: Inbound "Chat" has no rate limit
╭─[net.blink:1:11]
│
005 │ event Chat {
┆ ──┬──
┆ │
┆ ╰── Add a Rate, in events per second
│ = note: option RequireRates is set, so every inbound event and function must declare one.
│
────╯

The file is named by the path it was compiled from, normalised: blinkblox schemas/net reports schemas/net.blink, and an error inside an import names the imported file’s own path, not the string the import wrote. The bracket after the file name is the range of lines in the file – line 1 to line 11 here – not the position of the error. The line number on the left of the source is the one to look at.

With --compact, the same diagnostic is one line, in the form [code] [Lfirst:Llast] [file] Error: message:

[E3020] [L005:L005] [net.blink] Error: Inbound "Chat" has no rate limit

It carries no column, labels or notes. A program that needs those should use --json instead.

Warnings (codes starting with W) use the same layout in yellow. They do not stop the compile: the modules are written, and the warning is printed alongside. Diagnostics go to standard error; the banner and progress lines go to standard output.

Every code, and what to do about it, is listed in the diagnostics reference.

Code When
0 The modules were written (or, with --check, would have been), or --help or --version was asked for. Warnings do not change it.
1 Anything else: a diagnostic error in the schema, an unknown --profile, a schema file that does not exist, a schema without ServerOutput or ClientOutput, a refused directory prompt, a stale or missing file under --verify, --json or --verify combined with --watch, or a --from that named an unknown tool, could not read the definitions, or found a draft already there.

Only diagnostics are rendered as above. A missing schema file or a refused prompt is reported as a plain error message followed by a stack trace. Under --json every failure of a compile – a diagnostic, a missing file, a missing output path, a refused prompt – is reported in the document instead. An unknown --profile and --json combined with --watch are refused before anything is compiled, and print a plain line.

Terminal window
blinkblox net --check

--check runs the whole compile – the parse, every analysis, and the generator – and stops short of the disk: no module is written and no output directory is created, so it never prompts. The diagnostics and the exit code are the ones a real compile would give, and on success it ends with Schema checked, nothing written. instead of Network files generated!.

Use it wherever the modules on disk should not change: a pre-commit hook, a CI step that only has to say whether the schema is valid, or an editor task run on save. It combines with --watch, which then reports each change without writing anything.

Terminal window
blinkblox net --check --json

--json prints one JSON document on standard output and nothing else: no banner, no progress, and no rendered diagnostics on standard error. It is meant for programs – an editor task, a CI step, a coding assistant – that need to know exactly where a diagnostic is without parsing the rendered form. The exit code is unchanged: 1 when success is false, 0 otherwise.

For the schema in Diagnostics above, it prints (formatted here; the real output is one line):

{
"version": 1,
"success": false,
"diagnostics": [
{
"severity": "error",
"code": "E3020",
"name": "AnalyzeMissingRateLimit",
"message": "Inbound \"Chat\" has no rate limit",
"file": "net.blink",
"range": {
"start": { "line": 5, "column": 7, "offset": 122 },
"end": { "line": 5, "column": 11, "offset": 126 }
},
"labels": [
{
"primary": true,
"message": "Add a Rate, in events per second",
"range": {
"start": { "line": 5, "column": 7, "offset": 122 },
"end": { "line": 5, "column": 11, "offset": 126 }
}
}
],
"notes": [
"option RequireRates is set, so every inbound event and function must declare one."
]
}
],
"outputs": []
}
Field Meaning
version The format’s version, 1. It changes when a field is removed or changes meaning; a new field does not change it.
success false if the compile failed, for any reason. Warnings do not make it false.
diagnostics Every error and warning, in the order they were raised. A compile stops at its first error, so there is at most one error, after any warnings.
outputs The paths written, normalised. Empty under --check and --verify, and empty when the compile failed.
verify Only under --verify: every file the compile would write, in the order it would write them, each as { "file", "status", "line" }. See verifying committed output.

Each diagnostic has:

Field Meaning
severity "error" or "warning".
code The code as the rendered form prints it – "E3020", "W3019" – listed in the diagnostics reference. null for a failure that is not a diagnostic.
name The code’s name in the compiler, such as "AnalyzeMissingRateLimit". null when code is.
message The headline, without colour codes.
file The file it is in, named as in the rendered form.
range Where the primary label points, or null when there is none.
labels Every underlined span: the primary one and any secondary ones, each with its message and range.
notes The = note: lines, which say what to do about it.

A range’s start is inclusive and its end exclusive. line and column count from 1, as the rendered form’s line numbers do, and offset is the byte offset into the file, counting from 0. A column counts bytes, so a tab is one column.

A failure that is not a diagnostic – a schema file that does not exist, a schema without ServerOutput – is reported the same way, with code, name and range set to null and the error in message. Standard output is a JSON document on every run that reaches the compile, including the ones that fail before the schema is read. The runs that do not reach it print plain text: an unknown --profile, --json or --verify with --watch (below), --version, and --help or a command line with no schema.

--json cannot be combined with --watch: watch mode never finishes, so its document would never be complete. The compiler refuses the pair and exits with 1.

Terminal window
blinkblox net --verify

A game that commits its generated modules – so that Rojo and Studio need no codegen step – needs a check that they still match the schema. --verify runs the compile exactly as a real build would, with the same output paths and the same --profile, writes nothing, and compares each file it would have written with the file at that path, byte for byte:

stale: src/server/Net.luau (first difference on line 212)
missing: src/shared/Net.d.ts
2 of 4 generated files do not match the schema. Compile it again to update them.
  • Exit code 0 when every file matches, and it prints All 4 generated files match the schema. – nothing at all under --quiet.
  • Exit code 1 when a file is stale (its bytes differ) or missing, or when the compile itself fails, which is reported as under --check. Stale and missing files are printed even under --quiet. Paths are relative to the directory you ran it from when they are inside it.
  • Only the files the compiler would write are compared. The compiler never deletes anything in an output folder, so another file there is not an error – nor is a .d.ts left behind after Typescript was turned off, which a real build would leave in place too. Delete it by hand.
  • It never writes and never prompts: no module, no output directory. It implies --check, and combines with --json and --profile. It cannot be combined with --watch, which is refused with exit code 1.
  • The compiler’s version is part of every module’s first line, so a file generated by another release is stale. Pin the compiler, for example in rokit.toml, and verify with the same one.

Under --json the document has a verify field, one entry per file the compile would write:

"verify": [
{ "file": "src/server/Net.luau", "status": "stale", "line": 212 },
{ "file": "src/shared/Net.luau", "status": "current", "line": null },
{ "file": "src/shared/Net.d.ts", "status": "missing", "line": null }
]

status is "current", "stale" or "missing", and line is the first line that differs, counting from 1, for a stale file and null otherwise. success is false whenever any entry is not "current". A schema error ends the compile before anything is compared, so verify is empty then and the error is in diagnostics.

Run it from the repository root with the same flags the build uses. With lefthook:

lefthook.yml
pre-commit:
commands:
network:
glob: "*.{blink,luau,ts}"
run: blinkblox net/Game.blink --verify --quiet

or as a CI step:

.github/workflows/checks.yaml
- name: Generated network modules match the schema
run: blinkblox net/Game.blink --verify

A build under a profile verifies under the same one: blinkblox net/Game.blink --verify --profile dev. Unlike regenerating and asking git for a diff, it leaves the working tree as it was and needs no repository.

Terminal window
blinkblox net.zap --from zap

--from reads CONFIG as the definitions of the tool it names and writes a draft schema beside them: net.zap becomes net.blink. The path is used exactly as given, with no extension added. It compiles nothing, and prints what the draft still needs from you – a Rate for each event a client fires, a bound for each length a client sends – each of which is also marked in the draft with a TODO(convert) comment.

Tool Reads Guide
zap a zap config, as zap 0.6.29 reads it Coming from zap
bytenet a Luau module of ByteNet 0.4.3 definitions, which it runs Coming from ByteNet
packet a Luau module of Packet 1.7.0 definitions, which it runs Coming from Packet
quicknet a Luau module of QuickNet v0.4.0-beta definitions, which it runs Coming from QuickNet
warp a Luau module of Warp 1.1.0-pre8 useSchema calls, which it runs Coming from Warp
  • A draft already there is not replaced unless --yes is passed. There is no prompt: without --yes the command stops with exit code 1.
  • --check writes nothing and prints the draft on standard output, with no banner in front of it. The list of what is left to decide goes to standard error, so --check > draft.blink captures the draft alone.
  • --quiet writes the draft and prints nothing.
  • The draft is compiled before it is written out as finished. One that does not compile is a fault in the converter and is reported as one, with exit code 1; the draft is still written, so it can be attached to the report.
  • Definitions the tool itself would refuse stop the command with the line and what was expected.
  • Definitions written in Luau are run, as require would run them, against a stand-in for the library that records what is defined. They get placeholders for game, script and the other Roblox globals, and no filesystem, network or process library. A require of a file beside them is run the same way; any other is answered with a placeholder and named in the printed list.
  • It cannot be combined with --watch, --verify or --json, and an unknown tool is refused with the list of those it reads.
Terminal window
blinkblox net --watch

Watch mode compiles the schema once, then keeps running and recompiles whenever the schema or any file it imports changes – imports of imports included. It prints what it is watching when it starts, and again whenever an edit changes how many imports there are:

BlinkBlox 1.3.9
BlinkBlox is watching for changes:
Entry: net.blink
Imports: 2

While it runs:

  • It checks for changes once a second, by comparing each file’s modification time. Saving a file with no change still counts as a change.
  • A successful compile prints nothing. Silence means the modules on disk are current.
  • A failed compile prints its diagnostic and keeps watching. Fix the schema and save; the next compile replaces the error with silence. The modules from the last good compile stay on disk until then.
  • Missing output directories are created without asking, as if --yes were passed.
  • --profile applies to every recompile. --compact does not; watch mode always renders diagnostics in full.
  • It runs until you stop it with Ctrl+C.

Watch mode finds imports by reading import "..." lines as text. An import that is commented out, or marked with a profile the build leaves out, is still watched. That costs nothing, but a missing file named there has the effect described above.