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 0.33.0
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.
--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.

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 is the only flag that takes a value, and its value is never mistaken for the schema: blinkblox --profile dev net compiles net under dev. A missing or misspelt profile 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 0.33.0
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, or --json combined with --watch.

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 one of these is reported in the document instead.

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 empty when the compile failed.

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, including the ones that fail before the schema is read.

--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 --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 0.33.0
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.