Working on the documentation
This site lives in website/ and is built with
Docusaurus 3. Part of it is hand-written, part is generated
from the Haxe sources so it cannot drift.
Running it locally
cd website
npm install
npm start # dev server with hot reload on http://localhost:3000
npm run build # production build into website/build
npm run serve # serve the production build
npm start and npm run build both regenerate the derived pages first (see
prestart / prebuild in package.json), so the reference is always current.
Layout
website/
├── docs/
│ ├── intro.md
│ ├── getting-started/ hand-written
│ ├── architecture/ hand-written
│ ├── subsystems/ hand-written
│ ├── modding/ hand-written + 2 generated pages
│ ├── contributing/ hand-written
│ └── reference/ GENERATED — do not edit
├── scripts/
│ ├── haxe-parser.mjs minimal Haxe parser (no dependencies)
│ ├── generate-api-docs.mjs source/**.hx → docs/reference/**
│ ├── generate-script-api.mjs psychlua/** → the Lua/Python API pages
│ └── check-doc-syntax.mjs lint: admonitions and other silent render failures
├── src/ theme customisation and the landing page
├── static/img/ logo and favicon
├── docusaurus.config.js
└── sidebars.js one autogenerated sidebar from the folder tree
The generators
| Command | Reads | Writes |
|---|---|---|
npm run gen:api | source/**/*.hx | docs/reference/** (one page per type, one index per package) |
npm run gen:scripts | source/psychlua/** | docs/modding/lua-api-reference.md, docs/modding/python-api-reference.md |
npm run gen | both | both |
How the API generator works
scripts/haxe-parser.mjs is a small lexical parser — it does not type-check, it extracts
shape. For every module it records the package, imports, types (class, interface,
enum, abstract, typedef), inheritance, fields, methods with full signatures, enum
values, structure fields, metadata, and the #if stack active at each declaration.
scripts/generate-api-docs.mjs renders that into markdown: a fact table, the doc comment,
known subclasses, public fields, public methods with parameter tables and a GitHub source
link per member, and a collapsed Internals section for private members. Package
descriptions come from the PACKAGE_INFO table at the top of that file — add an entry
when you add a package.
How the script-API generator works
scripts/generate-script-api.mjs scans for registerFunction("name", function(...)) in
source/psychlua/, splitting Lua (callbacks/) from Python (pystdlib/). It uses the
parameter list for the signature and the // comment block directly above the
registration as the description, then groups functions by their source module using the
GROUPS table.
Put a // comment immediately above registerFunction(...) and it becomes the function's
description on the reference page. The same is true for /** ... */ on Haxe types and
fields.
Adding a page
-
Create a markdown file in the right
docs/folder. -
Give it front matter:
---title: My pagesidebar_position: 7description: One sentence for search engines and the category index.--- -
The sidebar is autogenerated from the folder tree, so nothing else is needed. Category labels and ordering come from each folder's
_category_.json.
Markdown gotchas
docusaurus.config.js sets markdown.format: 'detect', so .md files are parsed as
CommonMark and only .mdx files as MDX. That is what lets the generated pages print Haxe
generics (Array<Foo>) literally, but it also means MDX-only conveniences are unavailable in
.md:
{and<can still be read as expressions/JSX in.mdx— wrap identifiers in backticks.- Inside tables, escape
|as\|. - The generators already handle both for generated content.
Admonitions (the coloured notices)
future.v4 disables the MDX-v1 compatibility layer, so the old space-separated title is
not recognised any more — :::info Generated page builds without an error and shows the
raw colons as a paragraph. Use the directive-label form, with a plain-text title, and keep a
blank line after the opening fence and before the closing one:
:::info[Generated page]
Some **content** here.
:::
Three rules for .md files:
| Do | Because |
|---|---|
:::tip[Title], never :::tip Title | only the directive label is parsed once mdx1Compat is off |
Keep titles plain text (no `code`, no <, no {}) | a complex title leaks a raw <mdxAdmonitionTitle> tag in CommonMark |
| Leave the body fenced by blank lines | Prettier otherwise reflows the block into something invalid |
npm run lint:docs (website/scripts/check-doc-syntax.mjs) enforces all three and runs as
part of prebuild and in the docs workflow, so a notice that would render as plain text
fails the build instead of shipping.
Deployment
.github/workflows/docs.yml builds the site and publishes it to GitHub Pages on pushes to
main that touch website/, source/ or the root documentation files — and manually via
workflow_dispatch. Because the reference is regenerated during the build, a change to a
Haxe doc comment updates the published site with no extra step.
The site is served from https://havaianasdestruido.github.io/FNF-Phoenix-Engine/; the
url and baseUrl live in docusaurus.config.js.
When to update what
| You changed | Update |
|---|---|
| A Haxe doc comment | nothing — regenerate |
| A new package | PACKAGE_INFO in generate-api-docs.mjs |
| A new Lua/Python callback | nothing — regenerate (add a // comment for a description) |
| A build flag | Project configuration |
| A subsystem's behaviour | the matching page in docs/subsystems/ |
| A haxelib patch | Haxelib patches and BUILDING.md |