Contributing
Last updated on
How to change opx_infinity, opx_lib or this site — branches, checks before pushing, code conventions, text, and the documentation to update in the same change.
The rules reviewers apply. In the framework, CONTRIBUTING.md (workflow and house style), docs/MANUAL.md (the design record, formerly the README) and ui/README.md are the authority when they disagree with this page. The framework's README.md is now only its front page.
Branches and pull requests
Nothing is pushed to main directly: branch, push, open a pull request. main is what the test server must be able to run.
| Branch | For |
|---|---|
feat/<thing> | a new capability |
fix/<thing> | a defect |
audit/<thing> | a sweep across the codebase |
Before you push
From the opx_infinity folder, with opx_lib checked out next to it:
lua tests/run.lua # must be green
luac -p $(find . -name '*.lua' -not -name 'open77.lua')
npm run typecheck && npm run build # only if ui/ changed| Check | Catches |
|---|---|
tests/run.lua | boots the real manifest on a stub platform; also fails on a file missing from the manifest, a global the sandbox removes, a client-only global in server code, or a module hanging its internals on OPX |
luac -p | syntax errors. open77.lua is the manifest DSL, not Lua, so it is excluded. |
npm run build | writes web/index.html, the shipped page. Commit it with the ui/ change. |
CI (.github/workflows/check.yml) | the above, plus tools/check-sandbox-globals.py (no metatables or require in shared code) |
The budget meter
The client kills a coroutine resume that runs past its instruction budget (about 10,000 VM instructions), silently, mid-operation. Desktop Lua has no such limit, so a green suite does not prove a handler fits. Run the opt-in meter on any client change:
OPX_BUDGET_METER=5000 lua tests/run.luaAt the end it lists every client call site whose worst single resume cost more than the figure, dearest first: each thread resume, net event, page handler, event handler, key mapping and scheduler job. The stub host's own work is not charged. A site on the list is a resume the platform may kill: split it (see the instruction budget). To pin a fix, tests/run.lua has callCost(fn, ...) and resumeCost(env, control, start, rounds), which count exactly and let a check fail at the old cost.
Code conventions
| Rule | Detail |
|---|---|
| Lua 5.4, not CfxLua | No vector literals, no backtick hashes, no ?., no +=. |
| Indentation | Tabs in .lua; two spaces elsewhere (.editorconfig). LF line endings, UTF-8. |
| Quotes | Single quotes in Lua. |
| One file per manifest line | Never glob scripts. Every .lua file must be listed in open77.lua. |
| Modules talk through contracts | Never reach into another module's files or tables. Nothing new goes on OPX without a reason (the test suite keeps a list). |
| Read settings late | M.Settings inside a phase, never at file scope. |
| Answer, don't raise | Prefer a refusal with a named code. Most functions answer a Result ({ ok = true, value } / { ok = false, error, detail }); error is stable, detail is for logs only. |
| Server decides | Re-check every network payload; see Server authority. |
| Repeating work | OPX.Scheduler.Every, not a hand-written loop. |
| Natives | Look every native up with the Open77 devkit; add its manifest permission in the same change. |
| Comments | Explain why, not what. -- @author <name> on a file header and on a public function. |
Text
- Player-facing text comes from a catalogue:
locale(key, params). Logs and error codes stay in English. modules/<id>/locales.luaholds English and French with the same keys and the same{placeholders}.- Never rename a key: it may be a code on the wire.
See Text and locales.
The WebUI
Read ui/README.md in the framework first; it is binding. Hard-code no colour — use the theme tokens. open77_pause owns Escape.
Commit messages
Say what changed and why it was wrong before. If a change fixes something that failed silently, describe what the player saw: that is what someone will search for later.
Deploying to the test server
- Archive from the commit, not the working tree, then restart and read the journal in the same change. See Install.
- A change a live server must confirm (a native the stub host only imitates) gets a section in
docs/IN_GAME_CHECKLIST.md: steps, expected result, log lines. Tick its boxes in a PR that edits the file, with the builds used. - A limit of the Open77 platform is not worked around in silence: it goes in
docs/PLATFORM_REQUESTS.mdwith the evidence, the current fallback, the exact ask and what to change here once it lands. - Never
DROP DATABASE; empty tables instead, after amysqldump.
Updating this documentation
A change that adds, removes or changes a command, event, contract function, config key or page channel updates this site in the same change. Pages are MDX files under content/docs/**.mdx; each folder's meta.json gives its sidebar title and page order.
Adding a module
- Create
content/docs/opx_infinity/<id>/from an existing module folder (weatheris the model):index.mdx— what it does, the summary table, Commands, how it works, Configuration, Refusal codes.server.mdx/client.mdx— the contract, one##per member.events.mdx— network and local events, hooks, page channels.meta.json—{ "title": "<id>", "pages": ["index", "server", "client", "events"] }. Leave out pages and sections the module does not have.
- Add
"<id>"topagesincontent/docs/opx_infinity/meta.json, in alphabetical order. - Run the checks (below) until nothing is missing.
Page format
Every function, export or event is one heading with a one-line description, the signature snippet, a typed list and a tiny example:
## SetWeather [#server-weather-setweather]
Crosses to a weather and reschedules the next roll.
```lua
weather.SetWeather(name, transitionSeconds, reason)
```
**Parameters**
- name: `string`
- transitionSeconds?: `number`
- 0..300. Default: the row's `TRANSITION_SECONDS`.
- reason?: `string`
**Returns**
- `Result` — errors `no_presets`, `unknown_preset`, `invalid_transition`
```lua title="Example"
local set = weather.SetWeather('rain', 30, 'storm event')
```- Optional parameters end with
?; types in backticks. Omit Parameters or Returns when there are none. - Commands, config keys and refusal codes are tables on the module's
index.mdx, anchor in the first cell. {,}and<outside code are read as JSX: wrap them in backticks.
Anchors
Every published name needs an anchor: [#id] at the end of a heading, a self-closing <a id … /> anywhere else (a table cell).
## SetTime [#server-weather-settime]
| <a id="config-weather-enabled" />`enabled` | `true` | … |The anchor is the name in lower case with every run of other characters replaced by -. Never define one twice on a page, and never change one: links and old-URL redirects depend on it.
| Name | Anchor |
|---|---|
server contract member character.AddMoney | server-character-addmoney |
command opx.admin.player.goto | opx-admin-player-goto |
event opx:net:chat:say | opx-net-chat-say |
page channel menu:choose | page-menu-choose |
config key SELLER_CUT_PERCENT of dealership | config-dealership-seller-cut-percent |
function OPX.Scheduler.Every | opx-scheduler-every |
opx_lib function Lib.Rpc.Call | lib-rpc-call |
server export AddMoney (in content/docs/creators/) | export-server-addmoney |
Checks
npm install
node scripts/check-content.mjs # MDX compiles, front matter, links and anchors
npm run check:api # every published name has an anchor
npm run types:check
npm run dev # local preview with hot reload
npm run build # static export to out/node scripts/check-content.mjs [file-or-dir ...]checks only the paths given.npm run check:apirunsscripts/check-api-coverage.sh: it boots the framework on its stub host and fails on any published name without an anchor. It needs Lua 5.4 and checkouts ofopx_infinityandopx_lib(next to this repository, or./scripts/check-api-coverage.sh /path/to/opx_infinity).
Writing style
- Front matter on every page:
titleand a one-sentencedescription(shown under the title, so do not repeat it). - Snippets first, little prose. Short, direct sentences; tables for anything a reader looks up.
- British spelling.
- Never document something you have not read in the code. If the code and the framework manual (
docs/MANUAL.md) disagree, the code wins; say so to the maintainers. - Callouts are rare:
<Callout type="warn">for a surprising or irreversible effect,type="error"for lost money or data.
Licence
The framework is MIT licensed. A contribution is accepted under the same terms.
Convert from FiveM
How FiveM, ESX and QBCore habits map onto Open77 and opx_infinity — what stays, what changes, and where each familiar framework call now lives.
From opx77_* to opx_infinity
Where every old opx77_* resource, export, event, command and config key went when the framework became the single opx_infinity resource, and what has no replacement.