Contributions are welcome. Follow these instructions to set up the development environment, run tests, and open pull requests.
- Nix with flakes enabled. Its dev shell pins Node.js, pnpm, Deno and dprint, and carries the sandbox that every command running dependency code goes through.
- On Linux, unprivileged user namespaces, which the sandbox needs. Ubuntu 24.04 blocks them by default; lift the block with
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0. - Optional: direnv, which enters the dev shell when you
cdinto the repository.
Clone the repository, enter the dev shell and install:
git clone https://github.com/systemfsoftware/xstate
cd xstate
nix develop # or: direnv allow
pnpm bootstrapDependency code never runs on your machine directly. Installs, builds, tests and git hooks run inside the sandbox, which reaches only the network hosts a command declares and writes only inside the project. pnpm bootstrap installs offline from the Nix pnpm store, then runs the allowed build scripts and prepare inside the sandbox. A plain pnpm install runs no scripts at all (ignoreScripts in pnpm-workspace.yaml), so it leaves the tree without its builds and patches; run pnpm bootstrap instead. pnpm never installs on its own before a script (verifyDepsBeforeRun: warn): when a script warns that your node_modules are out of sync with the lockfile, rerun pnpm bootstrap.
The project uses Turbo to orchestrate tasks across workspaces:
# Build all packages
pnpm build
# Run unit and integration tests
pnpm test
# Typecheck workspace packages
pnpm typecheck
# Check code formatting with dprint
pnpm format:check
# Format files with dprint
pnpm format
# Run linter across packages
pnpm lint
# Run all CI gates locally
pnpm check:ci
# Pack every public package into a tarball
nix build .#workspace-tarballsMutation testing is not part of pnpm check:ci, and pull requests do not run it. The release gate (.github/workflows/release-gate.yml) runs on every push to main: stryker plan once over every workspace package that declares a mutation script, then, in each planned shard, every project's stryker run --shard --project child at a break threshold of 100, with its stdout and stderr kept beside its progress stream (.github/scripts/run-shard.sh). A survivor on main gets a behaviour test in a follow-up pull request, and the next gate run proves it.
- We follow Conventional Commits (
feat(cli): ...,fix(core): ...). - Ensure all CI gates (
pnpm check:ci) pass locally before opening a pull request.