Testable guide snippets
July 15, 2026 ยท View on GitHub
Every code example in docs/guides/*.md lives in exactly one place: a runnable
TypeScript file in examples/guides/. Guides never contain code, only markers.
The docs build injects the code at build time, and the test suite typechecks and
executes it. If an SDK change breaks a guide example, CI fails.
How it works
-
A snippet file (for example
send-a-payment.ts) is a real script that imports@stellar/stellar-sdkand runs top to bottom.// #region nameand// #endregioncomments mark the parts that appear in the guide. Everything outside a region (account funding, assertions) runs in tests but never appears in docs. -
The guide references a region with an HTML comment marker, and nothing else:
<!-- snippet: send-a-payment.ts#build --> -
At docs build time,
snippetsIntegration(inconfig/snippets.ts, registered inastro.config.mjs) mirrorsdocs/into the gitignored.docs-build/directory with each marker replaced by a fenced code block. The site's content collection (src/content.config.ts) loads from.docs-build/, so Astro's cache invalidation covers snippet edits in bothastro devand builds.scripts/build-llms.ts(llms.txt) andscripts/build-md-siblings.ts(raw.mdsiblings) expand the same markers when they readdocs/. -
Three verification tiers, split by what they depend on:
- Hermetic PR gate
pnpm docs:snippets:check(runs inpnpm test,pnpm docs, and the tests and docs-build workflows on every PR): every marker resolves to a real file and region, no inline code block follows a marker, malformed markers (typos, indented markers) are hard errors, and snippets typecheck againstsrc/with the same strictness as the SDK build. - Local-network execution PR gate
pnpm test:guides:local(runs inguides_pr.ymlon every PR against a stellar/quickstart service container):test/guides/snippets.test.tsauto-discovers every file inexamples/guides/and executes it. Snippets keep their real testnet URLs and passphrase;config/guides-local-setup.tsredirects them to the local network at the transport layer. To run locally, start quickstart first:docker run --rm -p 8000:8000 -e NETWORK=local -e ENABLE_SOROBAN_RPC=true stellar/quickstart:testing - Real-testnet execution
pnpm test:guides(run bypreversionat release time, or manually): the same tests with no redirection. This tier catches drift a local network cannot: Horizon deployments, friendbot API changes, protocol upgrades. It is not a PR gate because testnet outages would fail PRs for reasons unrelated to the code. Since it only runs at release, bumpguides_pr.yml's quickstart image pin periodically so the local network tracks what testnet actually runs.
- Hermetic PR gate
Adding a guide with tested code
-
Write the snippet file in
examples/guides/. Name it after the topic, not the guide's number (issue-an-asset.ts, not03-issue-an-asset.ts). Structure it as one script in the same order the guide tells the story. Wrap each displayed block in a region:// #region create-keypair const keypair = Keypair.random(); // #endregion create-keypairRules that make this work:
- Import
@stellar/stellar-sdkby its package name. The test and typecheck configs resolve it tosrc/, so examples read like user code but run against the current source. - Put setup the guide assumes (funded accounts, existing trustlines) before the first region, outside any region.
- Add assertions outside regions so execution proves the flow worked, not just that it did not crash.
- A region name can appear multiple times. The parts are joined with a blank line in the rendered block, or seamlessly when the next part continues an indented expression (a builder-chain fragment). Use this to show a fragment while the full chain still compiles.
- Regions can overlap: a line belongs to every region open at that point.
#endregionmust be named (// #endregion build) whenever more than one region is open. - For a "Put it together" recap block, do not write a second program. Open a
fullregion spanning the display-worthy code and let the step regions overlap inside it โ the recap is then a view of the same lines, and nothing is duplicated. Lines only the steps show (bare annotation expressions) sit outsidefull; lines only the recap shows (console.logs) sit outside the step regions. - Prefer regions at module scope. A region carved from inside a block (a try/catch body, a function) renders dedented to the left margin. A region mixing column-0 lines with indented ones keeps its indentation as authored.
- The file must be a valid ES module. Top level await is fine.
- Import
-
Write the guide in
docs/guides/. Where each example goes, put a marker instead of a code block:<!-- snippet: issue-an-asset.ts#create-keypair -->Do not put a code fence after the marker.
check-snippetsrejects it, because an inline copy would go stale silently. -
Verify:
pnpm docs:snippets:checkfor fast validation and typecheck,pnpm test:guides:localto execute against a local quickstart container (the day-to-day loop, a few seconds per run), andpnpm docs:devto see the rendered guide. In dev, editing a snippet hot reloads the pages that embed it. No Docker? Run the check locally and let theguides_pr.ymlworkflow execute the snippets on your PR. There is no test wiring step:test/guides/snippets.test.tsauto-discovers every snippet file, so a snippet that typechecks but never runs cannot happen.
Intentionally unverified code
Code that must not compile or run (the migration guide's before examples, pseudocode) stays as a plain fenced block with no marker. Only marked blocks are tested. Prefer markers for anything a reader might copy.
Gotchas
- Snippet file names are shared across guides in a flat directory. Keep them topic based and unique.
- Two blocks that redeclare the same variables cannot live in one module scope.
A nested
{ }block usually solves it (see the rebuild-keypair region inconnect-and-fund.ts); a second snippet file is the last resort, only for genuinely incompatible alternative programs. - Snippets that need contract infrastructure can deploy their own contract (from a checked-in wasm fixture) in hidden setup, the same way the payment snippet funds its own accounts. The quickstart tier runs Soroban RPC, so this works on every PR.
- Never reassign
globalThis.fetchor mutateNetworksinside a snippet. The local-network tier redirects transport by patching exactly those, and all snippets share one process; a snippet that touches them breaks every snippet after it. - If the site sidebar ever loses its groups, check the
autogeneratedirectories inastro.config.mjs. They must be prefixed.docs-build/, matching the collection root.
Backlog
Deferred hardening from the design reviews, each with the trigger that makes it due. Do the item when its trigger arrives, not before.
| Item | Trigger |
|---|---|
One-time GitHub setup: add guides-local to the protect-main ruleset as a required check | When this system first lands on the remote |
Untested-fence opt-out annotation plus a tested/untested count in check-snippets (makes silent partial conversions visible) | Before the first partial guide conversion (invoke-a-contract is the likely first) |
Fence metadata passthrough in markers (for title= and del=/ins= annotations) | Before converting the before/after guides (contract-auth, protocol-27, migration) |
| Checked-in wasm fixture plus deploy-in-hidden-setup pattern | Before converting invoke-a-contract or contract-auth |
| Child-process snippet execution (isolates shared-process state; dynamic import caches failures, so vitest retry is a no-op) | Before converting the streaming or error-handling guides |
Reviewer preview: a command that prints a guide's expanded markdown, or a CI artifact of the .docs-build/ diff | Strongly recommended before conversions start |
| Sidebar canary: post-build assertion that the Guides and Reference groups render | Any time; value grows with guide count |