Composable atoms for spinning up real git repos in any state. For tests, demos, and tool development.


πŸš€ What is git-scenarios?

Testing a git tool (TUI, CLI, IDE plugin, anything that reads or modifies repo state) usually means hand-writing the same git init plus writeFile plus commit setup in every test. Or worse, checking real .git directories into the test tree.

@gfargo/git-scenarios replaces both. It hands you back a real temp git repository in whatever state you want, deterministically, in one line. No mocks. No Docker. Real git on real disk. Then rm -rf when you’re done.

The package was extracted from Coco, where it grew up as the internal test layer covering merge conflicts, recursive submodule navigation, bisect flows, and everything in between. After multiple months of consumption inside a real tool, it earned its way out as a standalone npm package.

✨ Key Capabilities

πŸ“š Curated scenario registry

A growing set of named, contract-asserted scenarios covering the shapes you actually test against: feature branches ahead of main, mid-merge with conflicts, mid-bisect, dirty worktrees, multi-contributor history, submodules with their own commit log, multi-remote fork topologies, and more. One call to spinUpScenario('name') returns a real repo in that state.

🧱 Composable atom layer

When the registry doesn’t cover your case, compose your own from atoms: chain, addCommit, startMerge, addSubmodule, withAuthor, insideSubmodule, onBranch, and a few dozen others. Every atom returns a Step, so custom helpers compose alongside the built-ins with no plugin API or inheritance.

⚑ Tool-agnostic CLI

The git-scenarios binary ships with a --run flag that materializes a scenario and spawns any shell command against it. lazygit, your IDE, your own dev-mode CLI, anything. The fastest possible manual-testing loop for a git tool.

πŸ§ͺ Jest adapter

The @gfargo/git-scenarios/jest subpath export gives you zero-boilerplate scenario setup. describeWithScenario(name, fn) handles spin-up and cleanup for a block; describeEachScenario([...names], fn) runs the same assertions across several scenarios at once. Extra steps and a custom timeout slot in through an options argument.

The adapter owns the lifecycle: describeWithScenario wraps Jest’s describe with a beforeAll that spins the scenario up and an afterAll that tears it down, so the temp repo is created once for the block and removed when the block finishes. Inside your it() tests you reach the live repo through the getRepo() accessor. describeEachScenario goes one further: each scenario gets its own describe block with independent setup and teardown, so one scenario’s cleanup never touches another’s.

🎯 TypeScript-first

Every public API ships with full type declarations and source maps. The atom signature is uniform ((repo: TempGitRepo) => Promise<void>), so your custom helpers feel identical to the built-ins. defineScenario validates at module load time, catching typos before tests run.

πŸ” Deterministic by design

Same setup, byte-identical repo every time. Pinned identities, seeded content generators, fixed-date helpers. Tests built on top stay deterministic; flake-hunting moves elsewhere.

🎯 Who It’s For

  • Tool authors and DevX engineers building anything that reads or modifies git state (TUIs, IDE plugins, linters, formatters, CI helpers)
  • Test-infrastructure folks tired of the 80-line setup boilerplate in every test that needs a non-trivial repo shape
  • Solo OSS maintainers who need a fast manual-testing loop and don’t want to hand-roll fixture scripts
  • TypeScript developers who want fully typed atoms and scenarios, not generic any-returning helpers

⚑ Quick Start

npm install --save-dev @gfargo/git-scenarios simple-git

The package ships both ESM and CommonJS, so import and require both work out of the box. Spin up a scenario in a test:

import { spinUpScenario } from '@gfargo/git-scenarios'

const repo = await spinUpScenario('mid-merge-conflict')
// repo is mid-merge with src/widget.ts conflicted, MERGE_HEAD set
// ... run your tool against repo ...
await repo.cleanup()

Or drive any tool against a known state from the command line:

npx git-scenarios list
npx git-scenarios describe submodule-with-history
npx git-scenarios create mid-merge-conflict --run "lazygit"
npx git-scenarios create feature-pr-ready --run "code -n"

🧹 Cleaning up

Every scenario materializes a real temp directory, so something has to remove it. Who does that depends on how you spun it up:

  • Jest adapter (describeWithScenario / describeEachScenario): cleanup is automatic. The adapter tears the repo down in an afterAll when the block finishes, with each block getting its own independent teardown. You write zero cleanup code.
  • spinUpScenario, fromScenario, or raw createTempGitRepo: you own teardown. Call the returned repo.cleanup() in an afterAll / afterEach, or wrap the body in try/finally so it runs even when an assertion throws. cleanup() is idempotent, so calling it twice is safe.
  • CLI: the temp dir persists by default so you can inspect it after the launched tool exits (the command prints the path and an rm -rf hint). Pass --ephemeral to auto-clean on exit instead.

The rule of thumb: the Jest adapter is the only path that cleans up for you. Anywhere else, a cleanup() call (or --ephemeral) is what keeps /tmp from filling up over a long test run.

🌟 Why it exists

Everyone who has tested a git tool has written their own version of this. simple-git is the right library to call git from JavaScript, but it isn’t a fixture builder. isomorphic-git and nodegit are alternate git implementations with different goals. A few small fixture packages exist on npm but most are stale or scoped to one use case.

@gfargo/git-scenarios is the combination I couldn’t find: a curated scenario registry plus a composable atom layer plus a tool-agnostic CLI plus full TypeScript types, with the boundary discipline validated by multi-month consumption inside Coco before extraction.


If you’re testing a git tool of your own, the registry is the fastest way to get a baseline; the atoms are how you cover the edge cases your tool actually hits. PRs welcome for scenarios that turned out to be useful in your project.

Like what you saw?

There's more where that came from.

Browse all projects
Fin.

griffen.codes

made with πŸ’– and β˜•

Β© 2026all rights reservedupdated 50 seconds ago