All functionality is exposed through one binary, wowlua.
wowlua <command> [options]12.1 Global options
| Option | Meaning |
|---|---|
-p, --package <name> |
Select a workspace member |
--manifest <path> |
Use a specific wowlua.json |
--locked |
Fail if wowlua.lock would change (E4104) |
--offline |
Use only the cache; never access the network (E4105 if something is missing) |
--frozen |
--locked plus --offline (Package manager §10.6) |
--features <a,b> / --no-default-features |
Select features (Manifest §9.6) |
--message-format human|short|json|github |
Diagnostic output format. human shows each diagnostic with its source lines; a line longer than 160 characters is shown as 120 characters around the labels, with ... where it is cut. short is one line per diagnostic. Both end with a summary, warning: N warnings emitted or error: aborting due to N previous errors; M warnings emitted (not with -q). json emits one JSON object per line, for editors and tools. github emits GitHub Actions annotations. |
--color auto|always|never |
Terminal colors |
-q, --quiet / -v, --verbose |
Output verbosity. Tokens are always redacted. |
--deny-warnings |
Treat warnings as errors |
-j, --jobs <N> |
Worker threads for per-file work (parsing, lowering and finishing, linting); 1 runs it in order on one thread. Default: the number of cores, at most 8. The output and the order of diagnostics do not depend on it. |
12.2 Project commands
| Command | Description |
|---|---|
wowlua new <path> [--kind addon|library|lua-library|types] [--name <Name>] [--flavors <f,…>] [--ci github] |
Create a new package from a template (§12.9) |
wowlua init [--from-toc <file>] [--from-pkgmeta [<file>]] |
Create wowlua.json in an existing directory (§12.6) |
wowlua check [--flavor <f>…] [--watch] |
Parse and type-check without emitting |
wowlua build [--profile <p> | --release] [--flavor <f>…] [--watch] [--out <dir>] [--stats] [--reproducible] |
Build the AddOn into dist/<profile>/<Name>/ |
wowlua dev [--flavor <f>…] [--client <folder>…] [--link] [--wow <install>] [--force] |
Watch, build with the dev profile, and sync the AddOn into WoW (§12.5) |
wowlua install [--profile <p> | --release] [--flavor <f>…] [--client <folder>…] [--wow <install>] [--force] |
Build once (default profile dev, decision T45) and install into WoW (§12.5) |
wowlua uninstall [--client <folder>…] [--wow <install>] |
Remove installed builds of this addon (§12.5) |
wowlua test [filter] [--flavor <f>] [--watch] [--coverage] [--reporter <r>] [--game-data] |
Run tests (Tooling §16.1); --game-data with the data wowlua data pull read |
wowlua data pull --wow <path> [--flavor <f>] |
Read the flavor's game data from a local WoW installation into wowlua's local cache, for wowlua test --game-data (decision T203) |
wowlua fmt [--check] [paths…] |
Format sources |
wowlua lint [--fix] |
Run lints only. --fix applies machine-applicable fixes. |
wowlua fix [--manifest] |
Apply all machine-applicable fixes from diagnostics. --manifest migrates wowlua.json to the current schema (Manifest §9.16). |
wowlua trace [file] [--clipboard] [--build <id>] |
Decode error stacks (Tooling §16.4) |
wowlua prof report [--account <name>] [--client <folder>] [--wow <install>] [--recording <name>…] [--out <file>] [--no-open] |
Write an HTML report of the recordings exported from the dev window (Tooling §16.5) |
wowlua clean |
Remove dist/ (archived source maps in $WOWLUA_HOME/maps/ are kept) |
wowlua explain <code> |
Explain a diagnostic code, with examples (works offline) |
wowlua lsp |
Start the language server on stdio |
wowlua toc [--flavor <f>] |
Print the TOC(s) that would be generated |
wowlua compile <files…> [--profile <p> | --release] [--flavor <f>…] [--bundle | --no-bundle] [--minify <level>] [-o <path>] |
Compile files to Lua without making an AddOn folder (§12.2.1) |
12.2.1 wowlua compile
Compiles the given .wowlua and .lua files (and applies the given
.d.wowlua declaration files) as a build restricted to them: type checks,
imports among them and from wowlua:*, profiles, optimizations and
bundling work as in wowlua build.
- Project. Inside a project (a
wowlua.jsonis found as for every command), its manifest applies: profiles, flavors, API versions, lint levels and its own declaration files. The files must be inside the package. Elsewhere, the files compile with the built-in defaults (profiledev, flavorstandard), rooted at the deepest folder containing them. - Output. The Lua files the build makes for them: the compiled files
(
src/<path>.lua), and the runtime (wowlua/runtime.lua) and standard-library modules (wowlua/std/<name>.lua) when the code needs them. One output file is printed to stdout, or written to-o <file>. Several need-o <dir>: they are written under it, and their load order is printed. TOC, XML, assets and fonts are not written. - Bundling.
--bundle(the default ofrelease) makes one file in the bundle format of builds (Optimization §15.3);--no-bundlekeeps one file per source. - No dev tooling.
dev-console,monitorandcapture-errorsare off: they need an installed AddOn folder. Instrumentation and runtime checks follow the profile (adevcompile of a plain script includes the runtime for its profiler;--releasegives plain Lua). - Output files expect the arguments the client passes to an addon's files,
(addonName, addonTable).
12.3 Dependency commands
| Command | Description |
|---|---|
wowlua add <name>[@<req>] [--git <url> [--tag|--branch|--rev <x>] [--subdir <d>]] [--svn <url> [--rev <n>]] [--path <p>] [--archive <url> --sha256 <h> [--strip-prefix <p>]] [--kind <k>] [--files <a,b>] [--install-path <p>] [--dev] [--optional] [--flavors <f,…>] [--features <a,b>] |
Add (or replace) a dependency and update the lockfile. If the dependency cannot be resolved, wowlua.json is left unchanged |
wowlua add --addon <Name> [--optional] |
Add an addon-dependencies entry |
wowlua remove <name> [--dev] |
Remove a dependency |
wowlua update [<name>…] [--precise <pin>] |
Move locked pins within their declarations: branches and default branches to their newest commit, SVN sources without rev to their newest revision. Tags and explicit revisions are pinned by the manifest. --precise pins one dependency to a given git commit or SVN revision |
wowlua fetch |
Download all dependencies into the cache |
wowlua tree [--flavor <f>] [--invert <name>] |
Show the dependency graph |
wowlua outdated |
List dependencies with newer tags (by SemVer), newer commits on their branch, or newer SVN revisions, and what to change |
wowlua cache clean [--all] |
Remove cache entries not used by any fetch or build for 30 days; --all empties the cache (decision T109) |
wowlua types sync [--flavor <f>…] |
Refresh the flavor table, the pinned interface numbers and the locked wow-api-<flavor> types packages after a game patch (Package manager §10.9) |
wowlua types convert <path> [--out <dir>] |
Convert LuaLS ---@ annotations into .d.wowlua declarations |
add, remove and update rewrite wowlua.json in place: key order,
indentation and line endings are preserved, and only the affected entries change.
12.4 Publishing commands
| Command | Description |
|---|---|
wowlua package [--target <t>] [--flavor <f>…] [--registry] |
Build and package zips into dist/packages/. --registry creates the registry archive of a library package instead (Package manager §10.10.3). |
wowlua publish [--target <t>…] [--dry-run] [--tag <tag>] [--release-type <t>] [--allow-dirty] [--no-test] |
Package and upload (Publishing §11.9) |
wowlua login <type> [--token <t>] [--no-verify] |
Store an API token for a site type (curseforge, wago, wowinterface, github, registry) in the OS credential store (Package manager §10.11). The token comes from --token, from stdin when piped, or from a hidden prompt. CurseForge and GitHub tokens are checked with a read-only request first, and a rejected token is not saved. |
wowlua logout <type> |
Remove a saved API token |
12.5 WoW installation integration
wowlua dev, install and uninstall need the WoW install directory. It is
determined from, in order: the --wow option, the WOWLUA_WOW_PATH
environment variable, the global config, and auto-detection of default install
locations (Battle.net default paths on Windows and macOS).
Clients. Each flavor maps to client folders inside the installation (e.g.
standard → _retail_, _ptr_, _xptr_), from the flavor table
(Manifest §9.4). By default the AddOn is installed
into every existing client folder of every built flavor:
<install>/<client folder>/Interface/AddOns/<Name>/. --flavor restricts the
flavors, and --client restricts the client folders. Every client receives the
same AddOn folder: in split flavor mode it contains all flavors' files, and the
TOC lets each client load its own. A built flavor without any existing client
folder is reported with a note.
| Mode | Behavior |
|---|---|
| copy (default) | Replace the AddOn folder after each successful build (Compiler §13.12). Files the build did not produce are removed, so the folder always matches the build exactly. |
--link |
Create a symlink (macOS, Linux) or directory junction (Windows) to the build folder once. Later builds update it in place. |
Safety. dev and install only replace an existing AddOn folder whose TOC
carries an X-Wowlua-Build field (i.e. was installed by wowlua). Any other
existing folder, such as the same addon installed by an addon manager, is
left untouched and reported as error E4331, unless --force is given.
wowlua uninstall removes folders (or links) of this addon that carry
X-Wowlua-Build, in the selected clients.
A failed build never touches the installed AddOn, so the last working version
stays in place. WoW only reloads Lua on /reload. wowlua dev prints a
reminder after each sync, and in dev builds the dev console's /wl reload is
available.
Rebuilds. wowlua dev rebuilds once per batch of changes: changes saved
while a build runs cause one more build, not one each. It ignores the
manifest's output folder (build.out), which its own builds write. Between
builds it keeps every compiled file in memory and compiles again only what
changed, and does not parse unchanged plain Lua files again unless
check.lua type-checks them
(Compiler §13.9):
each rebuild writes the same files and reports the same diagnostics as a fresh
wowlua build.
12.6 Adopting an existing addon
wowlua init in a folder containing an existing addon:
- Reads the
.tocfile(s) the client finds in the folder:<Folder>.tocand<Folder><suffix>.toc, compared case-insensitively, so the addon name is the folder name (LittleWigs_Classic.tocinLittleWigs_Classic/is the plain TOC, not a_Classicvariant). Suffixes are the current ones and the legacy-Classic,-BCC,_BCC,-WOTLKC,_WOTLKC,_TBC,_Vanillaand_Mainlineforms. It takes the metadata (Title,Notes,Author,Version,IconTexture,Category, …), interface versions (giving the flavors), SavedVariables, dependencies andX-fields intowowlua.json. Addon-wide## AllowLoadGameType:and## ExcludeLoadGameType:lines narrow the flavors. Boolean directives count as set when their value is1ortrue, as in the client (decision T62). - Moves every Lua and XML file the TOCs load (directly or through XML
includes) into
src/, keeping relative paths (git mvinside a Git repository), so the project gets the standard layout (decision T42). If the addon already has asrcfolder of its own, the sources go towowlua-src/andbuild.srcis set accordingly, so nothing is nested in the old folder (decision T60). Paths keep their on-disk spelling, even if a TOC spells them differently. Files that AddOns next to this one load through..\<this>\…lines move too (Manifest §9.7.2). All references are updated: relative<Script>/<Include>paths stay valid because files move together, and absolute in-game paths to moved files (Interface\AddOns\<Name>\<path>) in Lua and XML files are rewritten to the new output location (…\<Name>\src\<path>, also when the sources are inwowlua-src/). Other files (textures, sounds, fonts) stay where they are and are added tobuild.assets, so their in-game paths do not change. - Files loaded by only some flavors' TOCs, or by lines with
[AllowLoadGameType …], are recorded inbuild.flavor-files. Inline directives may stand anywhere in a line, may be written with a colon ([AllowLoadGameType: tbc]), and several may follow each other. Lines loading files of another AddOn (..\Other\file.lua) go tobuild.external-files. File lines with variables ([Game],[Family],[TextLocale]) go tobuild.variable-files, and all their expansions are moved (Manifest §9.7.1). - Records the existing file order in
build.load-order. Each flavor's order comes from the TOC its client reads (decision T53). The orders are merged topologically, so files keep their relative order in every flavor. When the flavors conflict (A before B in one flavor, B before A in another), the merged order follows the first flavor, and each flavor whose TOC order differs keeps its own inbuild.flavor-load-order(decision T126). The compiler keeps these orders wherever load-order analysis finds no reason to change it (Modules §5.4), so the build loads files as before. - Takes per-flavor metadata from the flavors' TOCs: the value most flavors use
becomes the base and the others become
addon.flavor-overrides.Versionand the load flags take the value used by the most flavors among the TOCs that set them.SavedVariablesandSavedVariablesPerCharacterlists that differ between the flavors' TOCs stay per flavor (flavor overrides, decision T128), and publish IDs that differ become publish targets restricted to their flavors (T129), so every flavor's TOC keeps its lines. TOCs with more than 200X-directives getaddon.toc-datafiles (Manifest §9.3.2). Addons that load no code at all (data or archive addons) are valid. Lua and XML files that no TOC loads are listed in a warning and left in place. - With
--from-pkgmeta(or automatically if.pkgmeta/pkgmeta.yamlexists;--from-pkgmeta <file>names another project file, and without one the file the CI workflows pass to the packager with-mis used):externalsbecomedependencies: git, SVN and zip-archive externals are converted (decision T51); hg externals are listed with warning W4333 and must be replaced by a git mirror or archive. The common parent folder of the externals' paths (e.g.Libs) becomesbuild.libs-dir, and each remaining path becomes the dependency'sinstall-path, so the addon's existing XML (embeds.xml) keeps working. URLs are interpreted like the packager does (decision T116): old CurseForge URLs, SVNtrunkandtags/<tag>paths (andtag:on a trunk URL), sub-paths of CurseForge and GitHub git URLs (subdir),.ziparchives (theirsha256is computed by downloading them once), and git otherwise, with"submodules": truelike the packager. Each dependency is named after its folder (Libs/AceAddon-3.0→aceaddon-3-0), takes the TOC lines into its folder (and the files the addon's own XML loads from it) as itsfiles, and gets aflavorsrestriction when only some flavors load it. The TOC lines keep their place inbuild.load-orderunder their output path (decision T106). An external that nothing loads is not converted (warning);tag: latestresolves to the newest tag for git (decision T149) and follows trunk for SVN (warning).ignorebecomesbuild.exclude,plain-copybecomesbuild.assets,package-asbecomesaddon.name,manual-changelogbecomespublish.changelog.move-foldersentries that move whole addon folders become workspace members, adopted the same way; the externals andignoreentries inside such a folder become the member's. A repository whose addons all come frommove-folders(WeakAuras, DBM, ElvUI) becomes a workspace root without a package. Entries that make an external (or a folder of one) an AddOn of its own becomeworkspace.external-addons(decision T127); entries that move a library's inner folder up become the external'ssubdir. Othermove-foldersuses are reported with warning W4332..pkgmetais read line by line like the packager reads it (decision T130), so unquoted patterns such as- *.mdwork.- TOCs are read as the packager packages them:
#@do-not-package@,#@debug@and#@alpha@blocks are dropped and#@non-debug@/#@non-alpha@blocks uncommented. An XML file's references count when any build loads them: the source tree's, a release's or a flavor's (decision T131). - A member's
..\<Name>\TOC lines resolve to the package's other AddOns (decision T133); the files the root addon shares that way move into its sources unless.pkgmetaignores them. license-outputbecomescompat.license-output(decision T136),changelog-titlecompat.changelog-title; a workspace without a root addon recordscompat.package-as(decision T146).- The CurseForge target gets the packager's relations (decision T150).
- When every packager run in
.github/workflowspackages one game, runs with their own project file (-g classic -m .pkgmeta-classic.yml) give that game's flavor its ownignore: what every flavor ignores becomesbuild.exclude, the restbuild.flavor-files(decision T138). Such single-game projects with packager flavor blocks (@non-retail@) getbuild.flavor-mode: "split". - In such single-game projects, and with
enable-toc-creationor-S, a plain TOC with## Interface-<Game>:lines stands for one TOC per game, as the packager makes them (decision T139).@toc-version-<game>@placeholders (theNumyAddon/ToCVersionsaction) take the flavor table's current interface numbers (decision T140). - Network operations (clones, fetches, SVN, downloads) are retried up to three times, like the packager does.
enable-nolib-creation: yesbecomescompat.nolib-package(the packager's nolib zip, for GitHub only; decision T157).- The relation lists (
required-dependencies,optional-dependencies,embedded-libraries,tools-used) become CurseForge relations. wowi-convert-changelog: no,wowi-create-changelog: noandwowi-archive-previous: nobecome the WoWInterface target'schangelog-output: "markdown",changelog: nullandarchive-previous: false(decision T158).- Passthrough files that use packager keywords turn on
compat.packager-keywords. A TOCVersionof@project-version@takes the version of the latest git tag.
- Reads
## X-Curse-Project-ID,## X-Wago-IDand## X-WoWI-IDinto publish targets. - Writes
wowlua.lockpinning the interface numbers the TOCs list, so the first build generates the same## Interfacelines;wowlua types syncmoves them on (decision T115). - Leaves the contents of all
.luafiles untouched except for the path rewrites of step 2. They build as-is, and can be renamed to.wowluaone at a time. - Leaves CI workflows untouched: workflows running the BigWigs packager keep
working. For each,
initprints how to switch it: its packager steps becomesetup-wowluaand onewowlua publishwith the sameenv(plus apandocinstall where the step asked forpandoc), and runs with-dbecomewowlua package(decision T159).
As an alternative to converting, a project may keep its .pkgmeta as the source
of truth for dependencies and publishing with "compat": { "pkgmeta": true }.
The manifest then only adds wowlua-specific settings, and conflicting keys are
error E4005. Externals become dependencies as above (named after their folders);
a manifest entry of the same name may add files, flavors, optional,
sha256 (zip externals) and the other non-source keys, while source keys
(git, svn, tag, install-path, …), build.libs-dir, build.exclude,
addon.name and publish.changelog that .pkgmeta also sets are E4005
(decision T117).
Packager keywords. With "compat": { "packager-keywords": true }, BigWigs
packager keyword substitution is applied to passthrough .lua and .xml
files, as a text preprocessing step before parsing, per build configuration and
flavor (Compiler §13.3.1, stage 5). TOC files are
generated, so TOC keywords do not apply.
| Keyword | wowlua value |
|---|---|
@project-version@ |
build.version |
@project-hash@, @project-abbreviated-hash@, @project-revision@ |
VCS commit hash, short hash, commit count |
@project-author@, @project-date-iso@, @project-date-integer@, @project-timestamp@ |
From the HEAD commit |
@file-*@ variants |
From the last commit touching the file |
@build-date@, @build-date-iso@, @build-date-integer@, @build-timestamp@ |
Build time (SOURCE_DATE_EPOCH when reproducible) |
| Block | Active when |
|---|---|
@debug@ … @end-debug@ |
The profile's debug is true |
@alpha@ … @end-alpha@ |
build.version is an alpha or dev version (untagged build) |
@do-not-package@ … @end-do-not-package@ |
Removed (lines deleted, like the packager) when the profile's debug is false, kept otherwise (decision T120) |
@retail@, @version-retail@ |
Flavor standard |
@version-classic@, @version-vanilla@ |
Flavor vanilla |
@version-bcc@ |
Flavor tbc |
@version-wrath@, @version-titan@ |
Flavor wrath |
@version-cata@ |
Flavor cata |
@version-mists@ |
Flavor mists |
@version-forever@ |
Flavor camelot |
The non- prefixed forms invert a block. Inactive blocks are commented out in
the packager's style, so line numbers stay correct; @do-not-package@ blocks
are deleted like the packager deletes them. Embedded lua-library files get the processing the packager gives its
externals: repository keywords from the library's locked commit or revision,
@debug@ and @do-not-package@ blocks as above, and XML
<!--@no-lib-strip@--> blocks commented out (decision T120). Keywords are applied before the files are checked. Flavor blocks
need build.flavor-mode: "split" when several flavors are built, and then
produce per-flavor variants of the file (decision T118). @localization(...)@ is not supported (W4030). Use
wowlua:locale instead.
12.7 Configuration
Global configuration lives in $WOWLUA_HOME/config.json (default ~/.wowlua/config.json):
{ "wow": { "path": "/Applications/World of Warcraft" }, "registries": { "default": "https://registry.wowlua.dev" }, "dev": { "link": false }, "build": { "jobs": 8, "jobs-memory": 1024, "compile-cache": true, "compile-cache-size": 256 }}build.jobs is the default for --jobs; build.jobs-memory (MiB, default
1024) caps the memory the files compiled at the same time may take: larger
files wait, and one larger than the cap is compiled alone. Projects under
256 KiB of source are compiled on one thread unless --jobs,
WOWLUA_JOBS, WOWLUA_JOBS_MEMORY or these keys set it.
build.compile-cache (default true) keeps Lua compile results between
runs, within build.compile-cache-size MiB (default 256; Package manager
§10.7).
API tokens live in the OS credential store (wowlua login). Only when no
credential store is available does wowlua login write a tokens object into
this file (Package manager §10.11).
WOWLUA_NO_KEYRING=1 makes login, logout and publish skip the OS
credential store and use config.json only (headless machines, tests).
Environment variables override config values: WOWLUA_HOME,
WOWLUA_WOW_PATH, WOWLUA_JOBS and WOWLUA_JOBS_MEMORY (MiB),
WOWLUA_NO_COMPILE_CACHE (set to anything but 0: no compile cache), the token
variables in Publishing §11.1.3, and NO_COLOR.
--jobs overrides both.
12.8 Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | User errors (compile errors, failed tests, failed uploads) |
| 2 | Invalid command-line usage |
| 101 | Internal compiler error. The message asks for a bug report with the build id. |
12.9 Templates (wowlua new)
| Kind | Files |
|---|---|
addon |
wowlua.json, src/main.wowlua, tests/main.test.wowlua, locales/enUS.json, CHANGELOG.md, README.md, .gitignore (dist/; wowlua.lock is committed) |
library |
wowlua.json (kind: "library"), src/init.wowlua, tests/init.test.wowlua, README.md, .gitignore (dist/, wowlua.lock) |
lua-library |
wowlua.json (kind: "lua-library" with lua-library.files), <Name>.lua, README.md, .gitignore |
types |
wowlua.json (kind: "types"), types/index.d.wowlua, README.md, .gitignore |
With --ci github, the template also contains the workflow of §12.10. A Git
repository is initialized when git is available and the target is not
already inside one.
12.10 Continuous integration
A typical GitHub Actions setup checks every push and publishes on tags:
name: cion: push: branches: [main] tags: ["v*"] pull_request:jobs: build: runs-on: ubuntu-latest permissions: contents: write # GitHub releases steps: - uses: actions/checkout@v4 with: { fetch-depth: 0 } # tags and history for build.version and changelogs - uses: DomenikIrrgang/wowlua/.github/actions/setup-wowlua@main # installs the release binary - uses: actions/cache@v4 with: path: ~/.wowlua/cache key: wowlua-${{ hashFiles('wowlua.lock') }} - run: wowlua check --locked --message-format github - run: wowlua test --locked --message-format github - run: wowlua build --release --locked - if: startsWith(github.ref, 'refs/tags/') run: wowlua publish env: CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} WAGO_API_TOKEN: ${{ secrets.WAGO_API_TOKEN }} WOWI_API_TOKEN: ${{ secrets.WOWI_API_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}fetch-depth: 0is needed for tag-basedbuild.versionand"git"changelogs.- Caching
~/.wowlua/cachekeyed by the lockfile hash avoids refetching dependencies and types packages. setup-wowlua(a composite action in the wowlua repository) downloads the prebuilt binary from the project's GitHub Releases (decision T41). Other ways to install: the install scripts attached to every release (install.shfor macOS and Linux,install.ps1for Windows), and later Homebrew (macOS), winget and Scoop (Windows).