Upgrading
Read this page before every minor bump. Breaking changes land only on minor releases, and every one has a section in MIGRATING.md on GitHub, which stays the full, canonical history. This page summarizes the latest two.
0.34.x → 0.35.0
Section titled “0.34.x → 0.35.0”0.35.0 changes no Stack code: the Dart API, synth output and every provider pin stay as they were. It changes how the terradart command behaves in CI and scripts.
- Raise every TerraDart constraint to
^0.35.0by hand, then rundart pub upgrade, anddart pub global activate terradart_clifor the new command. - Pass
--auto-approvewhere nobody can answer. Without a terminal — or with--no-input,CI, or in an AI agent’s shell —terradart applyandterradart destroystop beforeinitwith exit code 3 instead of starting the engine. A script that pipedyesinto them passes--auto-approveinstead. - Check exit codes against the new table. A failed engine step exits 12 (the engine’s own code is
error.engineExitCodeunder--json), a failed entry point 10, an unknown--env65. A script that tested$? -eq 1tests$? -ne 0, or readserror.codefrom--json. See JSON and exit codes. - Optional: install the agent skill.
terradart skill installwrites the TerraDart Agent Skill of the CLI’s release into the project;terradart skill status --checkkeeps it current in CI. See Let an AI agent do it.
0.33.x → 0.34.0
Section titled “0.33.x → 0.34.0”0.34.0 changes no Stack code: the Dart API, synth output and every provider pin stay as they were.
- Raise every TerraDart constraint to
^0.34.0by hand, then rundart pub upgrade, anddart pub global activate terradart_clifor the new command. - Optional: name a default environment.
runEnvironments(args, Env.values, build, defaultEnv: Env.dev)letsterradart planrun without--env;TERRADART_ENVoverrides it. See Environments. - Appwrite Stacks run on Terraform. The
terradartcommand now picks theterraformonPATHfor a Stack that usesterradart_appwrite, and stops beforeinitwhen there is none or OpenTofu is asked for. - A state another engine wrote now needs an answer. When
.terradart/engines.jsonhas no record (a fresh clone, or right after a migration),plan,applyanddestroyread the state first. If the engine the command picked by itself is not the one that wrote the state, they ask on a terminal. Without a terminal they stop with exit code 64; pass--engine, or setterradart: engine:inpubspec.yaml, in CI.
0.32.x → 0.33.0
Section titled “0.32.x → 0.33.0”0.33.0 adds the terradart command (terradart_cli) and changes no Stack code: the provider packages keep their Dart API, no provider pin moves, and synth output is unchanged.
-
Raise every TerraDart constraint to
^0.33.0by hand, then rundart pub upgrade:dependencies:terradart_core: ^0.33.0terradart_google: ^0.33.0# and ^0.33.0 for terradart_google_beta, terradart_aws,# terradart_cloudflare, terradart_appwrite or terradart_time -
Switch to the command.
dart pub global activate terradart_cli, thenterradart applyreplacesdart run bin/infra.dart,cd tf-out,terraform initandterraform apply, and writes the define file a client reads its outputs from. An existingbin/infra.dartkeeps working; callingrunStackorrunEnvironmentsfrom it lets the command select an environment with--env <name>. See The terradart command. -
terradart-migrateusers: runterradart migratewith the same flags.terradart-migratestill runs, prints that it is deprecated, and goes away in a later release. -
Maintainers only:
dart pub global activate terradart_codegennow installsterradart-codegen(terradart-codegen wrap ...), becauseterradartis the user command. Deactivate and reactivateterradart_codegenbefore activatingterradart_cli;dart run terradart_codegen:terradartis unchanged.
0.31.x → 0.32.0
Section titled “0.31.x → 0.32.0”0.32.0 makes every argument take what it means — an attribute getter, an enum member, a variable handle, a provider instance, the blocks a resource depends on — so most of the upgrade is deleting wrappers. It breaks the Dart API of every package, not your Terraform: no provider pin moves, and synthesized JSON changes only where a typed reference now emits the attribute the provider expects, an IAM adjunct now carries its parent’s project / location, or an explicit false lifecycle flag is now written.
Upgrade steps
Section titled “Upgrade steps”-
Raise every TerraDart constraint to
^0.32.0by hand, then rundart pub upgrade. The Dart SDK minimum stays 3.10:dependencies:terradart_core: ^0.32.0terradart_google: ^0.32.0# and ^0.32.0 for terradart_google_beta, terradart_aws,# terradart_cloudflare, terradart_appwrite or terradart_time -
Drop
import 'package:terradart_core/terradart_core.dart';where a file imports a provider barrel: every barrel re-exports it. -
Fix the compile errors with the upgrade guide in MIGRATING.md, which lists the groups in order of how many stacks they touch, each with a before / after table.
-
Read
terradart planbefore you apply. A few typed references emit a different attribute than the one a stack passed (the list);.ref.pinned('id')keeps the old one. -
terradart-migrateusers:dart pub global activate terradart_migrateinstalls 0.32.0, which writes the new API.
The common changes
Section titled “The common changes”| Before (0.31) | After (0.32) |
|---|---|
GooglePubsubTopic(localName: 'orders', ...) | GooglePubsubTopic('orders', ...) |
topic.nameRef, labels: .ref(other.labels) | topic.name, labels: other.labels |
routingMode: .literal(.regional) | routingMode: .regional |
addVariable('region', const TfVariable(type: 'string')), then TfArg.variable('region') | final region = variable<String>('region');, then location: region |
dependsOn: [ResourceDependency(api)], addData(...) | dependsOn: [api], add(...) |
provider: 'google.eu' | provider: eu, from final eu = addProvider(GoogleProvider(alias: 'eu')); |
setBackend(...), TfTimeouts(create: '30m') | super(backend: ...), TfTimeouts(create: Duration(minutes: 30)) |
Apis.enable(this, barrels: [Barrels.cloudRun]) | enableApis([.cloudRun]) |
member: .ref(sa.iamMember), member: .literal('user:[email protected]') | member: sa.principal, member: .user('[email protected]') |
name: .ref(api.nameRef), location: ... on an IAM member | service: api.ref |
password: .literal('...') on a sensitive argument | a variable or an expression; Sensitive<T> has no .literal |
ignoreChanges: ['target_size'] | ignoreChanges: .of(['target_size']) |
on StateError / on SensitiveLiteralError around synth() | on SynthException, or stack.validate() |
Together:
import 'package:terradart_google/iam.dart';import 'package:terradart_google/provider.dart';import 'package:terradart_google/pubsub.dart';
final class PublisherStack extends Stack { PublisherStack({required String projectId}) : super(providers: [GoogleProvider(project: projectId)]) { final topic = add(GooglePubsubTopic('orders', name: .literal('orders'))); final publisher = add( GoogleServiceAccount('publisher', accountId: .literal('orders-publisher')), ); add( GooglePubsubTopicIamMember( 'publish', topic: topic.ref, // was name: .ref(topic.nameRef) role: .literal('roles/pubsub.publisher'), member: publisher.principal, // was .ref(publisher.iamMember) ), ); addOutput('orders_topic_id', topic.id); }}New in 0.32.0 and not breaking: typed outputs in Flutter and web clients (addDartDefineOutput), and provider aliases in migrated child modules.
0.30.x → 0.31.0
Section titled “0.30.x → 0.31.0”0.31.0 reshapes the Dart API of every package for type safety, but not your Terraform: no provider pin moves, and synthesized JSON changes only where a typed reference now emits a different attribute. dart analyze lists every break, and code completion on the argument offers the replacement.
Upgrade steps
Section titled “Upgrade steps”-
Install Dart 3.10 or later and set
sdk: ^3.10.0in your stack’spubspec.yaml. Dot shorthands need it. -
Raise every TerraDart constraint to
^0.31.0by hand — below 1.0 a caret never crosses a minor — then rundart pub upgrade. The packages release in lockstep:environment:sdk: ^3.10.0dependencies:terradart_core: ^0.31.0terradart_google: ^0.31.0# and ^0.31.0 for terradart_google_beta, terradart_aws,# terradart_cloudflare, terradart_appwrite or terradart_time -
Fix the compile errors, group by group (each section of MIGRATING.md has a before / after table): sealed arguments, typed references, outputs and constants, typed nested helpers, type names.
-
Read
terradart planbefore you apply. Typed references emit the attribute the argument expects — Googlenetwork/subnetworkemitidwhere many stacks passedself_link. Pin the old one with.ref.pinned('self_link')to keep the old value exactly. -
terradart-migrateusers:dart pub global activate terradart_migrateinstalls 0.31.0, which writes the new API.
Sealed arguments and dot shorthands
Section titled “Sealed arguments and dot shorthands”An argument that takes exactly one (or at most one) of several inputs is one sealed argument, named by concept like a protobuf oneof. Each member is a factory constructor, picked with a Dart 3.10 dot shorthand. Leaving it out, or setting two, no longer compiles:
final role = add(AwsIamRole( 'fn', name: .namePrefix(.literal('app-')), // was name / namePrefix assumeRolePolicy: .literal('{}'),));add(AwsLambdaFunction( 'fn', functionName: .literal('hello'), role: role.ref, code: .filename(.literal('bootstrap.zip')), // was filenameOrImageUriOrS3Bucket));The same shorthand works for every TfArg and enum: .literal('orders'), sa.principal, .postgres15.
Typed references
Section titled “Typed references”An argument that names another resource takes RefTo<R>, so passing the wrong kind of resource does not compile. Take it from the target’s ref getter; the argument picks the attribute it emits:
| Before (0.30) | After |
|---|---|
network: TfArg.ref(vpc.selfLink) | network: vpc.ref |
role: TfArg.ref(role.arn) | role: role.ref |
zoneId: TfArg.ref(zone.id) | zoneId: zone.ref |
network: TfArg.literal('default') | network: .literal('default') |
final vpc = add(GoogleComputeNetwork('vpc', name: .literal('app')));add(GoogleComputeSubnetwork( 'app', name: .literal('app'), ipCidrRange: .literal('10.0.0.0/24'), network: vpc.ref, // emits id; vpc.ref.pinned('self_link') keeps the 0.30 value));Every input also has a <name>Ref getter (topic.nameRef) for wiring one resource’s argument into another, or into a constant.
Outputs and constants
Section titled “Outputs and constants”addExport is gone. addOutput declares a Terraform output, addConstant a Dart constant, and the generated file is configured once with appExports::
import 'package:terradart_google/provider.dart';import 'package:terradart_google/pubsub.dart';
final class OrdersStack extends Stack { OrdersStack({required String projectId}) : super( providers: [GoogleProvider(project: projectId)], appExports: AppExports('lib/generated/orders_stack.app.dart'), ) { final topic = add(GooglePubsubTopic('orders', name: .literal('orders-prod'))); addConstant('ordersTopicName', .ref(topic.name)); addOutput('orders_topic_id', topic.id); }}The same file now also holds OrdersStackOutputs, a typed reader of the outputs, and outputEnvironment() hands them to a service as its environment. See How TerraDart works — the app boundary.
Typed nested helpers and type names
Section titled “Typed nested helpers and type names”Google blocks take helper classes derived from the provider schema (no Google TfArg<Map> block is left), and derived types are named <ResourceStem><Block> without repeated words — CloudRunV2ServiceContainers, not CloudRunV2ServiceServiceContainer. Arguments and synth output do not change; dart analyze lists the old names, and completion offers the new ones.
Older releases
Section titled “Older releases”Every earlier breaking change — TfArg.expression and provider aliases in 0.28.0, sealed exactly-one slots in 0.12.12, typed enums in 0.12.10, and more — is documented in MIGRATING.md.
Next steps
Section titled “Next steps”- Status & versioning — alpha expectations and change policy
- Examples — every quickstart is on the current API