Tooling Gotchas
Before adding an entry
Would this save someone real debugging time? If you wouldn't warn a teammate about it, don't add it here.
When a section grows to 10+ items, graduate it to its own doc.
Lune
- No
--separator: When spawninglune run script.luau arg1 arg2, do NOT use--between the script path and arguments. Lune passes--through toprocess.args, shifting all arguments by one. - DataModel attributes:
roblox.deserializePlace()returns a DataModel.SetAttributemust be called on a child service (e.g.,game:GetService("Workspace")), not on the DataModel root. - ObjectValue cross-DataModel reparenting: When reparenting instances from one deserialized DataModel to another (e.g., in
combine-test-places.luau), ObjectValues (which are links to other instances) may or may not survive the move. Reparenting a whole subtree as a unit preserves intra-subtree ObjectValue references in practice, but this behavior is not explicitly guaranteed by Lune's@lune/robloxAPI. If batch tests start failing with nil references, this is the first thing to investigate — the fallback is to resolve broken ObjectValues after reparenting by rebuilding them from Name/path lookups.
Symlinks
- Each package under
src/has anode_modules/directory that is symlinked and recursive. Regex searching or recursive file operations (grep -r,rg,find) can consume excessive memory. Always use--ignoreflags to excludenode_modules, or use targeted file paths.
Linter CLI Tools
- Per-package execution: moonwave-extractor, selene, and other linters run via
npx lerna exec --parallelmust be run per-package, not repo-wide. The recursive symlinkednode_modulesundersrc/will cause them to traverse infinitely and freeze. This is whypackage.jsonusesnpx lerna exec --parallelrather than running the tools at the repo root. Same caution applies when debugging locally. - CI annotations: The
linting.ymlworkflow emits GitHub Actions annotations vianevermore tools post-lint-results. For the luau-lsp job (which already has pnpm), annotations run in-job. For stylua/selene/moonwave (lightweight Aftman-only jobs), output is uploaded as artifacts and a separatelint-annotationsjob processes them. GitHub caps annotations at 10 per step and 50 per run — the job summary serves as a fallback for large lint failures. - Template CI annotations: Game and plugin templates use a simpler pattern — every linter job posts annotations inline via
npx @quenty/nevermore-cli tools post-lint-results. No artifact relay or separatelint-annotationsjob needed, sincesetup-nodeis sufficient to runnpx(no pnpm install required in the annotation step).
Rojo
- Nevermore uses a custom fork of Rojo that understands symlinks and turns them into ObjectValues. This is required for development but not for consuming packages.
- Symlink deduplication: When multiple
$pathentries resolve to the same physical filesystem path (common with pnpm workspace links wheresrc/A/node_modules/@quenty/loaderandsrc/B/node_modules/@quenty/loaderboth symlink tosrc/loader), rojo only includes the content once — under whichever tree entry it processes first. The second entry's subtree silently loses those dependencies. This means you cannot combine multiple packages into a single rojo project if they share workspace-linked dependencies. The workaround is to build each package individually with rojo, then merge the outputs using Lune's@lune/robloxAPI (reparenting whole subtrees preserves ObjectValue references within each package).