Environments
env.<name>, environments/<name>.config.json, manifestOverrides, --env.
An environment is a named, opt-in build profile — different builds of the
same app (staging/production) that need
different build-time variables (API keys, service URLs), a patched manifest, or a different set of bundled
files, without a second copy of the source tree. With no environment active, a build behaves exactly as it did
before this feature existed — this is entirely additive.
Selecting an environment
--env <name> selects environments/<name>.config.json
(a dedicated folder, sibling to src/). A FLASH_THEATER_ENV
environment variable is honored as a fallback whenever --env isn't passed —
specifically so it flows through npm run build:roku's compile && zip
chain with zero package.json changes:
FLASH_THEATER_ENV=staging npm run build:roku
# equivalently, passed directly to the compiler itself:
flash-theater compile --env staging
An active environment writes to out-<env>/ instead of
out/. flash-theater zip --env <name>
(see Getting started) then names its
output dist/<app>-<env>-<version>.zip instead of
dist/<app>.zip — different environments' builds never clobber each
other or the plain build.
environments/<name>.config.json
All four keys are optional, but — unlike the base flash-theater.config.json —
an unrecognized top-level key is a hard error: designResolution/srcDir/outDir
stay base-config-only, never per-environment.
{
"variables": {
"apiBaseUrl": { "value": "https://staging.api.example.com" },
"apiKey": { "fromEnv": "STAGING_API_KEY" }
},
"manifestOverrides": {
"title": "My App (Staging)"
},
"exclude": ["images/production-only/**"],
"include": ["images/staging-only/**"]
}variables— each name resolves to a plain string: a literal (value) or a build-time bash/CI variable (fromEnv, failing the build if unset). Readable from DSL code asenv.<name>below.manifestOverrides— a partial patch on top of the basesrc/manifest: each key is upserted when writingout-<env>/manifest.src/manifestitself is never touched.exclude— glob patterns added on top of the base config's ownexclude, for this environment only.include— glob patterns exempted fromexclude(base's or this environment's) for this environment only — lets a base config permanently exclude e.g.images/staging-only/**from the plain build, while each environment's ownincludepulls its own subtree back in.
env.<name> — reading a declared variable
Resolved the same structural way theme.a.b is — not a schemaless, free-form
scan like store/router — because an
environment's whole variable set is known from its own config file at compile time. Using
env.* with no active environment, or referencing an undeclared name, is an
ordinary compile error, not a runtime bug:
derived apiBaseUrlLabel: string = "API: " + env.apiBaseUrlLocal overrides
An optional, git-ignored environments/<name>.local.config.json, same
shape, sitting beside the committed file — picked up automatically whenever that environment is selected, no
extra flag. Lets a developer point apiBaseUrl at localhost
or supply a personal sandbox key without touching the committed config:
{
"variables": {
"apiBaseUrl": { "value": "http://localhost:3000" }
}
} variables/manifestOverrides are merged
key-by-key with the local file winning on any conflict; exclude/include
are concatenated, committed patterns first.
Reference implementation — apps/environments-demo
Every mechanism on this page has a router-mounted, scaled
chapter in apps/environments-demo — 2 chapters
(/variable-reads and
/overrides-and-manifest), reachable with REWIND/FAST-FORWARD
once compiled and sideloaded. Unlike every other chapter app, this one has no meaningful "plain"
(no active environment) build at all — every chapter reads env.*,
which is a hard compile error with no active environment — so its own
package.json defaults FLASH_THEATER_ENV
to dev via a small scripts/with-env.mjs
wrapper whenever the caller hasn't already set one. Chapter 1 reads three declared variables (a
URL-shaped apiBaseUrl, a feature-flag-shaped
enableBetaFeatures, and a fromEnv-sourced
buildLabel) and narrates the closed-set validation errors that can't be
demonstrated live (a build that hit either one would never have produced a running app). Chapter 2
proves manifestOverrides actually patched the shipped manifest by
reading the title back at runtime with roAppInfo.GetTitle(), proves
include/exclude actually changed which
files were packaged by reading two environment-only placeholder files back with
ReadAsciiFile, and narrates local overrides
(environments/dev.local.config.json.example is this app's own
copy-paste starting point). See findings/environments-demo-app.md
for what each chapter covers and findings/demo-app-conventions.md
for the app-structure convention it follows.
⚠️ Not (yet) supported
- ○ Non-string variable values (numbers, booleans, nested groups) — every
env.<name>value is a plain string. - ○ Per-environment
designResolution/srcDir/outDir— those stay base-config-only, app-wide settings. - ○
env.*in the site's in-browserThrPlayground— there is no realprocess.env/environment-file concept to demo against client-side.
Exact grammar: GRAMMAR.md. Full feature status: docs/features.md.