This repository hosts a JSON index of web-playable RPG Maker games found on GitHub.
Data file:
list.json
The list is sorted by title. Language metadata is intentionally omitted until it can be verified reliably.
The Index GitHub RPG Maker repositories workflow runs every three hours and searches GitHub code for RPG Maker MV/MZ HTML entries and MZ main.js loaders. It adds repositories that are not already in list.json. Candidates are saved in candidate-queue.json; at most 32 repositories are inspected per run (MAX_CANDIDATES_PER_RUN). Same-name repositories are queued independently, and failed candidates move to the back of the queue.
The Fork listed repositories workflow runs automatically after the index workflow succeeds. It reads list.json, deduplicates source repositories, and forks missing repositories into WebRPG-org.
Add a repository or organization secret named WEBRPG_FORK_TOKEN to create forks. The same token can be used for code search, or you can add a separate WEBRPG_SEARCH_TOKEN.
The token must belong to a user or app that can create repositories in WebRPG-org. For fine-grained tokens, GitHub documents the fork endpoint as requiring repository Administration write permission and Contents read permission.
The workflow waits between fork creation requests to avoid GitHub secondary rate limits. Defaults:
create_delay_seconds:20retry_limit:5retry_base_delay_seconds:60
If GitHub still reports that requests were submitted too quickly, increase CREATE_DELAY_SECONDS in .github/workflows/fork-listed-repos.yml. Existing forks are detected and skipped.
The prepare workflow also treats WebRPG forks as disposable generated deployments:
- It reads the upstream repository's default-branch commit.
- When the upstream commit differs from the recorded
sourceHeadSha, it force-resets the fork's default branch to the upstream commit, discarding previous generated changes. - It then runs the normal validation, flattening, analytics injection, cover generation, and Pages setup again.
- The index records
sourceDefaultBranch,sourceHeadSha, andprocessedHeadShafor processed forks.
Repositories are skipped when:
- The source repository is already forked into
WebRPG-org. WebRPG-orgalready has a repository with the target fork name.- The
list.jsonentry is markedinvalid_structure,deleted_invalid_structure, orduplicate_name. - Another entry already covers the same repository, identified by
owner/name.
Fork names use this format:
sourceOwner-sourceRepo
This avoids common name collisions. scripts/repo-identity.mjs allocates names for the entire index, including terminal entries, and reserves recorded names first. Ambiguous pairs such as a-b/c and a/b-c receive a hash suffix. plannedForkName persists an allocation before creation; forkName records the actual repository, including legacy names. Fork reports carry actual names to both planning and aggregation.
The Prepare fork repositories workflow runs automatically after the fork workflow succeeds. It processes fork repositories that already exist in WebRPG-org.
When the HTML entry lives inside the project directory, it moves the project to the repository root. Outside files retain their paths and modes; the game wins identical file-path collisions, and file/directory conflicts stop processing. HTML and startup contents are reread from the resulting tree before analytics injection. An entry outside the project directory is kept in place so its relative script references remain valid. It then:
- Adds this analytics script tag to validated HTML entries sharing the selected game project if they do not already contain it:
<script defer src="https://insight.ravelloh.com/script.js?siteId=5ace6623-f51b-4571-8f60-e0473ea3317b"></script>- Enables GitHub Pages from the repository default branch and
/.
The public Pages URL path is determined by the repository name. For example, WebRPG-org/example-game is published at:
https://webrpg.org/example-game/
For a non-index entry such as game.html, pagesUrl points to https://webrpg.org/example-game/game.html. Deployment verification checks that exact public link. Cover URLs still resolve against the repository root.
This workflow uses a GitHub App token. Create and install a GitHub App on WebRPG-org, then add these Actions settings to this repository or to the organization with access granted to this repository:
- Variable:
WEBRPG_APP_CLIENT_ID - Secret:
WEBRPG_APP_PRIVATE_KEY
Recommended GitHub App repository permissions:
Administration: read and writeContents: read and writePages: read and writeMetadata: read-only
Install the App on all repositories in WebRPG-org. This matters because new fork repositories will be added over time; a selected-repositories installation will not automatically include new forks.
The workflow is fully automatic. It does not run in dry-run mode.
During each run, the workflow validates every matching fork before preparing Pages. scripts/rpgmaker-project.mjs requires a real HTML entry loading js/main.js, all engine runtime scripts and plugins.js, and the standard startup database JSON files. Local script references must resolve to existing files with exact casing. MZ loaders using scriptUrls are supported. Scores only select between candidates that meet these hard conditions.
When dry_run=false and delete_invalid_repos=true, invalid forks are deleted from WebRPG-org — unless the repository has no upstream, in which case it may be the only copy and is kept. The final aggregation job updates list.json with validation metadata:
statuscheckedAtforkNamepagesUrlentryPathcoverinvalidReasondeletedAtlastCheckErrorconsecutiveFailureslastFailedAt
Cover URLs are inferred from the fork's title screens: img/titles1/* first, then img/titles2/*. Nothing else is used — the application icon and favicons are the wrong shape and look broken as a full-size banner. Encrypted .rpgmvp covers are decrypted back to PNG before being committed.
status |
Meaning |
|---|---|
indexed |
Discovered by the index workflow, not checked yet. |
verified |
The fork has a complete RPG Maker MV/MZ web structure and Pages is enabled. |
invalid_structure |
No usable project was found; the fork is deleted and never re-forked. |
skipped_large |
The upstream repository exceeds a size limit, so it is not prepared. |
duplicate_name |
Another entry already maps to this fork. |
hidden |
Manually hidden in list.json; its fork is removed on the next prepare run if it has an upstream. Detached copies are kept. |
check_error |
The last check could not reach a verdict; the entry is not advertised as playable. |
unavailable |
The check cannot succeed. Retired without further retries. |
retry_exhausted |
Every retry failed. Retired, but kept so it can be revived by hand. |
Every status other than indexed, verified and check_error is terminal. scripts/repo-status.mjs holds that list so the fork workflow and the prepare plan cannot drift apart, skip an entry in one and act on it in the other.
A repository is identified by owner/name. Its name on its own identifies nothing: two unrelated repositories can share one, and collapsing them on the name alone silently dropped whichever game arrived second. Only a genuine repeat of the same repository is treated as a duplicate.
Fork names follow the same idea — sourceOwner-sourceRepo — so repositories that share a name but not an owner each keep their own fork.
normalize-list.mjs, used by indexing and the one-off metadata migration, only lifts a duplicate_name owned by indexing, matched through duplicateReason. A duplicate recorded by the aggregation job comes from several entries sharing one fork, and lifting that one here would make the two jobs undo each other on every run.
Processing budgets defer candidates in the persistent queue instead of excluding repositories based on their name. Existing entry IDs are retained; colliding newly generated IDs receive a source hash suffix. Check results include entryId and indexedSource, so aggregation cannot assign a result to an unrelated source or revive a terminal entry.
pagesUrl, cover, coverPath, entryPath, projectRoot and deployment-verification fields only describe a fork that the most recent check verified. Every other outcome clears them, so an entry cannot advertise a page, cover or entry path that is no longer prepared. A successful verified result additionally clears old failure, deletion and duplicate metadata. Historical verified entries without a public link return to indexed and have their check timestamp cleared for prompt reprocessing. The discovered repo, owner, name and id remain stable; sourceRepo separately records the immediate upstream used for synchronization.
process-fork-repo.mjs classifies every failure as transient or permanent and records it as failureKind:
- transient — server errors, network failures, rate limits, and lost ref races (
Update is not a fast forward). These recover on their own. - permanent — a
404, a403that is not rate limiting (such asResource not accessible by integration, which means the GitHub App is not installed on that repository), or a422the API rejects outright.
A failing check increments consecutiveFailures and records lastFailedAt, and the entry degrades in stages:
- Under
FAILURE_THRESHOLD(default3) failures, with apagesUrlon record, the entry keeps its status and its link. A rate limit must not pull a working game off the site. - From
FAILURE_THRESHOLDfailures:check_error. Not advertised as playable, still retried. RETRY_LIMIT(default8) consecutive failures:retry_exhausted. Retired.- Any
permanentfailure:unavailable, retired immediately.
The fork workflow can also fail before a check ever runs. It reports those failures to the prepare workflow, which retires an entry when its source repository cannot be forked at all: a name that no longer exists will not resolve on a later run, and until then every run retried it while the entry stayed indexed forever. Transient fork failures are left alone and retried as before. Every successful fork run uploads a report, including when there are no failures; report-download errors stop Prepare rather than silently dropping outcomes. An empty prepare plan still runs aggregation to record permanent fork failures. Target-name conflicts are reported and excluded from processing.
Once a repository is prepared, process-fork-repo.mjs fetches its exact pagesUrl, checks its startup references against the repository structure, fetches main.js and parses data/System.json, and checks the remaining startup resources with HEAD requests. A fresh deployment, source reset, directory move or Pages configuration change defers verification until the next run. Verification metadata records a successful, deferred or inconclusive deployment check; structural verification is not a browser gameplay test.
A missing page, or one that answers with something other than the game, fails the check and enters the usual ladder. Cloudflare challenges, blocked requests, network errors and 5xx responses are inconclusive and recorded without failing: they say more about the network than about the deployment, and a site-wide outage must not retire every entry at once.
Membership in the index is what makes a repository one of ours, not GitHub's fork flag. A fork loses that flag when its upstream is deleted, made private or transferred away, and those repositories are the ones still serving the game.
The prepare plan selects every repository named in list.json, and process-fork-repo.mjs validates a repository without an upstream in place instead of dropping it. There is nothing to synchronize from, and nothing to recover if it is deleted, so such a repository is never removed.
Retired entries are skipped by the fork and prepare workflows and have their derived metadata cleared, but the entry itself stays in list.json. That record is what stops the index workflow from discovering the same repository again, forking it a second time and repeating the same failure. Their forks are kept as well: when the upstream repository is gone, the fork may be the only remaining copy of the game.
Setting the status back to indexed returns an entry to the queue.
A fork is claimed by a single entry. When several entries map to the same fork — a monorepo exposing more than one project — the selected active owner keeps the result and the others become duplicate_name with their derived metadata cleared.
plan-fork-repos.mjs orders forks by the checkedAt recorded in list.json, least recently checked first. Ordering by the repository's own updated_at stranded forks that fail validation: a failed run never bumps updated_at, so the same repositories were retried on every run while the rest of the queue never advanced.
Terminal entries are skipped by the fork workflow, so a fork that was deleted is never recreated on a later run. hidden is the exception: the prepare plan still visits it so its fork can be removed when an upstream exists.
The plan is uploaded before matrix processing. Aggregation synthesizes a transient failure for every planned job without a matching result, including failed checkout, App-token creation or artifact upload. These attempts update checkedAt and use the same failure ladder as script failures, so they cannot remain at the front of the queue indefinitely.
Run node --test tests/*.test.mjs. The suite runs the real script entry points with isolated files and mocked network requests. It covers MV/MZ deployment, directory moves, missing startup resources, public-entry URLs, naming collisions, duplicate recovery, deferred indexing, actual fork names, metadata cleanup and failed matrix jobs. Test index pipeline runs the suite on pushes and pull requests.