Command line
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.
Compiling
Section titled “Compiling”blinkblox netThe schema’s path is looked up in this order, and the first file that exists is used:
- the path exactly as given –
net - with
.txtappended –net.txt - with
.blinkappended –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
TypesOutputis set; - a
.d.tsdeclaration next to each module, whenTypescriptis set. A module namedinitgetsindex.d.ts.
On success it prints its progress and stops:
BlinkBlox 1.3.9Reading source from net.blink...Parsing source into AST...Generating output files...Network files generated!A missing output directory
Section titled “A missing output directory”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:
--yesis 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 releaseDiagnostics
Section titled “Diagnostics”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:
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.9Reading 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 limitIt 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.
Exit codes
Section titled “Exit codes”| 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.
Checking without writing
Section titled “Checking without writing”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.
JSON output
Section titled “JSON output”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.
Verifying committed output
Section titled “Verifying committed output”blinkblox net --verifyA 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.ts2 of 4 generated files do not match the schema. Compile it again to update them.- Exit code
0when every file matches, and it printsAll 4 generated files match the schema.– nothing at all under--quiet. - Exit code
1when 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.tsleft behind afterTypescriptwas 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--jsonand--profile. It cannot be combined with--watch, which is refused with exit code1. - 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.
In a pre-commit hook or CI
Section titled “In a pre-commit hook or CI”Run it from the repository root with the same flags the build uses. With lefthook:
pre-commit: commands: network: glob: "*.{blink,luau,ts}" run: blinkblox net/Game.blink --verify --quietor as a CI step:
- name: Generated network modules match the schema run: blinkblox net/Game.blink --verifyA 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.
Converting another tool’s definitions
Section titled “Converting another tool’s definitions”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
--yesis passed. There is no prompt: without--yesthe command stops with exit code1. --checkwrites 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.blinkcaptures the draft alone.--quietwrites 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
requirewould run them, against a stand-in for the library that records what is defined. They get placeholders forgame,scriptand the other Roblox globals, and no filesystem, network or process library. Arequireof 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,--verifyor--json, and an unknown tool is refused with the list of those it reads.
Watch mode
Section titled “Watch mode”blinkblox net --watchWatch 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.9BlinkBlox is watching for changes: Entry: net.blink Imports: 2While 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
--yeswere passed. --profileapplies to every recompile.--compactdoes 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.
