Looking for a step-by-step author guide? See the MultiworldGG-Index docs site - "I want the easiest setup" or "I want to write my own
pyproject.toml".This README is the workflow reference (inputs, outputs, version-pinning rules).
Reusable GitHub workflow for per-world MultiworldGG repos. When you want to release a world this action:
- Reads
worlds/<apworld>/archipelago.jsonfrom your repo at the release tag's checked-out source - Builds a pip-installable wheel (
<dist>-<world_version>-py3-none-any.whl) - Uploads the wheel as an asset on the GitHub release
The matching MultiworldGG-Index PR is opened separately by the
Oliver-Multiworld-Squirrel GitHub App after the completed release is
published. Karen's review checks fire automatically once that PR is open. None
of that lives in this action.
In your per-world repo, create .github/workflows/make_pyproject.yml. The
workflow can still respond to published releases, but the recommended path is
to let the helper dispatch it against an existing draft before publication:
name: Create and Release Python Package
on:
release:
types: [published]
workflow_dispatch:
inputs:
release-tag:
description: "Existing draft release tag, e.g. myclgm-1.2.3"
required: true
type: string
source-ref:
description: "Git ref to build. Defaults to release-tag."
required: false
type: string
apworld:
description: "World folder name under worlds/<apworld>/, e.g. myclgm"
required: false
type: string
permissions:
contents: write # for `gh release upload`
jobs:
publish:
uses: MultiworldGG/gen-pymod-release/.github/workflows/build.yml@v3
with:
release-tag: ${{ inputs['release-tag'] || github.event.release.tag_name }}
source-ref: ${{ inputs['source-ref'] || inputs['release-tag'] || github.event.release.tag_name }}
apworld: ${{ inputs.apworld }}Then run the draft-release helper from your world repo:
python /path/to/gen-pymod-release/scripts/prepare_apworld_release.py \
--apworld myclgm \
--version 1.2.3 \
--notes RELEASE_NOTES.md \
--openThe helper reads worlds/<apworld>/archipelago.json, persists repo_url,
normalizes world_version, commits that manifest metadata, pushes the branch
and <apworld>-<world_version> tag, creates or reuses a draft GitHub Release,
dispatches make_pyproject.yml, waits for it, and verifies that the draft has
exactly one wheel asset. Review the attached assets in GitHub and click
Publish. Wheel uploads intentionally do not use --clobber.
For repos that ship a single pip-installable Python package at the repo root
(a pyproject.toml plus a top-level package directory - no worlds/<apworld>/
shape), use the sibling build-wheel.yml workflow. It rewrites
pyproject.toml:[project].version in the runner to the input version, builds
the wheel, tags v<version>, creates a GitHub Release, and uploads the wheel
as an asset. The committed pyproject.toml is not modified - the workflow
input is the single source of truth.
In your client repo, create .github/workflows/release.yml:
name: Release wheel
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (e.g. 0.1.1). Tag will be v<version>."
required: true
type: string
dry-run:
description: "Build only -- skip tag, release, and asset upload."
required: false
type: boolean
default: false
permissions:
contents: write
jobs:
build-and-release:
uses: MultiworldGG/gen-pymod-release/.github/workflows/build-wheel.yml@v3
with:
version: ${{ inputs.version }}
dry-run: ${{ inputs.dry-run }}Inputs accepted by build-wheel.yml:
| name | required | default | notes |
|---|---|---|---|
version |
yes | - | Becomes both the tag (v<version>) and the wheel's metadata version. |
source-ref |
github.sha |
Caller-repo ref to build from. | |
python-version |
"3.13" |
Build interpreter. Pure-Python wheels are py3-none-any regardless. |
|
dry-run |
false |
Build the wheel, skip tag/release/upload. |
Release tag prefix <apworld>-<world_version>: On release events, the
action parses the apworld from the tag. Tag your release as e.g.
generic-1.2.3 to publish worlds/generic/ at 1.2.3.
Draft releases: Use scripts/prepare_apworld_release.py to create the tag
and draft release, then dispatch the caller workflow with release-tag so the
wheel and optional .apworld are attached before publication.
Manual dry-runs: If your caller workflow exposes workflow_dispatch, pass
the apworld input and set dry-run: true. If you pass release-tag, uploads
target that release; without release-tag, the workflow builds only and skips
release upload.
Oliver-the-Multiworld-Squirrel GitHub App must also be installed on your repo (read-only) so it can see the published release and open the Index PR on your behalf. Install it from https://github.com/apps/oliver-the-multiworld-squirrel.
A .apworld is the zipped-folder format end-users drop into custom_worlds/.
It is built by Launcher.py "Build APWorlds" and is separate from the
pip-installable wheel that Oliver consumes for the Index PR.
Add a second job to your make_pyproject.yml to produce both artifacts from
a single draft release:
name: Create and Release Python Package
on:
release:
types: [published]
workflow_dispatch:
inputs:
release-tag:
description: "Existing draft release tag, e.g. myclgm-1.2.3"
required: true
type: string
source-ref:
description: "Git ref to build. Defaults to release-tag."
required: false
type: string
apworld:
description: "World folder name under worlds/<apworld>/, e.g. myclgm"
required: false
type: string
permissions:
contents: write
jobs:
publish-wheel:
uses: MultiworldGG/gen-pymod-release/.github/workflows/build.yml@v3
with:
release-tag: ${{ inputs['release-tag'] || github.event.release.tag_name }}
source-ref: ${{ inputs['source-ref'] || inputs['release-tag'] || github.event.release.tag_name }}
apworld: ${{ inputs.apworld }}
publish-apworld:
uses: MultiworldGG/gen-pymod-release/.github/workflows/build-apworld.yml@v3
with:
game: "Your Game Name"
release-tag: ${{ inputs['release-tag'] || github.event.release.tag_name }}
source-ref: ${{ inputs['source-ref'] || inputs['release-tag'] || github.event.release.tag_name }}
apworld: ${{ inputs.apworld }}publish-wheel is what Oliver consumes (the wheel URL gets a #sha256= pin in
the Index PR). publish-apworld is for users who install .apworld files
directly into custom_worlds/. Drop either job if you do not need it.
| name | required | default | notes |
|---|---|---|---|
game |
yes | - | The world's display name - the value of World.game in your Python class (e.g. "My Cool Game"). Must match exactly; the Launcher looks worlds up by display name. |
mwgg-ref |
"main" |
Ref of MultiworldGG/MultiworldGG to check out as the Launcher host. Pin to a release tag (e.g. "0.6.1") for reproducibility. Always resolves against the canonical monorepo, not your fork. Ignored when from-fork: true. |
|
from-fork |
false |
Set to true when calling from an Archipelago fork (a full source tree with its own Launcher.py). Skips the canonical MWGG checkout and builds from the caller's own tree; mwgg-ref is ignored. |
|
release-tag |
"" |
Existing GitHub Release tag to upload to. Required for pre-publication draft uploads from caller workflow_dispatch; release events default from github.event.release.tag_name. |
|
source-ref |
release tag, else github.sha |
Caller-repo ref to check out for the world source. Prefer this input for new callers. | |
apworld |
"" |
World folder name under worlds/<apworld>/. Ignored on release events, where the apworld is parsed from the release tag prefix. Required for manual/non-release dry-runs. |
|
apworld-source-ref |
release tag, else github.sha |
Legacy alias for source-ref; kept for existing callers. |
|
dry-run |
false |
Build the .apworld but skip gh release upload. |
A single <apworld>.apworld file attached to the GitHub release as an asset.
The apworld is inferred from the release tag prefix (<apworld>-<version>).
The upload uses --clobber - unlike the wheel, the .apworld is not pinned
by a SHA256 fragment on the Index side, so overwriting on workflow re-runs is
safe.
https://github.com/<owner>/<repo>/releases/download/<release_tag>/<apworld>.apworld
Your world's source must live at worlds/<apworld>/ in your repo.
archipelago.json is required at worlds/<apworld>/archipelago.json and must
include world_version.
If you ship worlds/<apworld>/pyproject.toml, it is used as-is - version,
authors, and description are injected from archipelago.json only when
missing. Otherwise a minimal default is generated.
The archipelago.json inside the shaped wheel carries a components key: the
list the MultiworldGG launcher renders without importing world code. Each entry
mirrors one of the world's LauncherComponents.Component registrations -
{"name": <display_name>, "type": "client"|"tool"|"adjuster"} plus an optional
"description"; MISC and HIDDEN components are never declared.
If your source archipelago.json hand-authors a components list, it is
authoritative and ships unchanged; the build only warns when it disagrees with
the registrations found by static scan. Without one, the list is derived
automatically from the world source and written into the wheel's copy of the
manifest - your source file is not modified.
| name | required | default | notes |
|---|---|---|---|
release-tag |
"" |
Existing GitHub Release tag to upload to. Required for pre-publication draft uploads from caller workflow_dispatch; release events default from github.event.release.tag_name. |
|
apworld |
"" |
World folder name under worlds/<apworld>/. Ignored on release events, where the apworld is parsed from the release tag prefix. Required for manual/non-release dry-runs. |
|
source-ref |
release tag, else github.sha |
What to check out from your repo. | |
dry-run |
false |
Shape + build the wheel but skip the release-asset upload. |
A single .whl file attached to the GitHub release as an asset. Its URL has
the form:
https://github.com/<owner>/<repo>/releases/download/<release_tag>/<dist>-<world_version>-py3-none-any.whl
This URL is what the Index manifest's module_location will pin to. Pip can
install it directly: pip install <url>.
The release tag is the immutability boundary. GitHub does not silently allow re-publishing a release tag at a different SHA - you must manually delete and re-create the release to do so.
The action uploads without --clobber: re-running the workflow on a release
that already has a .whl asset will fail (gh release upload refuses to
overwrite). This is deliberate - the asset bytes are pinned by a
#sha256=<hex> fragment on the consumer side once Oliver opens the Index PR,
and a silent overwrite would invalidate that pin without warning. To fix a
transient build failure on an existing release, either
gh release delete-asset <release_tag> <asset> first, or delete and recreate
the entire release. Both are explicit human actions.
To publish a new build, bump world_version in archipelago.json, tag a
new release, and let the workflow run.
Pin to a major-version tag (@v3); patch updates fast-forward v3. Breaking
changes cut a new major. Pin to a SHA (@<full-sha>) for full reproducibility.
.github/workflows/
build.yml per-world reusable workflow (workflow_call) -- worlds/<apworld>/ layout
build-apworld.yml per-world reusable workflow (workflow_call) -- builds .apworld via Launcher.py
build-wheel.yml pure-Python reusable workflow (workflow_call) -- flat-layout setuptools repos
scripts/
prepare_apworld_release.py local helper that creates the tag + draft GitHub Release
shape_tree.py build the temp orphan tree from caller's worlds/<apworld>/
build_apworld_wheel.py local one-shot wrapper: shape_tree + `python -m build --wheel`
for fork authors building the wheel by hand (no CI)
templates/
pyproject.toml.j2 fallback per-world pyproject (only used when caller has none)
README.md.j2 auto-generated README that ships inside the wheel
MIT (see LICENSE).