Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Key rules (full guide with examples in `docs/conventions/luau.md`):

## Testing & Deployment

- `nevermore init-package` scaffolds new packages. It can also fill in missing standard files on existing packages.
- `nevermore init package` scaffolds new packages. It can also fill in missing standard files on existing packages.
- `nevermore deploy run` builds and uploads; pass `--publish` for Published.
- `nevermore ci post-test-results` posts or updates a PR comment with test results.

Expand Down
1 change: 1 addition & 0 deletions docs/_AI_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ This file is an index for AI agents. The `_` prefix keeps it out of Docusaurus.
|-----|-------------|
| [intro.md](intro.md) | Getting started with Nevermore, why use it, key packages |
| [install.md](install.md) | Installation methods: NPM + CLI, existing Rojo projects, plugins |
| [cli.md](cli.md) | `nevermore` CLI command reference: every command and flag (`init`, `install`, `login`, `test`, `deploy`, `batch`, `tools`), global options, command tree |
| [deploy.md](deploy.md) | `nevermore deploy`: login, `deploy init`, `deploy run`, config schema, flag reference, common workflows |
| [architecture/](architecture/index.md) | Architecture: workspace layout, design philosophy, ServiceBag, dependency injection |
| [architecture/patterns.md](architecture/patterns.md) | Core patterns: Maid, BaseObject, Binder, Rx, Brio, Blend, AdorneeData, TieDefinition |
Expand Down
250 changes: 250 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
---
title: CLI Reference
sidebar_position: 4
---

import TOCInline from '@theme/TOCInline';

# The `nevermore` CLI

`nevermore` is the command-line tool that drives the Nevermore workflow: scaffolding new games and packages, installing dependencies, running tests, and deploying places to Roblox via the [Open Cloud API](https://create.roblox.com/docs/cloud). This page is the command reference. For the two deep workflows it links out to their own guides — [Deploying with the CLI](deploy.md) and [Test Infrastructure](testing/testing.md).

## Installing the CLI

You don't have to install anything to use it once — `npx` fetches and runs the latest version:

```bash
npx nevermore init
```

For day-to-day work, install it globally so `nevermore` is always on your `PATH`:

```bash
npm install -g @quenty/nevermore-cli
```

The CLI needs [Node.js](https://nodejs.org/en/download/) v18+. Most commands also expect the Luau toolchain (rojo, aftman) to be available — see [Install](install.md) for the full environment setup.

## Getting help

Every command and subcommand responds to `--help`, and the listing is generated from the CLI itself, so it never drifts from the installed version:

```bash
nevermore --help # top-level commands
nevermore deploy --help # a command and its subcommands
nevermore deploy init --help # a specific subcommand's flags
nevermore --version # installed version (and update check)
```

## Command overview

<TOCInline toc={toc.filter((node) => node.level === 3)} />

At a glance:

```
nevermore
├── init [name] scaffold a game (default), package, or plugin
│ ├── game [name]
│ ├── package [name] [description] [template]
│ └── plugin [name]
├── install [packages..] install @quenty/* packages from npm (alias: i)
├── login store a Roblox Open Cloud API key
├── test test a single package
├── deploy [target] build and upload a place
│ ├── init create deploy.nevermore.json
│ └── run [target] deploy a target (default)
├── batch <subcommand> run across many packages (change detection)
│ ├── test
│ └── deploy
└── tools <subcommand> internal CI utilities
```

## Global options

These apply to every command:

| Flag | Description |
|------|-------------|
| `--yes` | Never prompt; fail instead of asking. Use in CI and scripts. |
| `--dryrun` | Describe what would happen without touching the file system or Roblox. |
| `--verbose` | Show intermediate output (building, uploading, credential loading). Also disables the live spinner in favor of plain, scrollable logs. |
| `--help` | Show help for the current command. |
| `--version` | Print the installed CLI version. |

### `nevermore init`

Scaffolds a new project from a template. The type is a subcommand; if you omit it, `init` scaffolds a **game**.

```bash
nevermore init # scaffold a game in the current directory
nevermore init MyGame # same, named "MyGame" (game is the default)
nevermore init game MyGame # explicit game
nevermore init package brio "Lifecycle-scoped values"
nevermore init plugin MyPlugin
```

| Subcommand | What it creates |
|------------|-----------------|
| `game [game-name]` | A working Nevermore game: `default.project.json`, server/client entry scripts, and the default packages (`loader`, `servicebag`, `binder`, `clienttranslator`, `cmdrservice`). |
| `package [package-name] [description] [package-template]` | A new package inside a monorepo. `package-template` is `library` (default) or `service`. |
| `plugin [plugin-name]` | A Roblox Studio plugin wired up with `loader` and `servicebag`. |

Notes:

- Positional arguments are optional. When omitted, the name defaults to the current directory (and, for `package`, `description` and the name fall back to an existing `package.json`). Run without arguments inside a package to fill in missing standard files.
- `game` and `plugin` also run the toolchain bootstrap after scaffolding: `pnpm install`, `git init`, `aftman install`, `npm run format`, and `selene generate-roblox-std`. Missing tools are reported but don't abort the scaffold.
- `--dryrun` previews the files that would be written.

:::note Back-compat aliases
`nevermore init-package` and `nevermore init-plugin` (hyphenated) still work as hidden back-compat aliases, but the subcommand forms `nevermore init package` and `nevermore init plugin` are canonical. Prefer them.
:::

### `nevermore install`

Installs one or more Nevermore packages from npm. Names are given **without** the `@quenty/` scope — the CLI adds it and validates each name against the published `@quenty/*` packages before installing. Alias: `i`.

```bash
nevermore install maid
nevermore install maid rx blend
nevermore i servicebag # short alias
```

This is a convenience wrapper over `npm install @quenty/<name>`. Plain `npm install @quenty/maid` works identically if you prefer.

### `nevermore login`

Stores a Roblox Open Cloud API key so `test --cloud` and `deploy` can authenticate. Prompts interactively (masked input) unless you pass `--api-key`. The key is validated before it's saved to `~/.nevermore/credentials.json`.

```bash
nevermore login # prompt for a key, validate, and store it
nevermore login --api-key <key> # non-interactive
nevermore login --status # show what's loaded and re-validate it
nevermore login --force # replace an existing key
nevermore login --clear # remove the stored key
```

| Flag | Description |
|------|-------------|
| `--api-key <key>` | Provide the key directly instead of being prompted. |
| `--status` | Report the active credential source (flag, env var, or stored file) and validate it. |
| `--force` | Overwrite an existing key rather than reporting "already logged in". |
| `--clear` | Delete the stored credentials. |

Credentials can also come from the `ROBLOX_OPEN_CLOUD_API_KEY` environment variable (preferred in CI) — see [Deploying → Credentials](deploy.md) for the full resolution order.

### `nevermore test`

Runs the test target for the package in the current directory. Reads the `test` target from `deploy.nevermore.json`. Runs locally by default; pass `--cloud` to run through Open Cloud. See [Test Infrastructure](testing/testing.md) for the full guide.

```bash
nevermore test # run locally
nevermore test --cloud # run via Open Cloud
nevermore test --cloud --logs # include execution logs
nevermore test --cloud --script-text 'print("hi")' # run arbitrary Luau to debug
```

| Flag | Description |
|------|-------------|
| `--cloud` | Run via Open Cloud instead of locally. |
| `--api-key <key>` | Open Cloud API key (`--cloud` only). Otherwise resolved from login/env. |
| `--logs` | Show execution logs even on success. |
| `--universe-id <id>` / `--place-id <id>` | Override the IDs from `deploy.nevermore.json` (`--cloud` only). |
| `--script-template <path>` | Override the Luau script template to execute. |
| `--script-text <luau>` | Run the given Luau directly instead of the configured template. Handy for one-off debugging. |
| `--output <file>` | Write JSON results to a file. |
| `--timeout <seconds>` | Max execution time, sent to Open Cloud so Roblox cancels server-side on overrun (default: 120). |

### `nevermore deploy`

Builds a Rojo project and uploads it to a Roblox place. `deploy` has two subcommands; `run` is the default, so `nevermore deploy` and `nevermore deploy <target>` both deploy. Full walkthrough in [Deploying with the CLI](deploy.md).

```bash
nevermore deploy init # create deploy.nevermore.json (interactive)
nevermore deploy run # build + upload, saved (not live)
nevermore deploy run --publish # build + upload and publish live
nevermore deploy production --publish # 'run' is implied; deploy the 'production' target
```

**`nevermore deploy init`** — writes a `deploy.nevermore.json` for the current package.

| Flag | Description |
|------|-------------|
| `--universe-id <id>` / `--place-id <id>` | Roblox IDs to write into the config. |
| `--target <name>` | Deploy target name (auto-detects `test` or `integration` if omitted). |
| `--project <path>` | Rojo project file, relative to the package. |
| `--script-template <path>` | Luau script `nevermore test` will execute after upload. |
| `--create-place` | Auto-create a new place in the universe (uses cookie auth). |
| `--force` | Overwrite an existing `deploy.nevermore.json`. |

**`nevermore deploy run [target]`** — builds and uploads a target. Defaults to the only target if there's one, otherwise `test`.

| Flag | Description |
|------|-------------|
| `--publish` | Publish the place live. Without it, the upload is *Saved* (draft) only. |
| `--api-key <key>` | Open Cloud API key. Otherwise resolved from login/env. |
| `--universe-id <id>` / `--place-id <id>` | Override config IDs (single-place targets only). |
| `--place-file <path>` | Upload a pre-built `.rbxl` instead of building via rojo (single-place targets only). |
| `--output <file>` | Write JSON results to a file. |
| `--logs` | Show build/upload logs even on success. |

### `nevermore batch`

Runs `test` or `deploy` across many packages at once, using git change detection so PRs only touch what changed. Scans the pnpm workspace for packages with a matching deploy target. See [Test Infrastructure](testing/testing.md) and [Deploying](deploy.md) for how targets are configured.

```bash
nevermore batch test # test packages changed since origin/main
nevermore batch test --all # test every package with a test target
nevermore batch test --cloud --concurrency 3
nevermore batch deploy --all --publish # deploy + publish everything
```

**`nevermore batch test`**

| Flag | Description |
|------|-------------|
| `--cloud` | Run via Open Cloud instead of locally. |
| `--all` | Test every package with a test target, not just changed ones. |
| `--base <ref>` | Git ref to diff against for change detection (default: `origin/main`). |
| `--concurrency <n>` | Max parallel tests (`0` = unlimited, the default). |
| `--aggregated` | Build all packages into a single place and run one batch script (default: **on**). |
| `--batch-place-id <id>` / `--batch-universe-id <id>` | Override IDs for the aggregated upload (`--aggregated` only). |
| `--limit <n>` | Cap the number of packages (local debugging). |
| `--logs` | Show execution logs for every package, not just failures. |
| `--output <file>` | Write JSON results to a file. |
| `--api-key <key>` | Open Cloud API key (`--cloud` only). |
| `--timeout <seconds>` | Max execution time for the whole batch, sent to Open Cloud (default: 300, the API max). |

**`nevermore batch deploy`**

| Flag | Description |
|------|-------------|
| `--target <name>` | Deploy target name in `deploy.nevermore.json` (default: `test`). |
| `--publish` | Publish the places live (default: Saved). |
| `--all` | Deploy every package with the target, not just changed ones. |
| `--base <ref>` | Git ref to diff against (default: `origin/main`). |
| `--concurrency <n>` | Max parallel deploys (default: 3). |
| `--smoke-test` | After deploying targets with a `basePlace`, run server scripts via Open Cloud and fail on boot errors. |
| `--limit <n>` | Cap the number of packages (debugging). |
| `--logs` | Show build/upload logs for every package. |
| `--output <file>` | Write JSON results to a file. |
| `--api-key <key>` | Open Cloud API key. |

### `nevermore tools`

Internal tooling and CI utilities. You rarely run these by hand — they're wired into GitHub Actions workflows — but they're documented here for completeness.

| Subcommand | Purpose |
|------------|---------|
| `post-test-results <input>` | Post test results as a PR comment (requires `GITHUB_TOKEN` and CI context). |
| `post-deploy-results <input>` | Post deploy results as a PR comment (requires `GITHUB_TOKEN` and CI context). |
| `post-lint-results <input>` | Parse linter output and emit GitHub Actions annotations (requires CI context). |
| `download-roblox-types [file-name]` | Download the Roblox Luau type definitions. |
| `strip-sourcemap-jest` | Remove Jest nodes from `sourcemap.json` to avoid luau-lsp name conflicts. |

## See also

- [Install](install.md) — Setting up Node, Rojo, the toolchain, and scaffolding your first project.
- [Deploying with the CLI](deploy.md) — The full `deploy` / `login` workflow, config schema, and credentials.
- [Test Infrastructure](testing/testing.md) — Configuring test targets, credentials, and CI.
- [TypeScript Conventions](conventions/typescript.md) — How the CLI itself is built (for contributors).
30 changes: 28 additions & 2 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ When you run `nevermore deploy run`:

1. Reads `deploy.nevermore.json` in your current directory and resolves the target you asked for (default: `test`).
2. Runs `rojo build` on the target's `project` file to produce an `.rbxl` place file in a temp directory.
3. Uploads the `.rbxl` to the configured `universeId` / `placeId` over Open Cloud.
4. Saves the new version as a draft. If `--publish` is passed, it is also published as the live version.
3. Injects deploy metadata (commit, target, timestamp, place/universe IDs) into the built place if it includes the [`nevermore-cli-manifest`](#reading-deploy-metadata-at-runtime) package.
4. Uploads the `.rbxl` to the configured `universeId` / `placeId` over Open Cloud.
5. Saves the new version as a draft. If `--publish` is passed, it is also published as the live version.

That's the whole pipeline. There are no deploy hooks or post-processing steps to register.

Expand Down Expand Up @@ -235,6 +236,31 @@ If you want to deploy every game affected by a code change (for example, on ever

See [Integration Testing → Batch deploy](testing/integration-testing.md#batch-deploy) for the full flag list and CI usage.

## Reading deploy metadata at runtime

`nevermore deploy` and `nevermore batch deploy` can stamp each build with the deployment that produced it — which commit, which target, when, and whether it was published — so the running game can report its own provenance. This is opt-in by package: a place only gets stamped if it depends on the [`nevermore-cli-manifest`](https://github.com/Quenty/NevermoreEngine/tree/main/src/nevermore-cli-manifest) package.

That package ships a `NevermoreCLIManifestUtils` ModuleScript. Between the rojo build and the upload, the CLI finds that module in the built place and writes the metadata onto it as attributes (via a Lune transform, the same way `basePlace` merges work). Because the data lives on the package's own instance, it replicates to clients automatically. If the module isn't present, the deploy proceeds unchanged.

`nevermore test` and `nevermore batch test` apply the same stamp — but only for packages that ship or directly depend on `nevermore-cli-manifest`, so unrelated packages don't pay for a Lune pass. This is what lets that package's own spec assert the injection actually ran (`getGameMetadata().deployed` is `true`, with a real commit and the `test` target) rather than checking a synthetic fixture. Note the consequence: for those packages, `deployed` is `true` during a test run too, since the test place really was built and uploaded by the CLI.

Read it from either the client or the server:

```lua
local NevermoreCLIManifestUtils = require("NevermoreCLIManifestUtils")

local metadata = NevermoreCLIManifestUtils.getGameMetadata()
if metadata.deployed then
print(string.format("%s @ %s (%s)", metadata.target, metadata.commit, metadata.timestamp))
else
print("Undeployed build (Studio)")
end
```

`metadata.deployed` is the source of truth for "is this a real deploy?" — it's only ever `true` when the CLI injected it, so it stays `false` in Studio and in any place that wasn't deployed through the CLI. The full field list (`commit`, `version`, `branch`, `target`, `timestamp`, `published`, `placeId`, `universeId`) is documented in the [package README](https://github.com/Quenty/NevermoreEngine/tree/main/src/nevermore-cli-manifest). Consumers like `GameConfig`, `GameVersionUtils`, and PlayerMetrics read from this module rather than reaching for the raw attributes.

The attribute names the CLI writes (`Commit`, `Version`, `Target`, …) live in two places that must agree: `buildDeployMetadataAttributes` in `tools/nevermore-cli/src/utils/deploy/deploy-metadata.ts` (the write side) and the `ATTRIBUTE` table in `NevermoreCLIManifestUtils.lua` (the read side). The Lune transform itself is generic — it writes whatever keys it's handed — so adding a field is just those two edits. Note place/universe IDs are written as strings on purpose (Lune serializes number attributes as float32, which corrupts large IDs); the reader converts them back with `tonumber`.

## Common workflows

### First-time setup for a new game
Expand Down
Loading
Loading