@mafiahub/services-cli
v0.9.0
Published
MafiaHub build publishing and scripting documentation CLI
Readme
MafiaHub Services CLI
Build publishing and scripting API documentation tooling for MafiaHub mods.
Generate scripting API documentation
The CLI converts schema-v2 binding metadata emitted by the MafiaHub v8pp integration into TypeScript declarations, checks those declarations against C++ binding names, validates them with TypeScript, renders the authoritative reference through typedoc-plugin-markdown, and composes it into a static Astro Starlight site with Pagefind search.
npm install --save-dev @mafiahub/services-cli
npx mafiahub-services docs check --config ./scripting-api/docs.config.json
npx mafiahub-services docs generate --config ./scripting-api/docs.config.json --out ./build/docsProjects own the documentation configuration and API declarations. Authored guides, assets, branding, and theme overrides may live with the mod or in a separate public content repository. The configuration schema is published at node_modules/@mafiahub/services-cli/schema/scripting-docs.schema.json. Metadata-backed targets must define both metadataFile and generatedEntryPoint; the mod build is responsible for producing the metadata JSON before invoking the CLI. A metadata-backed target may also declare environment as "client" or "server", which adds the event-bus method the framework registers only on that side (emitServer for a client, emitAllClients for a server) to the generated EventBus interface.
Name the reference groups
A generated reference is divided by TypeScript kind, which is how the bindings were declared rather than how anyone reads them. Each division therefore carries a plain-language label and a one-line explanation, shown under its heading and reused verbatim in the sidebar so the two never name the same bucket differently.
The defaults suit a scripting API. Override any of them per project:
{
"referenceGroups": {
"classes": { "label": "Runtime classes", "description": "Objects you construct, or receive from the runtime." },
"interfaces": { "label": "Data shapes", "description": "Plain objects passed in and out of API calls." },
"variables": { "label": "Globals", "description": "Always available; nothing to construct or import first." },
"type-aliases": { "label": "Type aliases", "description": "Named unions and helpers used by the signatures above." }
}
}Keys are the reference directories: classes, interfaces, variables, functions, modules, type-aliases, enumerations, and namespaces. An unknown key is rejected. label and description are independent, so renaming a group keeps its default explanation unless you replace that too. A @groupDescription written in the declarations wins over the configured description.
Version the generated reference
Each reference is labelled with the scripting contract version the mod pushed, so readers can tell which release an API page describes. The version reaches the generator in one of two ways, the flag taking precedence:
npx mafiahub-services docs generate \
--config ./scripting-api/docs.config.json \
--out ./build/docs \
--contract-version 1.4.2or, declared by the mod inside reference.config.json so it travels with the published contract:
{
"schemaVersion": 1,
"slug": "example-mod",
"title": "Example scripting API",
"version": "1.4.2"
}A version starting with a digit is rendered with the conventional v prefix (Example scripting API - Server API v1.4.2); a contract revision such as contract-abcdef0 is rendered verbatim. The resolved value is also recorded as version in the generated manifest.json, which docs deploy forwards to the documentation platform as deployment metadata.
Omitting both leaves the reference unlabelled. Nothing is inferred from the surrounding package.json: the declarations are staged outside any package, so an inferred version would always be 0.0.0.
Pipelines that build from a published contract read the version straight out of the public channel manifest:
VERSION=$(curl -fsS "$MAFIAHUB_SERVICES_URL/documentation-contracts/$MOD_SLUG/$CHANNEL/manifest.json" | jq -r .version)
npx mafiahub-services docs generate --config ./scripting-api/reference.config.json --out ./build/docs --contract-version "$VERSION"Resolve the canonical generation target from the Services database before generating a site:
mafiahub-services docs target --api-url "$MAFIAHUB_SERVICES_URL" --mod-id "$MOD_ID" --channel "$CHANNEL" --jsonUse the returned siteUrl and basePath for docs generate. docs deploy resolves the target again and rejects output built for a stale domain or channel path.
The generated layout is shared by every tenant, while each mod controls its own identity through structured branding tokens. This keeps Mafia, Hogwarts, and future projects visually distinct without forking the documentation renderer. All fields are optional and fall back to a neutral, title-derived theme:
{
"title": "Hogwarts Multiplayer",
"tagline": "Build multiplayer experiences in the wizarding world.",
"accentColor": "#d5a84b",
"logo": "documentation/logo.svg",
"branding": {
"mark": "HM",
"eyebrow": "HOGWARTS MULTIPLAYER",
"headerSubtitle": "Scripting reference",
"background": "grid",
"colors": {
"background": "#10100f",
"surface": "#191816",
"text": "#f4f0e8",
"muted": "#aaa397"
},
"heroBadges": ["Generated API", "Community guides"],
"principles": [
{
"title": "Runtime accurate",
"description": "API reference generated from the mod's authoritative bindings."
}
]
}
}mark is used when no logo is configured. background selects the grid treatment or a plain surface. heroBadges and principles customize the landing page; colors customize the dark visual system. For exceptional brand requirements, the existing theme field can point to a mod-owned CSS file which is loaded after the generated theme.
Native event contracts can be exported as a metadata data type named EventMap. Define one property per event and use a named TypeScript tuple for its callback arguments, such as [player: Player, text: string]. The generated declarations preserve that interface as the event reference and add type EventName = keyof EventMap, keeping event-name autocomplete and the HTML event list derived from the same runtime metadata.
Custom Markdown documents can be included for every API target with top-level documents, or for one target with targets.<name>.documents. Paths and glob patterns are resolved relative to docs.config.json. TypeDoc adds these pages to navigation and copies locally referenced images into the generated static bundle.
{
"schemaVersion": 1,
"slug": "example-mod",
"title": "Example scripting API",
"tagline": "Reference and guides for Example Mod.",
"readme": "documentation/README.md",
"documents": ["documentation/guides/**/*.md"],
"targets": {
"client": {
"label": "Client API",
"entryPoint": "generated/client.d.ts",
"tsconfig": "tsconfig.json",
"description": "Client scripting APIs.",
"documents": ["documentation/client/**/*.md"]
}
}
}Keep images beside their guides and reference them with relative Markdown paths, for example .
Compose public community content
Closed-source mods can remain authoritative for scripting declarations while tutorials and resources are maintained in a separate public repository. Declare the public repository and the Markdown globs that are allowed into the generated site:
{
"schemaVersion": 1,
"slug": "example-mod",
"title": "Example scripting API",
"tagline": "Reference and community guides for Example Mod.",
"communityContent": {
"repository": "https://github.com/example/example-mod-docs",
"authority": "community",
"documents": ["guides/**/*.md", "resources/**/*.md"],
"targets": {
"client": ["client/**/*.md"],
"server": ["server/**/*.md"]
}
},
"targets": {
"client": {
"label": "Client API",
"entryPoint": "generated/client.d.ts",
"tsconfig": "tsconfig.json",
"description": "Client scripting APIs."
}
}
}Check out the public repository separately, then pass that checkout and its immutable revision to the generator:
npx mafiahub-services docs generate \
--config ./scripting-api/docs.config.json \
--out ./build/docs \
--content-root ../example-mod-docs \
--content-revision 0123456789abcdefcommunityContent.root and communityContent.revision may be set in the config for local workflows; the command-line values override them. Production builds should always provide an immutable commit revision. The generated manifest.json records separate mod and community authority plus the exact public repository revision.
Set communityContent.authority to "maintainer" when an official project documentation repository owns the authored guides but the closed-source mod remains authoritative for generated API contracts. The pages stay publicly editable while retaining maintainer-authored labels and navigation. Omitting the field preserves the default "community" authority.
Set communityContent.navigation to "unified" when all public guides should appear in one neutral Guides section with clean /guides/<page>/ routes. In unified mode, global and target-specific document patterns are flattened into the same guide collection and collisions are rejected. The default "segmented" mode preserves separate authority and API-target guide groups.
Community content is staged into an isolated temporary directory before TypeDoc runs. Symlinks, active HTML, active SVG content, paths outside the checkout, and TypeDoc children frontmatter are rejected. Include all public pages through communityContent.documents or communityContent.targets globs. Mod-owned documents remain trusted and are resolved from the private configuration directory.
Public guides must use regular Markdown links. TypeDoc inline tags such as {@link EventMap} are rejected because community pages are rendered independently from TypeDoc's API-symbol resolution; link explicitly to the appropriate generated reference route instead.
When the site will be hosted below a path, pass that exact public base to Starlight:
npx mafiahub-services docs generate \
--config ./scripting-api/docs.config.json \
--out ./build/docs \
--content-root ../example-mod-docs \
--content-revision 0123456789abcdef \
--site-url https://docs.mafiahub.dev \
--base-path /example-mod/stablePublish artifacts
mafiahub-services builds publish \
--api-url https://api.example.com \
--mod-id 00000000-0000-0000-0000-000000000000 \
--channel stable \
--version 1.0.0 \
--root ./dist/gameSet MAFIAHUB_BUILD_UPLOAD_TOKEN in CI, or pass --token / --token-file.
--api-url accepts either the API base URL or its /graphql endpoint. Build publishing uses the initBuildUpload and finalizeBuildUpload GraphQL mutations; the build token is sent as X-MafiaHub-Build-Token only to that endpoint and is never forwarded to presigned upload URLs.
The CLI build regenerates apps/api/schema.graphql from the NestJS code-first resolvers, then uses GraphQL Code Generator to produce typed documents in src/generated/graphql.ts. Schema drift therefore fails during code generation or TypeScript compilation instead of at runtime.
Publish public scripting contracts
Closed-source mods publish documentation inputs through the existing content-addressed build uploader using the reserved scripting-api-contract artifact and an isolated docs-contract-<channel> storage channel. Services reuses the mod and public-channel scoped docs_upload token, limits the artifact to the documented declaration/configuration paths, and rejects bundles larger than 16 MiB.
mafiahub-services builds publish \
--api-url https://api.mafiahub.dev \
--mod-id 00000000-0000-4000-8000-000000000001 \
--channel docs-contract-testing \
--version contract-abcdef0 \
--root ./build/scripting-contract \
--metadata artifact=scripting-api-contract \
--metadata release_revision=abcdef0Use the same metadata.release_revision for the mod build and docs contract. Legacy commit and source_revision values are also accepted. Uploads can finish in either order; the channel advances once both are ready. Each build keeps its linked contract, and late uploads cannot roll back a newer release.
promoteBuild moves the build and contract to stable atomically. Existing builds are matched by revision against contract history. Linked contracts cannot be deleted or pruned while their build is retained.
Legacy hosted docs follow their matching build using release_revision, source_revision, or commit. Independent channel changes through setDocumentationChannelRelease are rejected. The standalone Starlight platform still requires output built for its destination URL.
Pass buildId to createReleaseArchiveUpload when publishing concurrently. Otherwise, the archive attaches to the latest published upload in the channel, including uploads waiting for docs. Release notifications wait until the pair is active.
The public docs repository resolves the channel manifest without authentication at /documentation-contracts/<mod-slug>/<channel>/manifest.json, then pins /documentation-contracts/<mod-slug>/releases/<manifest-hash>/.... Channel manifests use short shared caching; revision manifests and verified files are immutable and cacheable for one year. This endpoint exposes documentation contracts only and never makes ordinary game builds public.
Deploy to the standalone documentation platform
The open-source multi-tenant platform replaces main-API file storage for new sites. It accepts the generated static directory, deduplicates files by SHA-256, creates an immutable deployment, and atomically points the requested channel at it:
MAFIAHUB_DOCS_UPLOAD_TOKEN=<database-backed-docs-token> \
mafiahub-services docs deploy \
--api-url https://api.mafiahub.dev \
--mod-id 00000000-0000-4000-8000-000000000001 \
--channel stable \
--version 1.0.0 \
--root ./build/docsUse the database-backed DOCS_UPLOAD token issued for that mod. Services resolves the tenant slug, platform URL, and canonical public target and exchanges the token for a short-lived, channel-scoped deployment grant; Railway does not need a token map when mods are added. Deployment is transactional: the CLI initializes the manifest, uploads only content-addressed objects the platform does not already have using bounded requests, and finalizes the immutable deployment atomically. Interrupted runs resume through hash deduplication. The removed docs publish command and API file-serving routes are not compatibility paths; all documentation bytes are deployed to and served by the standalone platform.
