How to Add Nark to a Turborepo / pnpm Monorepo
By Nark Team
Nark works in Turborepo and pnpm monorepos by scanning each package's tsconfig.json independently. You add a nark script to each package that has TypeScript source code, then run it through Turbo's task pipeline to get parallel execution and caching. The setup takes under five minutes for a typical monorepo with 3-10 packages.
Quick Answer: Add
"nark": "nark --tsconfig ./tsconfig.json"to each package'spackage.json, registernarkas a Turbo task, and runturbo nark. Turbo handles parallelization and caching. For CI:npx turbo nark. To scan a single package directly:npx nark --tsconfig packages/api/tsconfig.json
Monorepo Structure
A typical Turborepo + pnpm monorepo looks like this:
my-saas/
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── tsconfig.json
│ │ └── package.json
│ └── api/ # Express/Fastify backend
│ ├── tsconfig.json
│ └── package.json
├── packages/
│ ├── database/ # Prisma client wrapper
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── email/ # Email service (nodemailer, resend)
│ │ ├── tsconfig.json
│ │ └── package.json
│ └── shared/ # Shared utils (no npm deps to check)
│ ├── tsconfig.json
│ └── package.json
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
Each app and package has its own tsconfig.json. Nark scans each one separately.
Step 1: Install Nark
Install Nark as a dev dependency at the root of your monorepo:
pnpm add -D nark -w
The -w flag installs it at the workspace root. Every package can now run nark via the shared node_modules/.bin.
Step 2: Add Nark Scripts to Each Package
Add a nark script to each package that has TypeScript source code calling npm dependencies:
// apps/api/package.json
{
"name": "@my-saas/api",
"scripts": {
"build": "tsc",
"nark": "nark --tsconfig ./tsconfig.json"
}
}
// packages/database/package.json
{
"name": "@my-saas/database",
"scripts": {
"build": "tsc",
"nark": "nark --tsconfig ./tsconfig.json"
}
}
// packages/email/package.json
{
"name": "@my-saas/email",
"scripts": {
"build": "tsc",
"nark": "nark --tsconfig ./tsconfig.json"
}
}
Skip packages that have no npm dependency calls to check (like a pure utility/types package).
Step 3: Register Nark as a Turbo Task
Add nark to your turbo.json pipeline:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"nark": {
"dependsOn": ["build"],
"cache": true
}
}
}
Key settings:
dependsOn: ["build"]means Nark runs after the package builds. This ensures TypeScript compilation succeeds before scanning. If your packages do not need to build before scanning, remove this dependency.cache: truemeans Turbo caches the result. If the package source has not changed, Turbo skips the scan on the next run.
Step 4: Run It
# Scan all packages in parallel
pnpm turbo nark
# Scan a specific package
pnpm turbo nark --filter=@my-saas/api
# Scan only packages that changed since main
pnpm turbo nark --filter=...[main]
Turbo runs Nark on each package in parallel, respecting the dependency graph. Output looks like:
@my-saas/api:nark: cache miss, executing
@my-saas/database:nark: cache miss, executing
@my-saas/email:nark: cache miss, executing
@my-saas/database:nark:
@my-saas/database:nark: ERROR @prisma/client prisma.user.create() without P2002 handling
@my-saas/database:nark: src/users.ts:23 in createUser()
@my-saas/database:nark:
@my-saas/database:nark: 1 violation found
@my-saas/api:nark:
@my-saas/api:nark: ERROR axios axios.get() called without try-catch
@my-saas/api:nark: src/integrations/slack.ts:12 in sendNotification()
@my-saas/api:nark:
@my-saas/api:nark: 1 violation found
@my-saas/email:nark: 0 violations found
Tasks: 1 successful, 2 failed
Step 5: Add to CI
# .github/workflows/ci.yml
name: CI
on: [pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo build
- name: Lint
run: pnpm turbo lint
- name: Nark — runtime error handling
run: pnpm turbo nark
Turbo caching works in CI too. If you set up Turbo Remote Caching, packages that have not changed since the last CI run get cache hits and skip the scan entirely.
Scanning Without Turbo
If you use pnpm workspaces without Turborepo, you can run Nark directly:
# Scan a specific package
pnpm --filter @my-saas/api exec nark --tsconfig ./tsconfig.json
# Scan all packages with a nark script
pnpm -r run nark
# Scan from the root against a specific tsconfig
npx nark --tsconfig apps/api/tsconfig.json
The pnpm -r run nark command runs the nark script in every package that has one, sequentially.
Handling tsconfig Paths and Aliases
Monorepos often use TypeScript path aliases (@my-saas/database, @/lib/utils). Nark uses the TypeScript compiler to resolve imports, so it respects your tsconfig.json paths configuration.
If your package extends a root tsconfig.json:
// packages/database/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}
Nark resolves the extends chain and uses the full compiler configuration. No extra flags needed.
Scanning Only Changed Packages in CI
For large monorepos, scanning every package on every PR is wasteful. Use Turbo's filter to scan only packages affected by the current changes:
# Only scan packages that changed since the base branch
- name: Nark — changed packages only
run: pnpm turbo nark --filter=...[origin/main]
This uses Turbo's change detection to identify which packages have modified files since origin/main, then runs Nark only on those packages and their dependents.
Frequently Asked Questions
Does Nark scan across package boundaries?
Nark scans the files included in each tsconfig.json independently. If apps/api imports from packages/database, Nark scans each package's files based on its own tsconfig. It does not cross-reference violations across packages.
Should I install Nark in every package or just the root?
Install at the root with pnpm add -D nark -w. All packages can access it through the workspace's hoisted node_modules/.bin. No need to install it per-package.
Does Nark work with Nx monorepos?
Yes. The same pattern applies. Add a nark target to your project.json files and configure it in nx.json. Nark just needs a path to tsconfig.json.
What about packages that don't use npm dependencies?
Skip them. If a package only exports TypeScript types or pure utility functions with no external dependency calls, there is nothing for Nark to check. Do not add a nark script to those packages.
Can I use a shared Nark config across packages?
Nark reads .nark/config.yaml from the current working directory. You can place one at the monorepo root and one per-package for overrides. Package-level config takes precedence.
Try It Now
# From your monorepo root
pnpm add -D nark -w
npx nark --tsconfig apps/api/tsconfig.json
Nark checks 160+ packages — including axios, prisma, stripe, and redis — for correct error handling, resource cleanup, and runtime Nark Profiles. Set it up once in your Turbo pipeline and every package gets checked on every PR with parallel execution and caching.