Skip to main content
ubx is a compiler. Your .iac files go through a multi-stage pipeline before Pulumi ever runs.

Pipeline Stages

Stage 1 — Lexer

Tokenizes the .iac source into keywords, identifiers, strings, numbers, and punctuation. Recognises ubx-specific tokens: ~ (pending sigil), block keywords (unit, component, deploy, sync, input, output, local, data, extend, import, moved, policy, remote, interface, workspace, test, provider).

Stage 2 — Parser

Builds a typed Abstract Syntax Tree (AST). Each block type has its own AST node:
  • UnitBlockunit "type" "name" { ... }
  • OutputBlockoutput "name" { value = expr }
  • DataBlockdata "type" "name" { ... }
  • PolicyBlockpolicy "name" { rule { ... } }
  • etc.
Expressions are typed as StringLiteral, NumberLiteral, BoolLiteral, ObjectExpr, ListExpr, PendingRef, ResolvedRef, Interpolation, FunctionCall, or ConditionalExpr.

Stage 3 — Merger

Applies extend block overrides for the target environment (--env flag). Merges override attributes into base blocks. Extend blocks for environments other than the target are discarded.

Stage 4a — Schema Validation

Checks all unit block attributes against the provider schema registry:
  • Required attributes must be present
  • Unknown attributes produce warnings
  • Type mismatches produce errors

Stage 4b — Policy Evaluation

Evaluates all policy block rules against matching unit blocks. Violations produce errors (severity = "error") or warnings (severity = "warn").

Stage 4c — Type Checker

  • Resolves all symbol references (input.name, local.name, ~unit.type.name.attr)
  • Classifies every expression as Resolved<T> or Pending<T>
  • Detects circular dependencies
  • Validates depends_on references
  • Validates data block references
  • Validates cross-stack ~@name.output references

Stage 5 — Code Generator

Emits valid Pulumi TypeScript:
  • unit blocks → new Provider.ResourceName("name", { ... })
  • input blocks → new pulumi.Config().get("name") ?? default
  • output blocks → export const name = value
  • data blocks → pulumi.output(provider.getResource({ ... }))
  • Pending<T> refs → .apply() chains or pulumi.all([]).apply()
  • secret() calls → helper functions + pulumi.secret()

Generated File Structure

.ubx/
  index.ts       # generated Pulumi TypeScript (do not edit)
  Pulumi.yaml    # Pulumi project metadata
  package.json   # npm dependencies (provider packages)
  node_modules/  # installed packages
  state/         # local state (if using file backend)

Viewing Generated TypeScript

ubx apply --dry-run
cat .ubx/index.ts
Or validate with TypeScript compiler:
ubx validate --compile