Adoption tiers
The setup comes in four tiers. The first three are npm packages: install them, and upgrade when you choose. The fourth is files you copy. Each tier works without the ones after it, so stop wherever the next one costs more than it buys.
| Tier | You get | How |
|---|---|---|
| 1. Checks | Strict lint, and a check of each package as npm will get it | Install two packages |
| 2. Shared configs | TypeScript, SWC, c8, Biome and Vitest configs, one per concern | Install one package, extend it |
| 3. Nx plugin and lifecycle | Every build, test and lint target inferred, cached and ordered | nx add @leyman/nx-plugin |
| 4. Devcontainer, CI and skills | The same toolchain locally and in CI, releases, and agent skills | Copy files with the fork manifest |
All of it assumes TypeScript packages published as ESM.
To see where a repository stands, run @leyman/framework's audit in it. It runs a check for each tier, reports the highest tier met, and gives the fix for each gap. It also prints the path of a skill that has a coding agent audit what needs judgment against the principles:
pnpm dlx @leyman/framework audit1. Checks
Install @leyman/check-package and @leyman/eslint-config in any repository, Nx or not.
What it buys
check-packagepacks a package with your package manager and checks the tarball: publint, Are The Types Wrong, a strict typecheck of the published declarations, the Node.js support floor (engines.node,@types/nodeand every runtime dependency agree), and that every top-level file is either published or known not to be. Most broken releases fail one of these.@leyman/eslint-configturns on nearly every rule of ESLint and ten plugins, type-aware, with each exception explained, generated from the package'spackage.json. Its plugins take patch releases only, so new rules arrive with a release of the config.@leyman/expectis optional: Chai and chai-as-promised as one import, which this setup's tests use.
What it costs
- ESLint 10 with a flat config, and formatting left to Biome or Prettier.
- On an existing codebase, expect a long first list of errors. The config is opinionated to a fault on purpose: turn off what doesn't fit in your own
eslint.config.js, with a reason. - Type-aware lint is slower than syntax-only lint.
check-packagewantsengines.nodeas a floor (>=22), a matching@types/node, and afilesallow-list.
2. Shared configs
Install @leyman/configs in each package, and extend or point at its files.
What it buys
- A tsconfig per Node.js support floor (
tsconfig.node-22.json,tsconfig.node-24.json), holding only thelibandtargetthat version has, so using a newer API is a compile error rather than a crash for your oldest users. Strict options throughout. - One SWC, c8, Biome and Vitest config, shared rather than copied, upgraded with the package.
What it costs
- TypeScript 6 or newer, Biome 2.6, c8 12,
@swc/core1.16 and Vitest 5, for the configs you use. - c8 fails below 100% coverage of lines, statements, functions and branches.
- Each package installs
@leyman/configs, since its tools find the files in its ownnode_modules.
3. Nx plugin and lifecycle
In an Nx workspace, run nx add @leyman/nx-plugin. It installs @leyman/nx-plugin and runs its init generator, which adds it and nx-lifecycle to nx.json, installs tiers 1 and 2 at the root, and writes the root files the targets need. Add packages with nx g @leyman/nx-plugin:package. Getting started walks through it from an empty directory.
What it buys
- Every package's targets inferred from its files: an SWC build with a
tsctypecheck, Mocha or Vitest under c8 with one coverage report across them, ESLint, Biome,check-packagefor public packages, and the TypeScript project references kept in sync. - Precise caching: each target hashes only what its result depends on, its sources, the shared configs and its dependencies' built output, so changing one package's tests reruns nothing downstream.
- One order for everything: install, prepare, check, build, test and verify, wired by nx-lifecycle.
nx run-many -t verifyis the whole check of the workspace. - A
packagegenerator that writes a new package with its targets declared, so it is verified from its first commit.
What it costs
- Nx, and this layout in every package:
src/built todist/, tests intest/unit/andtest/integration/. - pnpm with
injectWorkspacePackages, whichinitsets: a package's dependents get it as published, not its source, and async-injectedtarget keeps those copies current after each build. - Tiers 1 and 2, as the targets run them.
4. Devcontainer, CI and skills
The rest can't be a package: a devcontainer per git worktree, CI through Dagger on GitHub Actions, releases with changesets, and the skills coding agents read about this repository's way of working. The skills about a package (its config, its lifecycle, its test helpers) ship with it instead, at any tier: pnpm 12.11 or later links them into .agents/skills once approved under permissions in pnpm-workspace.yaml, which the plugin's init sets, so they change with the version. A clone of this repository copies them into a new repository with the fork manifest, which installs tiers 1 to 3 from npm:
node leyman/main/scripts/fork-manifest.mjs apply --to <your repository> --with ci,dagger,publishing--with takes the optional parts you want: Go, Rust, Postgres or Valkey sidecars, Playwright, a docs site and more.
What it buys
- The same toolchain everywhere: the devcontainer image is what Dagger runs CI in, and each worktree gets its own container and sidecars, sharing one Nx cache.
- CI you can run locally (
dagger call), and releases from a merged version PR, published with provenance. - Skills and an
AGENTS.mdthat let a coding agent work in the repository from its first session, with the principles behind every choice.
What it costs
- Docker, and Dagger for CI.
- You own what you copy. Packages upgrade through their changelogs; copied files don't.
applyrecords inleyman-upstream.jsonthe commit it copied from and what it took, and from a checkout of this repositorynode leyman/main/scripts/fork-manifest.mjs drift --fork <your repository>lists the commits since then that touched those files, and the files new to the parts you took. You decide what to bring over, since your copies hold your own versions and names, thenresync --fork <your repository>records that you're caught up. - The skills name this repository's packages as examples until you rewrite them.