forgeBuild.ts v1 specification#forgeBuild.ts is the project-authored contract for one Forge application.
It is typed TypeScript for editors, but Forge does not execute it. Forge
statically reads one data object from the exact Git commit and converts it into
its provider-neutral deployment plan.
Committing this file does not activate Deploy by itself. Create a Deploy
project from the repository's Sites page, accept the current Deploy Terms,
and enable that project. smolforge deploy check validates only the local
manifest; it does not check project creation, account eligibility, or enabled
state.
An optional repository-root .forge/config.json has a different purpose: it
statically lists projects, shared defaults, and reviewed provider-binding
proposals. It does not replace any project-root forgeBuild.ts, and importing
it never applies Cloudflare state or grants builds DNS authority. See
Repository configuration.
Every new Forge deployment requires this file at its configured project root.
A repository may contain multiple independently enabled projects; each has its
own root, releases, previews, provider target, active pointer, and rollback
history. A production-branch push schedules every enabled project
independently. Forge does not infer changed paths or create atomic release
groups.
forge.yml and forge.yaml are unsupported; a commit containing either is rejected even when
forgeBuild.ts is also present. An already-active deployment remains available
until it is replaced, rolled back, disabled, or suspended.
import { defineForge, importWrangler } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'notes',
build: {
command: 'npm run build',
workingDirectory: '.',
context: {
exclude: ['docs/ai/**'],
include: [
'docs/ai/program-summary.json',
'docs/ai/shared/**',
],
},
},
app: {
entrypoint: 'src/server.ts',
assets: {
directory: 'dist',
fallback: 'index.html',
},
limits: {
state: { maxScopes: 200 },
realtime: {
maxProjectConnections: 50,
maxEventBytes: '4KiB',
},
},
identity: {
scopes: ['user:basic', 'accounts:read'],
},
},
routes: [
{ pattern: '/api/*', to: 'app.http' },
{ pattern: '/*', to: 'app.assets' },
],
provider: {
cloudflare: {
wrangler: importWrangler('wrangler.toml', {
shareProductionResources: true,
}),
},
},
});importWrangler('wrangler.toml', { shareProductionResources: true }) is a static reference, not a JavaScript
import. Forge reads the referenced TOML file from the same exact Git commit,
validates its syntax, and retains its path in the normalized deployment
configuration. Repository code never runs while Forge evaluates either file.
The path must be a string literal naming a repository-relative .toml file.
Absolute paths, .., dynamic expressions, other file formats, and missing or
invalid TOML are rejected. This reference describes provider requirements; it
does not provision or migrate Cloudflare bindings. The required acknowledgement
is explicit because a connected preview inherits the existing Worker's
bindings and can read or mutate the same KV, D1, R2, Durable Object, and other
resources. Forge never treats that preview as an isolated test environment.
Install @smolai/forge as a development dependency for autocomplete and local
checking. Forge recognizes the import directly and never installs repository
dependencies merely to read configuration.
npm install --save-dev @smolai/forge
npx smolforge deploy checkversion is required and currently 1.name is an optional lowercase DNS label. It does not claim or change the
stable production hostname.build optionally declares one sandboxed command, repository-relative
working directory, and a bounded source context. Omitting it executes no
repository build command.app must declare an entrypoint, assets, or both.app.runtime: 'opennext-cloudflare' selects Forge's
OpenNext build preset. It forbids authored build, entrypoint, assets,
and application limits because Forge derives .open-next/worker.js and
.open-next/assets.app.entrypoint may be authored JavaScript/TypeScript source or an
already-bundled ESM framework output. Forge still passes that module graph
through its pinned compiler without repository plugins or compiler
configuration. The resulting module must be Workers-compatible and export a
default object with an asynchronous fetch method; a Node HTTP server entry
is not compatible.app.assets.directory is the static root; index and fallback are
optional artifact-relative files.app.limits may lower state and realtime ceilings but cannot increase
entitlement or budget.app.identity opts into Forge-hosted, app-scoped visitor identity. Its
non-empty, duplicate-free scopes array accepts user:basic,
accounts:read, and teams:read. It is also valid for assets-only apps.routes map exact or suffix-wildcard paths to app.http or app.assets.provider.cloudflare.wrangler may reference one checked-in Wrangler TOML
file using importWrangler() and must explicitly acknowledge shared
production resources.Frameworks may point app.entrypoint at their generated Worker module. For
SolidStart/Nitro, select the Cloudflare module preset so the output is a Worker
module instead of Nitro's default Node HTTP server:
// vite.config.ts (inside the existing plugins array)
nitro({ preset: 'cloudflare_module' })// forgeBuild.ts
export default defineForge({
version: 1,
build: { command: 'pnpm build' },
app: { entrypoint: '.output/server/index.mjs' },
routes: [{ pattern: '/*', to: 'app.http' }],
});Framework configuration APIs can change between releases; the stable Forge requirement is that the configured entrypoint is an ESM Worker module, not a process-listening Node server.
Large repositories may omit committed inputs that the project build does not read:
build: {
command: 'npm run build',
context: {
exclude: ['docs/ai/**'],
include: [
'docs/ai/program-summary.json',
'docs/ai/evaluations/current.json',
],
},
}The context starts with the complete frozen project source, applies every
exclude, then restores the listed include exceptions. Paths are
case-sensitive and relative to the configured project root. Each path is an
exact file, directory/** for a subtree, or ** for the whole
project. Arbitrary globs, absolute paths, .., backslashes, and control bytes
are rejected. A context accepts at most 256 total paths and 16 KiB of
normalized path data.
Forge always retains deployment controls needed to verify the build, including
the exact commit's forgeBuild.ts, referenced Wrangler configuration, project
package manifest, root package-manager files, .forge, and discovered
workspace dependencies. An include exception does not discover application
imports: projects must list every excluded file that their build reads. Compare
full and projected build outputs before adopting a broad exclusion.
Forge reads and statically evaluates only forgeBuild.ts from authoritative
Git objects before checkout. Repository code never supplies projection paths
at dispatch time. The canonical project root, context, forced control paths,
and policy version are hashed into the immutable exact-source checkpoint key,
so projected and full checkouts at the same commit cannot collide. The runner
still verifies the exact HEAD after materialization.
Custom contexts currently use an exact projected checkpoint or perform one bounded projected tree traversal. Direct-parent object-layer reuse and sibling checkpoint waiting remain disabled until those paths can prove identical projection identities and closure-aware deltas.
import { defineForge, importWrangler } from '@smolai/forge/config';
export default defineForge({
version: 1,
name: 'next-app',
app: { runtime: 'opennext-cloudflare' },
routes: [{ pattern: '/*', to: 'app.http' }],
provider: {
cloudflare: {
wrangler: importWrangler('wrangler.toml', {
shareProductionResources: true,
}),
},
},
});The project package must declare a registry-resolved
@opennextjs/cloudflare dependency, and npm, pnpm, or Bun must resolve it to an
exact version through the frozen root lockfile. Forge performs the frozen root
dependency install, verifies the installed exact version, invokes that
installed project-local adapter in the project root, and never fetches an
implicit latest. The trusted publisher receives only quiesced, validated
output; repository build code receives no provider credentials or secret
values.
Adapter and framework peer compatibility is a build eligibility contract, not
an install warning. The vetted OpenNext 1.19.11 target requires Next
>=15.5.18 <16 || >=16.2.6 <17; incompatible projects fail before
installation or publication.
The Wrangler contract must use .open-next/worker.js,
.open-next/assets, the ASSETS binding, and nodejs_compat. Separately,
Forge validates the supported R2 incremental-cache identity against the
administrator-approved inherited production binding. Unsupported output,
unsafe files, overflow, compressed Worker size, startup validation, and
binding mismatch fail before publication. See limits and
failure diagnostics.
The native Forge runtime keeps deny-all egress and repository configuration
cannot widen it. Connected OpenNext Workers retain only their
administrator-approved provider runtime policy and inherited bindings; those
credentials and secret values never enter repository builds. /.forge is
reserved for platform endpoints, including the Forge Identity broker. Hosted
apps never receive Forge control-plane cookies
or unrestricted account and team directories.
Current v1 supports one application with built-in managed state and realtime;
it does not yet accept an author-defined resources map.
The forward contract will let repositories declare multiple named logical resources by required semantics, such as SQLite-compatible data, key/value data, object storage, or keyed actor state. Provider product names and resource IDs belong in the compiled plan, not those portable declarations. Deterministic preview seeds will target the same logical names.
The existing provider.cloudflare section is the deliberate exception. It is
a namespaced compatibility escape hatch for applications that depend on
Cloudflare-specific behavior and is excluded from Forge's portability
guarantee. Forge preserves those requirements and reports them as non-portable
rather than silently weakening or translating them.
Forge accepts:
@smolai/forge/config, containing unaliased
defineForge and optionally unaliased importWrangler;export default defineForge({...});null;as const, satisfies, and type arguments.Forge rejects:
new;defineForge and a literal
importWrangler('path/to/wrangler.toml', { shareProductionResources: true })
reference;await, promises, environment access, filesystem,
network, clocks, and randomness;tsconfig, plugins, and path aliases.This restriction makes configuration deterministic and cheap to validate without creating another untrusted-code runtime.
OpenNext and connected-provider limits are distinct from the generic application bundle limit. See Deploy limits.
Paths must be normalized, repository-relative paths and cannot traverse outside the checkout. Duplicate object keys and ambiguous routes are rejected.
Parser and unsupported-syntax diagnostics include a stable error code and source position. Semantic diagnostics include the stable code and affected field:
Invalid forgeBuild.ts [FORGE_CONFIG_UNSUPPORTED_SYNTAX]:
18:15: spreads are not supported in Forge configurationRepository configuration is authoritative by default. A repository administrator may explicitly override:
Forge validates the source object first, applies overrides, validates the effective configuration again, and records both forms on the deployment. Overrides are visible in the Deploy UI and build logs.
Saving an override offers Save only and Save and deploy current branch head. Saving never silently deploys.
Before scheduling work, Forge reads forgeBuild.ts from the exact pushed
commit and records:
The runner receives that frozen effective configuration and verifies the checked-out file still matches the recorded Git blob. Repository build code cannot rewrite its own deployment policy.
Activation and rollback move the active immutable deployment pointer. They do not rewind production state. Preview state remains isolated by deployment.
| Code | Meaning |
|---|---|
FORGE_CONFIG_MISSING |
forgeBuild.ts is absent. |
FORGE_CONFIG_YAML_UNSUPPORTED |
The commit contains forge.yml or forge.yaml. |
FORGE_CONFIG_TOO_LARGE |
Source or normalized data exceeds its bound. |
FORGE_CONFIG_PARSE_ERROR |
TypeScript syntax is invalid. |
FORGE_CONFIG_INVALID_MODULE |
Import/export structure is not allowed. |
FORGE_CONFIG_UNSUPPORTED_SYNTAX |
The file uses executable or dynamic syntax. |
FORGE_CONFIG_DUPLICATE_KEY |
An object contains a duplicate key. |
FORGE_CONFIG_INVALID |
The reduced object violates the v1 contract. |
FORGE_CONFIG_CHANGED |
Checkout differs from the frozen exact-SHA configuration. |
Invalid configuration creates a visible failed deployment and never starts a repository build.