WikiWire is a GitHub Action that syncs changed files under modules/, templates/, and mediawiki/ to a MediaWiki site via the Action API. Credentials are supplied only through the action inputs (or workflow secrets), never through the config file.
Wiki content lives under a path segment (second directory under modules/ or templates/):
- Modules:
modules/<path_segment>/<root_name>/… - Templates:
templates/<path_segment>/<root_name>/… - MediaWiki:
mediawiki/<path_segment>/…
The <path_segment> directory must match the effective path segment for the site: the site value when set in wikiwire.toml, otherwise the id. The loader indexes only this single derived segment (using the same site if present else id logic) and matches it when loading repo paths. Using an explicit site value removes ambiguity and keeps a stable id in config while the repo folder stays a short name.
Shared bucket (optional): If shared = true in wikiwire.toml, modules/shared/, templates/shared/, and mediawiki/shared/ are synced to every configured site. Wiki titles are the same as for a single site (the shared segment is not part of the title). When shared is false, paths under modules/shared/, templates/shared/, or mediawiki/shared/ cause the action to fail with a clear error. If you want to name a subfolder shared but don't want to use the first-party WikiWire support, name the folder _shared instead.
Groups bucket (optional): If [[groups]] are defined in wikiwire.toml, modules/groups/<group_id>/, templates/groups/<group_id>/, and mediawiki/groups/<group_id>/ are synced to every site listed in that group.
Example:
modules/obbywiki.com/GroupLink/GroupLink.module.lua
modules/obbywiki.com/GroupLink/doc.wikitext
modules/obbywiki.com/GroupLink/styles.css
modules/obbywiki.com/GroupLink/i18n/en.json
templates/obbywiki.com/Infobox/Infobox.template.wikitext
templates/obbywiki.com/MonthNav/MonthNav.template.wikitext
templates/obbywiki.com/MonthNav/styles.css
modules/shared/CommonUtil/CommonUtil.module.lua
modules/groups/agroup/CommonUtil/CommonUtil.module.lua
You can see and use our live repository at https://github.com/obbywiki/modules.
<path_segment>must match a site'sidorsite, or be the literalshared(whenshared = true), or be the literalgroups(followed by a third segment<group_id>for the group feature).<root_name>is the module or template root (e.g.GroupLink). For the main module file and template file, the basename in the filename must match<root_name>.
Any path under modules/ or templates/ that contains a path component starting with _ is skipped (not synced). Examples: modules/_legacy/..., modules/example.com/MyModule/_draft/example.wikitext, modules/example.com/shared/_imported/....
| Root | Repository path | Wiki title | Content model |
|---|---|---|---|
modules |
modules/<path_segment>/<root>/<root>.module.lua |
Module:<root> |
scribunto |
modules |
modules/<path_segment>/<root>/doc.wikitext |
Module:<root>/doc |
wikitext |
modules |
modules/<path_segment>/<root>/<any other path> |
Module:<root>/<any other path> |
See below |
templates |
templates/<path_segment>/<root>/<root>.template.wikitext |
Template:<root> |
wikitext |
templates |
templates/<path_segment>/<root>/doc.wikitext |
Template:<root>/doc |
wikitext |
templates |
templates/<path_segment>/<root>/<any other path> |
Template:<root>/<any other path> |
See below |
mediawiki |
mediawiki/<path_segment>/<any path> |
MediaWiki:<any path> |
See below |
Any other file under modules/<path_segment>/<root>/ maps 1:1: the wiki subpage path is exactly the relative path under <root>/, including nested directories (for example i18n/en.json becomes Module:GroupLink/i18n/en.json).
The same 1:1 rule applies under templates/<path_segment>/<root>/ for every path except the main <root>.template.wikitext (which maps to the bare Template:<root>) and doc.wikitext (which maps to Template:<root>/doc). For example styles.css becomes Template:MonthNav/styles.css.
Templates synced to Template: must live under templates/, not modules/. You can still use regular wikitext files under a template root like any other subpath.
Suffix matching is ordered; the first match wins:
| Pattern | Content model |
|---|---|
*.template.wikitext |
(invalid under modules/; the action will fail) |
*.module.lua |
scribunto |
*.module.luau |
scribunto |
*.wikitext |
wikitext |
*.css |
Per-site css_content_model in wikiwire.toml (default sanitized-css) |
*.json |
json |
*.js |
javascript |
| Anything else | Error: unsupported extension |
(TODO: these should be ignored instead, such as README.md)
Suffix matching uses the same order as under modules/, with one restriction: *.module.lua and *.module.luau are invalid under templates/ (Scribunto modules must live under modules/). The main page must be <root>.template.wikitext at templates/<path_segment>/<root>/<root>.template.wikitext.
| Pattern | Content model |
|---|---|
*.module.lua |
(invalid under templates/; the action will fail) |
*.module.luau |
(invalid under templates/; the action will fail) |
*.wikitext |
wikitext |
*.css |
Per-site css_content_model in wikiwire.toml (default sanitized-css) |
*.json |
json |
*.js |
javascript |
| Anything else | Error: unsupported extension |
Some wikis may reject certain content models on Template: subpages; in that case the Action API returns an error, similar to unusual Module: subpages.
Place at the repository root unless you override with the config_path action input.
Did you know? The Better GitHub File Icons extension supports wikiwire files! Both wikiwire.toml and .wikiwireignore will use the wikiwire logo!
| Key | Type | Required | Description |
|---|---|---|---|
version |
integer | no | Config schema version; default 1. Reserved for future use. |
shared |
boolean | no | If true, enables modules/shared/ and templates/shared/, synced to every [[sites]] entry. Default false. |
| Key | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Stable site key (sessions, logs). Must be unique across rows. |
site |
string | no | Directory name under modules/ and templates/. If omitted, defaults to id. Must be unique across sites. Cannot be shared or groups (reserved names). |
api |
string | yes | Full MediaWiki API URL, e.g. https://example.org/w/api.php. |
dry_run |
boolean | no | If true, only log planned edits; no action=edit requests for this site. |
default_branch |
string | no | If set, the action skips syncing when the workflow ref is not this branch (e.g. refs/heads/main). |
css_content_model |
string | no | Content model for *.css files under modules/ and templates/. Default sanitized-css. Some wikis need css. |
Example:
# This is a global WikiWire configuration file, a CI action which automatically syncs and uploads modules and templates from a Git repo towards a production or upstream MediaWiki instance via bot passwords and the MediaWiki Action API.
# Learn more: https://github.com/obbywiki/wikiwire
version = 1
shared = true
[[sites]]
id = "obbywiki.com"
api = "https://obbywiki.com/w/api.php"
[[sites]]
id = "dev"
site = "dev.example.org"
api = "https://dev.example.org/w/api.php"
dry_run = true
default_branch = "main"
css_content_model = "css"Credentials are not stored in this file. Use action inputs backed by secrets.
| Key | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Directory name under modules/groups/ and templates/groups/. |
sites |
string[] | yes | List of site id values that belong to this group. |
Example:
[[groups]]
id = "agroup"
sites = ["wiki1", "wiki2", "wiki3"]Optional file at the repository root (override with ignore_path). Patterns are relative to the repo root and follow .gitignore semantics (comments #, blank lines ignored; ** and negation supported via the ignore package).
Ignored paths are skipped after change detection and never uploaded. Ignoring a path does not delete anything on the wiki.
Example:
# Legacy copies kept in git only
modules/obbywiki.com/ObbyGameInfobox/ObbyGameInfoboxLegacy.module.lua
modules/obbywiki.com/ObbyGameInfobox/ObbyGameInfoboxLegacy.template.wikitext
# It is recommended to include any file you don't want WikiWire to sync
**/*README.md
**/*requirements.txtPlease note that WikiWire is currently a BETA and this shouldn't be required in the future. Be advised that WikiWire doesn't support markdown or txt files, so syncing them will likely result in an error with-in your CI.
| Input | Required | Default | Description |
|---|---|---|---|
username |
no | "" |
Default bot username for sites not listed in site_credentials. With Bot passwords, use UserName@BotPasswordName. |
password |
no | "" |
Default bot password for sites not listed in site_credentials. |
site_credentials |
no | "" |
JSON object whose keys are site id values from wikiwire.toml (not site). Each value must be {"username":"…","password":"…"}. Overrides the global username / password for that site. Keys that do not match any configured site produce a workflow warning. |
config_path |
no | wikiwire.toml |
Path to the TOML config. |
ignore_path |
no | .wikiwireignore |
Path to the ignore file (may be missing). |
dry_run |
no | false |
If true, no edits are sent (site-level dry_run in TOML still applies per site). |
sync_all |
no | false |
If true, sync every file under modules/ and templates/ from the workspace instead of using commit diffs. Requires a prior checkout of the repo. Not recommended as this may potentially be destructive. |
dark_lua_compat |
no | "" |
Deprecated and ignored. Luau modules are always synced as Scribunto. Add **/*.module.luau to your .wikiwireignore file instead. |
Use a workflow permissions block with at least contents: read so the default GITHUB_TOKEN can call the compare API.
Every site that performs a real (non–dry-run) sync must resolve to a username and password: either the global inputs or a matching entry in site_credentials.
name: WikiWire
on:
push:
branches: [main]
paths:
- 'modules/**'
- 'modules/*'
- 'templates/**'
- 'templates/*'
- 'mediawiki/**'
- 'mediawiki/*'
jobs:
wikiwire:
runs-on: ubuntu-latest
name: Sync files to upstream MediaWiki
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: obbywiki/wikiwire@latest
with:
username: WikiWireBot@BotPasswordNameHere
password: ${{ secrets.WIKI_PASSWORD }}DarkLua support is currently experimental and may be a bit finnicky.
WikiWire uploads whatever is in the checked-out workspace under modules/ and templates/. If you generate or transform Lua/Luau in CI (for example with Darklua), run that step before WikiWire.
For push syncs (default sync_all: false), when the GitHub diff includes a path modules/**/*.module.luau, WikiWire also adds the sibling *.module.lua path (same basename, .module.lua instead of .module.luau) if that file exists in the workspace. That covers CI that runs Darklua on changed Luau and writes *.module.lua without committing it—the Lua file does not have to appear in the commit diff. Put **/*.module.luau in .wikiwireignore if you want only the generated Lua synced to the wiki (both map to the same Module: title).
Use sync_all: "true" when you need to upload from the whole tree (for example many generated files that are not tied to changed *.module.luau paths in the diff).
Example (outline):
steps:
- uses: actions/checkout@v4
# install your tooling (darklua, compiler, etc.)
# run darklua so modules/** contains the final output
- uses: obbywiki/wikiwire@latest
with:
sync_all: "true"
username: WikiWireBot@BotPasswordNameHere
password: ${{ secrets.WIKI_PASSWORD }}You can avoid sync_all in two ways:
-
Commit generated Lua: Keep
*.module.luau, run Darklua, commit sibling*.module.lua. The diff usually lists both; add**/*.module.luauto.wikiwireignoreif you want only the Lua file uploaded. -
CI-only Lua: In the same job, checkout → Darklua (so
*.module.luaexists on disk) → WikiWire. The push diff need only include the changed*.module.luau; WikiWire adds the sibling*.module.luapath when that file exists in the workspace. Still use.wikiwireignorefor*.module.luauso the wiki receives the Lua output, not the Luau source.
Example workflow (outline; first variant assumes *.module.lua outputs are committed alongside sources):
name: WikiWire (with Darklua)
on:
push:
branches: [main]
paths:
- 'modules/**'
- 'templates/**'
- 'mediawiki/**'
jobs:
darklua_check:
name: Verify Darklua outputs are up-to-date
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Install Darklua # (pick your preferred installation method) and make sure it's up to date
run: |
wget https://github.com/seaofvoices/darklua/releases/download/v0.18.0/darklua-linux-x86_64.zip
unzip darklua-linux-x86_64.zip
chmod +x darklua
- name: Regenerate Lua outputs for changed Luau modules
shell: bash
run: |
set -euo pipefail
changed_files="$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")"
while IFS= read -r path; do
[[ "$path" == modules/**/*.module.luau ]] || continue
out_path="${path%.module.luau}.module.lua"
mkdir -p "$(dirname "$out_path")"
# Example CLI shape (adjust flags to your darklua config)
darklua process --config .darklua.json "$path" "$out_path"
done <<< "$changed_files"
- name: Fail if outputs were not committed
run: git diff --exit-code
wikiwire:
name: Sync files to upstream MediaWiki
runs-on: ubuntu-latest
needs: [darklua_check]
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: obbywiki/wikiwire@latest
with:
# default: sync_all: "false"
username: WikiWireBot@BotPasswordNameHere
password: ${{ secrets.WIKI_PASSWORD }}Use site_credentials with one JSON object. Interpolate secrets per field, or store the entire JSON in a single secret and pass site_credentials: ${{ secrets.WIKIWIRE_SITE_CREDENTIALS_JSON }}.
- uses: obbywiki/wikiwire@latest
with:
site_credentials: |
{
"production.example": {
"username": "WikiWireBot@prod",
"password": "${{ secrets.WIKI_PASSWORD_PROD }}"
},
"dev": {
"username": "WikiWireBot@dev",
"password": "${{ secrets.WIKI_PASSWORD_DEV }}"
}
}You can combine global username / password with site_credentials: only sites with an entry in the JSON use the per-site pair; all others use the defaults.
- Store
passwordand per-site passwords in GitHub secrets, not in committed workflow YAML (except${{ secrets.* }}references). - Prefer Bot passwords with the minimum rights needed (
editpage,highvolume, etc.). - The config file must remain free of secrets so it can be committed safely.
- Deletes: Removing a file from git does not delete the wiki page.
- Renames: Appear as delete + add; see deletes.
- Initial push: When GitHub sends an all-zero
beforeSHA, the action uses the singlepushhead commit’s file list instead ofcompareCommits. - Branches: Use per-site
default_branchor workflowon.push.branchesto avoid syncing from unintended branches.
After changing src/, run pnpm install and pnpm build so dist/index.js is updated before tagging a release consumers pin to.