This takes the ../minimal project multi-environment. It adds a
deploy override supplying its own build step and emitter, generated TypeScript
in place of a Luau table, and a GitHub Actions workflow that commits the
regenerated ids back to main. Read ../minimal first if the
deploy stages are new to you.
| Path | Role |
|---|---|
bedrock.config.ts |
Resources, two environments, codegen.output. |
.bedrock/deploy.ts |
Override the CLI spawns instead of its built-in deploy. |
.bedrock/build/build-place.ts |
Compiles the sources and builds the place artifact. |
.bedrock/codegen/emit.ts |
Deploy state in, source files out. Pure function. |
src/shared/assets/resources.ts |
Generated. Committed, and rewritten by CI. |
src/server/main.server.ts |
Resolves ids from game.GameId at runtime. |
src/dev/dev-only.server.ts |
Mounted by the development Rojo project only. |
.github/workflows/deploy.yaml |
Copy to your repository root to use. |
pnpm --filter @bedrock-rbx/example-ci-codegen buildbuild-place.ts runs the same command during a
deploy, picking its Rojo project from the environment being deployed. roblox-ts
reads tsconfig.roblox.json rather than
tsconfig.json, which is the Node-side program for .bedrock/: rbxtsc requires
its typeRoots to resolve against its own config's directory.
A config file cannot hold functions, so anything Bedrock has to call —
building a place, generating source, reporting progress — is passed to
deploy() in code. That is what .bedrock/deploy.ts is,
custom emitter included. When the file exists,
bedrock deploy --env X spawns it with --env X in argv and the credentials in
the environment; the same command still works locally and in CI.
For a build step alone, drop a .bedrock/build.ts and the CLI injects it;
deploy.ts is for when you also need a custom emitter.
Codegen rewrites src/shared/assets during the deploy, and those files are the
game's source of truth for asset ids. They have to land on the branch, or the
next build regenerates them from scratch and the diff never settles. The
deploy action runs bedrock deploy --env production, mints a short-lived
installation token from your GitHub App, then commits only the changed files
under paths, retrying if the branch tip moved. Its commit message carries
[skip ci] so the push does not re-trigger the workflow.
The App token is there because the built-in GITHUB_TOKEN cannot push to a
protected main.
Set up the deploy bot
covers creating it; pass commit-token instead if you already have a
write-capable token. With neither, the commit-back is skipped and the deploy
still runs.
| Secret | What it is |
|---|---|
BEDROCK_API_KEY |
Open Cloud API key with universe, place, and monetization scopes for your universe. |
BEDROCK_GIST_TOKEN |
GitHub token with gist scope; Bedrock reads and writes deployed state through it. |
DEPLOY_APP_CLIENT_ID |
Client id of your deploy GitHub App. |
DEPLOY_APP_PRIVATE_KEY |
Full contents of that app's .pem private key. |
Every action is pinned by commit SHA, the Bedrock deploy action included: a tag
can be repointed, and this job hands the action an Open Cloud API key and a
GitHub App private key. The pin stops at the outer reference. The deploy
composite resolves its own commit-back step through the actions-v0.1.1 tag, so
pinning freezes which composite you get, not every step it runs.
development sets redacted: true: Bedrock pushes a placeholder name,
description, and price to Roblox for that environment and records the real
values as a $realDisplay sibling of each resource inside the state file. The
emitter reads them back with codegenViewOf and realValue, so generated
source carries the real values in both environments while only production shows
them on the storefront. Drop redacted and the emitter is unchanged, because
codegenViewOf returns the declared value when there is nothing to see through.
- Replace every placeholder id in
bedrock.config.ts, and thegistId. - Replace
assets/icons/vip-pass.pngwith a real 512x512 icon, or drop thepassesblock. - Add a key under
environments; the emitter generates oneGameIdmember per environment that has deployed at least once. - To generate something else, edit
emit.ts. It is a pure function of the deploy state, so a test over it is an input/output comparison.