I’ve been learning Go recently and wanted to try a Terraform-like workflow for API testing: write scenarios as code, compile them into a static plan, then execute that plan.
So I built a small project. You define workflows in TypeScript, run budment plan to inspect the execution hierarchy and hooks without making network requests, then execute the compiled AST natively in Go.
“Scenario-as-code” isn’t new, but I’m interested in whether a plan-first approach can make test suites easier to inspect, audit, and maintain.
I’m still getting comfortable with Go, so I’d appreciate any feedback on the architecture, concurrency, or anything that looks questionable.
Repo: https://github.com/budment/budment
Docs: https://budment.com
Top comments (4)
Compiling to a static plan and executing a tree instead of replaying scripts is what makes Terraform tolerable, and applying it to API flows is a good use of the idea — being able to read the hierarchy and hooks before any network call is exactly what a lot of request collections can't do.
The question I'd have is the plan-then-apply half you don't get for free: what happens when the plan is stale. If step four depends on an id from step two, either you carry state forward, which turns the static plan into a template, or you re-plan, which means the thing you reviewed isn't quite the thing that ran. Do you persist plans as artifacts so CI can diff one run against the next, and how do you clean up resources a half-finished apply leaves behind? Those two are what made me give up on a similar design once.
Fair points. Here is how it works under the hood:
1. Dynamic State: You're right, it does turn into a template. In the plan phase,
${get('id')}compiles into a{{id}}token and gets split into static and variable chunks. At runtime, Go just loops over those chunks and pulls the value from the worker's scope map into astrings.Builder(implemented here if you're curious). So there is no re-planning or JS execution needed to resolve variables.2. Plan Artifacts: I don't persist plans right now because evaluating the TS scenario in-memory only takes milliseconds. That said, saving a plan artifact so CI can diff changes between runs makes a lot of sense, and I'll look into adding it.
3. Cleanup: Haven't solved this yet. A failed or interrupted run will leave orphaned data behind right now. Adding a guaranteed teardown step (even on cancellation) is something I need to add.
Thanks for pointing these out — really useful feedback.
Hey @raknaos, if you have a minute, I'd love to get your thoughts on this!
Your feedback was really helpful. I’ve pushed some updates in PR #10 and clarified the plan/runtime boundary a bit more documented here.
1. URL Skeleton
The route structure stays static in the plan, while
{{}}tokens are resolved dynamically at runtime:So
budment plan --detailcan show the complete skeleton before any network request:At runtime, the placeholders are resolved as the scope changes.
2. Teardown
I also added a dedicated teardown phase to handle cleanup.
The idea was to get some of the benefits of a Terraform-like plan — predictable execution and lower RAM/GC overhead for routine tasks — while still keeping the DX flexible for dynamic logic. That's what led me to this hybrid approach.
I'm still exploring the trade-offs here. Does this approach make more sense, or do you see any obvious blind spots?
Some comments may only be visible to logged-in visitors. Sign in to view all comments.