External Game Repo Workflow
This workflow is the reproducible path for a game that lives outside the TPG monorepo but still builds, registers, and launches through the platform.
Use it when you want to prove that an author-owned repository depends only on public packages, bundle files, and registry API endpoints.
For a supported starting point outside the monorepo, use
npm create @tpgames/game@latest. If @tpgames/sdk-dev-kit is already
installed, tpgames init <target-dir> creates the same starter. The package embeds and customizes
templates/game-kit-starter/. The
examples/external-game-repo/ directory remains the proof fixture for detached
package install and registry upload behavior.
What the Workflow Proves
- the game repository can be copied away from the monorepo and still run its own package scripts
- the public
@tpgames/game-kitauthoring package can be installed into that detached repository - TypeScript host/controller surfaces can be built before packaging
npm run bundleproduces a registry-ready zip archive without requiring a host-installedzipbinarynpm run register -- --mode publish --yesuploads, submits, publishes, and verifies the game through registry HTTP APIs with a scoped creator API key- the shell discovers the published manifest and loads the published host and controller assets from the registry asset origin
- authors can exercise the same shell and websocket room flow on multiple physical devices before any registry upload
Reference Fixture
The checked-in fixture is the current reference implementation:
The fixture intentionally keeps the public repository contract small:
package.jsondeclares the public TPG packages the game needs.src/host.tsandsrc/controller.tsuse@tpgames/game-kit, which owns iframe postMessage setup and runtime bootstrap.tpgames bundlereadstpgames.json, rendersdist/manifest.json, copies the package-backed HTML templates plus Vite-built surface assets, and writes the zip.tpgames registersafely stops after upload by default;tpgames register --mode publish --yesalso submits, creator-publishes, and verifies the manifest against the production registry or an explicit override..github/workflows/register-game.ymlfirst resolves aci-skip:head commit subject, plus Cloudflare-compatible skip subjects such as[CI Skip], as a green early exit. It then runs pull request checks without registry secrets and uploads the generateddist/*.zipfile as aregistry-bundleActions artifact for review. It runsnpm run register -- --mode publish --yesonly for configuredmainbranch or manual publish events.
Starter Template
The author-facing starter lives at:
Create a customized project from any directory with:
It has the same browser-authenticated local and API-key CI registration
contract as the fixture, but uses
author-facing naming, a surfaces/ folder, flat starter HTML, and a tiny typed
host/controller game that demonstrates shared state and player state through
@tpgames/game-kit. The scaffold command sets the package name, game id, title,
and version defaults while keeping iframe messaging behind @tpgames/game-kit.
The generated workflow runs the shared npm run check contract on pull requests
to main, then keeps the generated dist/*.zip bundle
available as a registry-bundle Actions artifact. Upload/publish still requires
configured TPG_API_KEY secret on a main push or
manual dispatch. npm run publish:dry-run proves the intended archive and
registry operation locally without credentials.
Generated projects also include AGENTS.md and a self-contained
tpg-game-development skill. Compatible coding agents can use it without
access to this monorepo.
Multi-device development without uploading
The starter and reference fixture configure tpgWorkbench(...), so their local
Vite surfaces can be attached to a real shell room:
tpgames starts the local server, creates a temporary Cloudflare Quick
Tunnel, and opens a random room at https://play.tp.games. The host fetches a
bounded development manifest once, validates its game ID and same-origin entry
base, and places the normalized manifest—not the source URL—in the authoritative
room snapshot. Controllers and spectators therefore mount the same local
surface URLs without accessing the registry.
Requirements are Node 24, the normal public author packages, the Vite workbench
plugin, and cloudflared. If another tunnel is already running, pass its HTTPS
origin with --public-url; use --shell-url http://127.0.0.1:<port> when testing
an unreleased shell locally, or --no-open to print the host link. Each room's
development selection expires eight hours after launch and receives the
unreviewed iframe capability baseline. Treat the generated host link as private
because it contains the temporary development-manifest descriptor. Quick
Tunnels expose the local Vite server and source modules publicly, so share only
the room QR or controller invite, keep .env files, credentials, and private
assets outside Vite's served root, and stop the command and tunnel after the
test.
To use a Quick Tunnel you manage, run both commands and keep the port identical:
For a named tunnel, configure the public hostname's ingress service as
http://127.0.0.1:5173, run cloudflared tunnel run <name>, then pass the
hostname to --public-url. The CLI does not own or stop a tunnel supplied this
way. It opens the host link first; the room QR and controller invite are created
after the shell connects. In a headless environment, --no-open prints that
host link.
Local Reproduction
Build public package artifacts from the TPG repo first when testing unreleased workspace changes:
Then copy the example outside the repository and install dependencies from that detached location:
The copied .npmrc uses the public npm registry. No package credential is required.
Render a bundle:
The output should include:
Start the registry and browser approval shell from the TPG repository in two terminals:
Register the game from the detached external repository:
The shell runs at http://127.0.0.1:3000 and the registry at
http://127.0.0.1:4020. The default mode is upload; use --mode submit to place the exact bundle
in the review queue. After accessibility certification,
an approved version can be published from the portal or creator endpoint. The interactive login
opens the local shell, requires explicit approval, and saves a session scoped to
the exact loopback registry origin. Run npm exec -- tpgames whoami --registry-url http://127.0.0.1:4020 to verify it and npm exec -- tpgames logout --registry-url http://127.0.0.1:4020 to revoke it.

For unattended CI, create TPG_API_KEY from the creator portal. The key needs
games:read, games:write, games:submit, and games:publish for the full
publish path.
CI Contract
External repositories should keep CI aligned with the same package scripts:
Keep TPG_API_KEY in CI secrets. Demo creator tokens are only for local key
creation and preview verification; external CI should not store reviewer tokens.
Pull request runs should retain the generated dist/*.zip bundle as a
registry-bundle Actions artifact so reviewers can inspect the exact upload
candidate before creator registry secrets are enabled or a main publish runs.
Verification in This Repo
The repository locks this workflow with integration coverage in
apps/web-shell/src/platform/registry.integration.test.ts.
That test copies examples/external-game-repo into a detached temp directory,
installs packed public TPG packages, runs the example's own package scripts under
a restricted PATH, and verifies:
- registry upload, submit, and creator-owned publish succeed with a scoped API key
- discovery returns the published game
- the published manifest has a registry
entryBaseUrl host.htmland Vite-builthost.jsare served from/published-assets/:gameId/:version, without low-level runtime vendor files
Run the proof directly:
Remaining Boundaries
This workflow proves a separate repository can scaffold, build, and register a TPG game. It is not a separate moderation product or a self-hosted discovery system. Today, the registry remains the publication and asset-serving boundary for uploaded games.