Rogen IconRogen

CLI

The complete command-line interface for Rogen.

rogen [command] [name…] [options]

The first positional is always a command. A bare rogen runs build. Any further positionals are config names: rogen build lobby builds lobby.rogen.json. Command names therefore never clash with config names.

rogen lobby is an error, not a shortcut. Rogen suggests rogen build lobby.

Commands

build [name…]

Writes each named config's project file. This is the default command. Several names build in one process, and two configs writing the same file is an error.

When the working directory holds other *.rogen.json files, build names the ones it wasn't asked to build. In a Darklua setup only one config feeds Studio, and the other feeds Darklua, so forgetting either should be visible. rogen build --all builds every config there is.

watch [name…]

Builds, then rebuilds whenever sources or configs change. Only file names and folders matter, so a change to what's inside a script never rebuilds.

watch reloads a config when it, or any config in its extends chain, or its template changes. CLI overrides stay in effect across reloads. When a saved config is invalid, watch prints the error and keeps building with the last valid config for that config, so a broken prod.rogen.json doesn't stop default.rogen.json.

Each rebuild prints a block headed by the time and what triggered it:

┌  rogen watch · default, lobby
│
◇  12:04:31 · 2 files changed
│  ✔ default.project.json · wrote
│  ■ lobby.project.json · not written
│  ■ /repo/lobby.project.json - error: …
│
◇  12:05:02 · default.rogen.json changed · reloaded
│  ✔ default.project.json · unchanged

watch is safe next to build: see Building While Watching. Press Ctrl+C to stop.

where [path…]

Prints where each file lands in the game, and why, without writing anything. One line per file, sorted when no path is given:

$ rogen where src/Net src/Combat/Server/Hit.luau
src/Net/HttpClient.luau -> StarterPlayer/StarterPlayerScripts/Net/Http · route Client (capital suffix)
src/Net/HttpMock.luau -> pruned · tag mock is off (capital suffix)
src/Net/Types.lua -> replaced by src/Net/Types.luau
src/Combat/Server/Hit.luau -> ServerScriptService/Combat/Hit · route Server (folder)

where maps files, so a directory is shorthand for the files inside it, never a line of its own, and it is not a listing of what's on disk. With no path it maps every file in the tree.

It also answers the reverse question. An argument that starts with a service is an instance, written the way Studio prints it or with slashes, and where prints the files placed at it or inside it. Paste the start of an error line as it is: a leading game. and everything from the line number on are dropped.

$ rogen where "ServerScriptService.Combat.Hit:12: attempt to index nil"
src/Combat/Server/Hit.luau -> ServerScriptService/Combat/Hit · route Server (folder)

An instance no file places says no file places it; the template's own nodes, such as package mounts, are among those. When the working directory holds a folder named after the service, the argument is read as that path instead.

A placed file shows its instance path, the governing route and how it matched (folder, marker, suffix, capital suffix or fallback), and any active tags. A file that isn't placed says why: pruned by a dormant tag, replaced by the file that took its name, displaced by a template node, unrouted, excluded with the glob that matched, or outside the root dirs. A path that doesn't exist yet is placed as if it did, since Rogen reads only names, so you can ask before creating a file.

An init folder is one instance, so its init script is the folder's line and every other script below it is a child of it.

Add --json to read the answer as data (see JSON output). Positionals are paths here, not config names. where reads the default config; pick others with -c or --all, and turn tags on or off with -t and -T. With several configs a line is printed once when they all agree, and otherwise once per config, headed by its name.

init [name]

Writes a starting <name>.rogen.json, or default.rogen.json when bare. It detects the language (roblox-ts when there is a tsconfig.json, else Luau), Darklua and packages once, and writes the result down as plain fields. Builds never guess.

In a terminal it asks first, and pressing Enter at every question writes what a non-interactive init writes. On a first run it asks whether you're setting up one place or several; with folders in places/ it sets up several, writing one config per place that extends default. When default.rogen.json already exists, it asks what to add: a place, a variant of default, or a separate config.

Without a terminal it takes those same defaults, so beside an existing default.rogen.json, rogen init lobby adds the place lobby with its code in places/lobby, and a bare rogen init fails, since a place needs a name. A variant is one line to write yourself: { "extends": "./default.rogen.json" }. With --json it never asks either, and prints what it wrote and the next steps as data (see JSON output).

Answers are checked where they're typed: root directories may not nest or repeat, and a roblox-ts project takes one. It never overwrites, and never touches .gitignore or editor settings. When a hand-written project file such as default.project.json exists, which a build would replace, init copies it to template.project.json first, so its name, properties and packages carry over.

It ends with the next steps: the long-running commands to run side by side (the compiler watcher, rogen watch, rojo serve), one darklua process per root directory with Darklua, and where to add routes and tags.

list [name…]

Lists every *.rogen.json in the working directory, with its root directories, sync directory, project file and active tags. With variants as files, this is where "what can this repo build?" is answered.

Name configs to list only those, and use -c, --all, -t and -T to pick configs and turn tags on or off first. With --json it prints each config fully resolved, with every default explicit and every path absolute, which is how to read the common root or an extends chain.

help [command]

Prints usage, or the details of one command.

version

Prints the installed version.

Options

build and watch accept the override flags, and where and list the flags that pick configs: --all, -c, -t and -T. Overrides replace a config value for one invocation. Anything that stays the same belongs in the file.

--all

Every *.rogen.json in the working directory, as if each were named. It takes no names or -c paths, and like several names it rejects -o, -s and --template.

-c, --config <path>

An explicit config path, for anything the name rule can't reach. Repeatable.

-o, --out-file <path>

Overrides outFile.

-s, --sync-dir <path>

Overrides syncDir.

--template <path>

Overrides template. It has no short flag.

-t, --tag <name>

Turns a tag on. Repeatable.

-T, --no-tag <name>

Turns a tag off. Repeatable. It is the capital of -t, the way ssh -t and ssh -T are a pair.

-o, -s and --template target one config, so using them with several names is an error. A tag flag applies wherever a built config declares that tag, and is an error only when none does.

--json

build, where, list and init. Prints one JSON document on stdout instead of text, for a program to read. See JSON output.

Global options

FlagDescription
-h, --helpPrints help.
-v, --versionPrints the version.
--verbosePrints debug output. Can't be combined with --quiet.
-q, --quietPrints errors only. Can't be combined with --verbose.
--no-inputNever asks, and prints plain lines.

Text you asked for, like help, the version and a --json document, is printed even with --quiet.

JSON output

--json replaces a command's text with one JSON document on stdout. The exit code is the one the text form would give, and a failure prints a document too, so a program can read stdout whatever the exit code. Nothing else goes to stdout, --quiet and --verbose don't change it, and it never draws a frame. A mistake in the command line itself, such as an unknown flag, prints { "error": "..." } too: the flag is read before the rest of the line is parsed.

Paths are absolute and use the operating system's separator, so on Windows they have backslashes. Instance paths and glob patterns aren't file paths, and keep /. A diagnostic is an object, and its code is the part a program can match on, since the wording of message may change:

{
	"file": "/repo/default.rogen.json",
	"line": 3,
	"column": 2,
	"severity": "error",
	"code": "config.unknownField",
	"message": "unknown field \"outDir\"."
}

line and column are left out when a diagnostic is about a whole file or folder. When a command fails before it has anything to report, such as when a config is invalid or none is found, the document is { "diagnostics": [...] } for diagnostics, or { "error": "..." } for anything else.

build --json

{
	"configs": [
		{
			"file": "/repo/default.rogen.json",
			"outFile": "/repo/default.project.json",
			"outcome": "wrote",
			"diagnostics": []
		}
	],
	"notBuilding": ["/repo/lobby.rogen.json"]
}

outcome is wrote, unchanged or notWritten. When any config fails to build, none is written, and every config is notWritten with its own diagnostics. notBuilding lists the configs here that the run wasn't asked to build.

where --json

An array with one entry per config and path, in the order the text form prints them, with no lines folded together. Every entry has config and status, and a source path, plus the fields its status carries:

statusFields
placedinstancePath (an array of names), route, routeMatch, tags
prunedtags, the dormant ones
replacedby, the file that took its instance path
displacednode, the template node's path
excludedpattern, the glob that matched
unrouted, outside, ignored, missing, empty, skippednone
noFileinstance in place of source: no file places an instance that was asked for

routeMatch is folder, marker, separator, capital or fallback. Each of tags is { "tag": "mock", "form": "capital" }, where form is folder, marker, separator or capital.

init --json

{
	"files": ["/repo/lobby.rogen.json", "/repo/tsconfig.lobby.json"],
	"notes": [],
	"nextSteps": {
		"setup": ["Add \"include\": [\"src\"] to tsconfig.json, so its own build leaves out the place folders."],
		"run": ["rbxtsc -w -p tsconfig.lobby.json --rojo lobby.project.json", "rogen watch lobby", "rojo serve lobby.project.json"],
		"darklua": [],
		"edits": ["Add tags under \"tags\" in lobby.rogen.json to swap in variants like Analytics.mock.ts."]
	}
}

files lists what was written, in order. notes are what init said before writing, such as a hand-written project file kept as the template. nextSteps groups what to do next: setup holds one-time edits to make before anything runs, run the long-running commands that each need a terminal, darklua the commands that process code into the sync dir, and edits pointers to what to change in the written files. When a write fails, the document is { "files": [...], "error": "..." }, listing the files written before it.

list --json

An object keyed by the config file's absolute path. Each value has extends (the parents, nearest first) and diagnostics, and for a config that resolved, name, rootDirs, commonRoot, routes, tags, exclude, template, syncDir and outFile. Unset template, syncDir and commonRoot are null. A config with errors has only extends and diagnostics, the other configs are still printed, and the exit code is 1.

Output

Every command prints through one logger. In a terminal it draws a framed layout with symbols. Without a terminal (CI, pipes, a redirect) it prints plain lines with no gutter or color, so nothing that greps the output breaks.

Rogen also takes the plain path, and never asks a question, when the CI environment variable is set to anything but empty, 0 or false, when TERM is dumb, and with --no-input. Use --no-input from a tool that runs commands in a pseudo-terminal, where init would otherwise wait for answers.

A build result line is <file> · wrote, <file> · unchanged or <file> · not written. Diagnostics keep their file:line:col - severity: message shape on their own lines under it, so editors and CI can parse them. The file is relative to the working directory, and a warning about a source file is located at that file, one line per file. A command that fails prints the error inside the frame and exits with 1.

On this page