Grove 0.12 & 0.13 — Replay Any Request, Bundle Your Environment, Catch Every Webhook" published
Grove now remembers what happens to your apps: replay failing requests, export your whole environment as a single file, catch and re-deliver webhooks, and turn any captured request into a Pest test.
0.12 — Replay, reproduce, and a splash of color in your SQL
Why: a failing request shouldn't be a one-time event
You know the loop. Something posts to your app, it 500s, and by the time you've added a dd() the moment is gone — you're re-triggering the flow by hand, re-filling the form, re-clicking the button. Because Grove sees every request, it can just keep them. So now it does.
How: expand it, replay it
The Requests timeline (and grove requests) now shows a stable id for every request, and you can expand any one to see its full headers and body. Then you replay it:
grove requests
# #142 14:03:21.882 500 POST 18ms myapp /checkout
grove replay 142
replayed → 500 in 16ms (see it in grove requests)
Replay re-sends the captured request through Grove's whole pipeline, so it hits your app exactly as the original did — same method, headers, body — while you edit the code. Fix, replay, fix, replay. No clicking through the UI again. It's a zero-config, framework-agnostic Telescope that works for Laravel, WordPress, a raw PHP file, or a Node container all the same.
Why: "works on my machine" should be a file you can hand someone
A grove.toml already captured your PHP/Node versions and services. But a teammate cloning your repo still needed your data to see what you see. So we made the whole environment portable.
How: bundle it, ship it
grove bundle export
wrote myapp.grovebundle — share it; restore with grove bundle import <file>
That single file contains the grove.toml, the .env, and the database — a SQLite file copied in, or a MySQL/Postgres dump taken through Grove's bundled tools. On the other end:
grove bundle import myapp.grovebundle
unpacks → brings the environment up → loads the database
restored to ./myapp — live at https://myapp.test
Reproducible dev environments, no Docker required. It's the fastest onboarding story we know: hand someone a file, they run one command, they're looking at the same running app with the same data.
And a small joy: SQL that isn't a wall of gray
The built-in Database panel's query editor now highlights your SQL — keywords, strings, numbers, comments — with a tiny, dependency-free highlighter. Small thing. Makes writing a quick select feel a lot nicer.
0.13 — Grove catches webhooks (and writes your tests)
Why: everyone can catch mail; nobody catches webhooks
Herd popularized the local mail-catcher, and it's genuinely great. But the modern pain isn't just email — it's webhooks. Stripe, GitHub, Slack, all POSTing to endpoints you can't easily see or re-run locally. You end up on webhook.site, copying payloads by hand. Grove already owns your DNS, TLS, and tunnels — so it can be the catcher.
How: point, capture, re-deliver
Any request to /__grove/hooks/<bucket> on a site is captured and answered 200 — it never touches your app. Expose it publicly with the tunnel you already have:
grove share myapp
https://calm-otter-1234.grove.li → myapp.test
Point Stripe at https://calm-otter-1234.grove.li/__grove/hooks/stripe, trigger an event, and it lands in the Webhooks panel (and grove hooks) with its full payload. Now the good part — re-deliver it to your real handler while you work:
grove hooks
#7 15:22:01 POST stripe /__grove/hooks/stripe
grove hooks replay 7 --to https://myapp.test/stripe/webhook
delivered → 200 in 24ms
Trigger the real event once in Stripe. Replay it into your handler a hundred times. No more "let me make another test payment."
Why: the best test is the request that just broke
You captured the exact request that failed. That is your test case — you shouldn't have to retype it.
How: copy it as a test
From any request or webhook, copy it as a curl command, a .http file, or a Pest feature test:
grove request 142 --as pest
<?php
it('POST /checkout responds', function () {
$response = $this->postJson('/checkout', [
'sku' => 'A1',
'qty' => 2,
]);
$response->assertStatus(500);
});
Grove reads the captured JSON body and renders it as a PHP array, sets the method and path, and asserts the status. Turn a bug you just saw into a regression test in one click — or grab the same request as curl to share in an issue. (The buttons are right there in the app, too.)
And yes — it's lime now
The app icon traded its orange for a bright lime. Small, cosmetic, and honestly kind of cheerful sitting in the menu bar. 🍋🟩
The throughline
Every one of these features exists because Grove sits at a layer bolted-on tools don't reach. It's the proxy, so it can remember and replay requests. It owns your config, secrets, and databases, so it can bundle a whole environment. It runs the tunnel, so it can be your webhook catcher. It captured the request, so it can write the test.
None of this gates the free core — serving sites, HTTPS, PHP/Node, databases, mail, tunnels, the request timeline, webhooks, replay, bundles, test-gen: all free and open source, forever.
# already installed? you're on your way here automatically
grove requests # what just happened
grove replay <id> # make it happen again
grove bundle export # hand it to a teammate
grove hooks # what the internet just sent you
Update the app (it does that itself), and go poke around. Expand a request and hit Replay. Point a webhook at Grove. Copy something as a Pest test. It's the same local dev environment you had yesterday — it just remembers a lot more now. 🌱🌳
Grove is free and open source. Try it at elyracode.com/grove.