@adddog/monorepo-consistency
v0.0.8
Published
A tool for maintaining consistency across a pnpm monorepo, including automated TypeScript configuration generation and package.json hygiene checks.
Readme
@adddog/monorepo-consistency
A tool for maintaining consistency across a pnpm monorepo, including automated TypeScript configuration generation and package.json hygiene checks.
Quick Start
Initialize a new configuration file:
# Interactive mode - prompts for common settings
mono init
# Use defaults - generates complete config without prompts
mono init --defaults
# Custom output location
mono init --output path/to/config.json
# Force overwrite existing config
mono init --forceThe init command creates a complete monorepo.config.json file with sensible defaults for:
- Dependency management
- TypeScript configuration generation
- Package.json hygiene checks
Commands
Initialization
# Initialize configuration interactively
mono init
# Initialize with all defaults (no prompts)
mono init --defaults
# Specify output location
mono init --output custom-config.json
# Force overwrite existing configuration
mono init --forceInteractive mode asks for:
- Enable/disable TypeScript config generation
- Enable/disable package.json hygiene checks
- Enable/disable dependency checks
- Taze runner preference
Defaults mode creates a complete configuration with:
- All checks enabled
- Recommended scripts for build, lint, types, test
- Node >= 22, pnpm >= 10 engine requirements
- Private packages by default
TypeScript Configuration Management
# Generate TypeScript configurations for all packages
mono tsconfig generate
# Generate with verbose output
mono tsconfig generate --verbose
# Generate only specific config types
mono tsconfig generate --type web
mono tsconfig generate --type node
mono tsconfig generate --type builder
# Dry run (preview without writing)
mono tsconfig generate --dry-run
# Force overwrite existing configs
mono tsconfig generate --force
# Check for configuration issues
mono tsconfig check
# Check and automatically fix issues
mono tsconfig check --fix
# Validate configurations
mono tsconfig validate
# Validate with strict rules
mono tsconfig validate --strict
# Validate specific files
mono tsconfig validate packages/shadcn-vue-design-system/tsconfig.jsonTypeScript Configuration Generation
How It Works
The TypeScript configuration generator creates consistent tsconfig.json files across your monorepo by:
- Scanning for marker files - Looks for
web.tsconfig.json,node.tsconfig.json, orbuilder.tsconfig.jsonin package directories - Merging configurations - Combines base configs from
packages/configwith local overrides - Filtering paths - Removes unnecessary
compilerOptions.pathsbased on actual package dependencies - Writing output - Generates final
tsconfig.jsonandtsconfig.typecheck.jsonfiles
Requirements
For the TypeScript generation to work, your monorepo needs:
1. Centralized Configuration Package
A packages/config directory containing base TypeScript configurations:
packages/config/
├── base.tsconfig.json # Base config with all compiler options and paths
├── web.tsconfig.json # Web-specific config (extends base)
├── node.tsconfig.json # Node-specific config (extends base)
├── builder.tsconfig.json # Builder-specific config
└── superbase.tsconfig.json # Additional base config (optional)Example packages/config/base.tsconfig.json:
{
"extends": "./superbase.tsconfig.json",
"compilerOptions": {
"paths": {
"@domain/env": ["./packages/env/src/index", "../env/src/index", ...],
"@domain/logging": ["./packages/logging/src/index", ...],
"@domain/config": ["./packages/config/src/index", ...],
// ... all possible workspace packages
}
}
}Example packages/config/web.tsconfig.json:
{
"extends": "./base.tsconfig.json",
"compilerOptions": {
"types": ["vite/client", "vitest/globals"],
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"]
}
}Example packages/config/node.tsconfig.json:
{
"extends": "./base.tsconfig.json",
"compilerOptions": {
"types": ["node"],
"lib": ["ESNext"],
"moduleDetection": "force"
}
}2. Local Marker Files (REQUIRED)
⚠️ CRITICAL PREREQUISITE: Each package MUST have a marker file for the generator to work.
The presence of web.tsconfig.json or node.tsconfig.json in a package directory tells the generator:
- That this package needs a generated tsconfig.json
- What type of config to generate (web or node)
- What local overrides to apply
Without these marker files, the generator will skip the package entirely.
Web Packages (Vite, Vue, React, etc.)
Create web.tsconfig.json in your package root:
// packages/shadcn-vue-design-system/web.tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src*": ["./src/*"]
}
}
}What this does:
- Signals this is a web package
- Merges with
packages/config/web.tsconfig.json(which extendsbase.tsconfig.json) - Adds local
src*path mapping - Results in a tsconfig with DOM types, Vite types, etc.
Node Packages (Backend, Scripts, CLI tools)
Create node.tsconfig.json in your package root:
// park-app/apps/backend/node.tsconfig.json
{
// Empty is fine - just needs to exist as a marker
}Or with local overrides:
// park-app/apps/backend/node.tsconfig.json
{
"compilerOptions": {
"types": ["node", "jest"]
}
}What this does:
- Signals this is a Node.js package
- Merges with
packages/config/node.tsconfig.json(which extendsbase.tsconfig.json) - Results in a tsconfig with Node types, ESNext lib, etc.
The Marker File Pattern
your-package/
├── package.json # Contains dependencies
├── web.tsconfig.json # ← MARKER FILE (web) - required for generation
├── tsconfig.json # ← GENERATED (do not edit manually)
├── tsconfig.typecheck.json # ← GENERATED (do not edit manually)
└── src/
└── index.tsKey points:
- Marker files are checked into source control - They define the package's config type
- Generated files can be gitignored - They're regenerated on demand
- Only one marker file per package - Use either
web.tsconfig.jsonORnode.tsconfig.json - Marker files can be minimal - Even an empty
{}works
When to Use Which Marker File
| Package Type | Marker File | Example Packages |
|--------------|-------------|------------------|
| Frontend (Vite/Vue/React) | web.tsconfig.json | packages/shadcn-vue-design-system, park-app/apps/webui |
| Backend (Fastify/Express) | node.tsconfig.json | park-app/apps/backend, ai-agents |
| CLI Tools | node.tsconfig.json | monorepo-scripts, kubernetes/kubectl-debug |
| Shared Libraries (Node) | node.tsconfig.json | packages/logging, packages/env |
| Shared Libraries (Web) | web.tsconfig.json | packages/design-tokens, packages/map2d-vue |
3. Package Dependencies
The generator reads package.json to filter compilerOptions.paths:
{
"name": "@domain/design-system",
"dependencies": {
"@domain/design-tokens": "workspace:*"
},
"devDependencies": {
"@domain/config": "workspace:*",
"@domain/eslint": "workspace:*"
}
}Result: Generated tsconfig.json will only include paths for:
@domain/design-tokens@domain/config@domain/eslint- Non-scoped paths (like
src*)
All other @domain/* or @park-app/* paths from the base config are filtered out.
Configuration Search Strategy
The generator searches for base configs in these relative locations from each package:
../config
../../config
../packages/config
../../packages/config
../../../packages/config
../../../../packages/configThis allows for both:
- Monorepo-wide configs at
packages/config/ - Sub-project configs at
park-app/packages/config/,dnd-3.5/packages/config/, etc.
Generated Files
For each package with a marker file, generates:
tsconfig.json- Merged and filtered configuration- Combines base config + local overrides
- Filters
compilerOptions.pathsto only dependencies - Removes
extendsfield (fully resolved)
tsconfig.typecheck.json- Type-checking configuration{ "extends": "./tsconfig.json", "compilerOptions": { "noEmit": true, "composite": false, "skipLibCheck": true } }
Path Filtering Logic
The generator automatically filters compilerOptions.paths to include only:
- Packages in dependencies/devDependencies from
package.json - Non-scoped paths (paths not starting with
@)
Example:
Base config has 15+ @domain/* paths, but package only uses 3:
// package.json
{
"dependencies": { "@domain/design-tokens": "workspace:*" },
"devDependencies": {
"@domain/config": "workspace:*",
"@domain/eslint": "workspace:*"
}
}
// Generated tsconfig.json (filtered)
{
"compilerOptions": {
"paths": {
"@domain/design-tokens/*": [...],
"@domain/config": [...],
"@domain/eslint": [...],
"src*": ["./src/*"] // Non-scoped, always kept
}
}
}Complete Generation Example
Here's a real-world example showing what gets generated:
Input Files
packages/shadcn-vue-design-system/package.json:
{
"name": "@domain/design-system",
"dependencies": {
"@domain/design-tokens": "workspace:*"
},
"devDependencies": {
"@domain/config": "workspace:*",
"@domain/eslint": "workspace:*"
}
}packages/shadcn-vue-design-system/web.tsconfig.json (marker file):
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src*": ["./src/*"]
}
}
}packages/config/base.tsconfig.json (15+ paths):
{
"compilerOptions": {
"paths": {
"@domain/env": ["./packages/env/src/index", "../env/src/index", ...],
"@domain/vite": ["./packages/vite/src/index", ...],
"@domain/logging": ["./packages/logging/src/index", ...],
"@domain/config": ["./packages/config/src/index", ...],
"@domain/design-tokens/*": ["./packages/design-tokens/*", ...],
"@domain/eslint": ["./packages/eslint", ...],
"@domain/type-utils": ["./packages/type-utils/src/index", ...],
"@domain/map2d/*": ["./packages/map2d/src/*", ...],
"@domain/worker-streams": ["./packages/worker-streams/src/index", ...],
// ... 6 more @domain/* paths
}
}
}packages/config/web.tsconfig.json:
{
"extends": "./base.tsconfig.json",
"compilerOptions": {
"types": ["vite/client", "vitest/globals"],
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"]
}
}Generated Output
packages/shadcn-vue-design-system/tsconfig.json (auto-generated):
{
"compilerOptions": {
"resolveJsonModule": true,
"strictNullChecks": true,
"esModuleInterop": true,
"target": "ESNext",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"],
"moduleResolution": "bundler",
"strict": true,
"paths": {
"@domain/config": [
"./packages/config/src/index",
"../config/src/index",
"../packages/config/src/index",
"../../packages/config/src/index",
"../../../packages/config/src/index"
],
"@domain/design-tokens/*": [
"./packages/design-tokens/*",
"../design-tokens/*",
"../packages/design-tokens/*",
"../../packages/design-tokens/*",
"../../../packages/design-tokens/*"
],
"@domain/eslint": [
"./packages/eslint",
"../eslint",
"../packages/eslint",
"../../packages/eslint",
"../../../packages/eslint"
],
"src*": ["./src/*"]
},
"types": ["vite/client", "vitest/globals"],
"baseUrl": "."
},
"include": ["src"],
"exclude": ["node_modules", "dist", "build"]
}Notice:
- ✅ All compiler options from
base.tsconfig.jsonmerged - ✅ Web-specific settings from
web.tsconfig.jsonmerged - ✅ Local
src*path from marker file included - ✅ Only 3
@domain/*paths (matching package.json dependencies) - ❌ 12+ other
@domain/*paths filtered out
packages/shadcn-vue-design-system/tsconfig.typecheck.json (auto-generated):
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"composite": false,
"declaration": false,
"declarationDir": null,
"emitDeclarationOnly": false,
"skipLibCheck": true
}
}Configuration
You can configure the tool's behavior via a monorepo.config.json file:
{
"version": "1.0.0",
"tsconfig": {
"enabled": true,
"types": ["web", "node", "builder"],
"configLocations": [
"../config",
"../../config",
"../packages/config",
"../../packages/config"
],
"generateTypecheck": true,
"filterPathsByDependencies": true,
"excludePatterns": [
"**/node_modules/**",
"**/dist/**",
"**/build/**"
],
"rootConfigDir": "packages/config",
"validation": {
"checkMissing": true,
"checkExtends": true,
"checkConsistency": true,
"strictMode": false
}
},
"packageJson": {
"enabled": true,
"scripts": {
"enforce": false,
"required": {
"build": "unbuild",
"lint": "eslint .",
"types": "tsc -p tsconfig.typecheck.json"
}
},
"fields": {
"required": ["name", "version"]
}
},
"deps": {
"checkUnused": true,
"checkMissing": true,
"checkVersionMismatch": true
}
}Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | true | Enable TypeScript config generation |
| types | array | ['web', 'node', 'builder'] | Config types to generate |
| configLocations | array | See config | Possible locations for app-specific configs |
| generateTypecheck | boolean | true | Generate tsconfig.typecheck.json files |
| filterPathsByDependencies | boolean | true | Filter paths by actual dependencies |
| excludePatterns | array | node_modules, dist, build | Patterns to exclude when scanning |
| rootConfigDir | string | 'packages/config' | Root config directory to skip |
| validation.checkMissing | boolean | true | Check for missing tsconfig.json files |
| validation.checkExtends | boolean | true | Validate extends chains |
| validation.checkConsistency | boolean | true | Check compiler options consistency |
| validation.strictMode | boolean | false | Enforce strict validation rules |
Health Checks
Check Command
The check command scans your monorepo for TypeScript configuration issues:
mono tsconfig checkWhat it checks:
- ✅ Missing
tsconfig.jsonfiles (when marker files exist) - ✅ Missing
tsconfig.typecheck.jsonfiles - ✅ Broken extends chains
- ✅ Circular extends references
Example output:
✓ Checked 45 packages
Issues found:
MEDIUM:
[missing-tsconfig] packages/new-feature/tsconfig.json: Package has base config but missing generated tsconfig.json
Fix: Run: mono tsconfig generate
LOW:
[missing-typecheck-config] packages/shadcn-vue-design-system/tsconfig.typecheck.json: Missing typecheck configuration
Fix: Run: mono tsconfig generate
Summary: 2 issue(s) - Critical: 0, High: 0, Medium: 1, Low: 1Auto-fix mode:
mono tsconfig check --fixThis will automatically regenerate configurations to fix issues.
Validate Command
The validate command performs deeper validation of TypeScript configurations:
mono tsconfig validateWhat it validates:
- ✅ JSON syntax correctness
- ✅ Extends chain validity
- ✅ Circular reference detection
- ✅ Missing compiler options (strict mode)
- ✅ Non-strict TypeScript settings (strict mode)
Strict mode validation:
mono tsconfig validate --strictIn strict mode, additional checks are performed:
- Validates that
compilerOptionsexists - Checks that
strict: trueis set - Reports missing critical compiler options
Validate specific files:
mono tsconfig validate packages/shadcn-vue-design-system/tsconfig.json park-app/apps/webui/tsconfig.jsonPractical Workflows
Setting Up a New Package
Create the marker file based on package type:
# For a web package echo '{"compilerOptions":{"baseUrl":".","paths":{"src*":["./src/*"]}}}' > web.tsconfig.json # For a node package echo '{}' > node.tsconfig.jsonAdd dependencies to
package.json:{ "devDependencies": { "@domain/config": "workspace:*", "@domain/eslint": "workspace:*" } }Generate the config:
mono tsconfig generate --verboseVerify the generated
tsconfig.jsononly includes paths for your dependencies
CI/CD Integration
Add TypeScript config health checks to your CI pipeline:
# .github/workflows/ci.yml
- name: Check TypeScript configurations
run: |
mono tsconfig check
mono tsconfig validateOr use strict mode for production:
- name: Validate TypeScript configurations (strict)
run: mono tsconfig validate --strictRegular Maintenance
Run these commands periodically to keep configs healthy:
# Check for issues
mono tsconfig check
# Regenerate all configs
mono tsconfig generate --force
# Validate everything
mono tsconfig validateAdding a New Dependency
When you add a new workspace dependency:
# 1. Add to package.json
pnpm add -D @domain/logging
# 2. Regenerate configs to pick up the new path
mono tsconfig generate
# 3. The generated tsconfig.json now includes @domain/logging pathsDebugging Generated Configs
# See exactly what's being generated
mono tsconfig generate --verbose
# Preview without writing files
mono tsconfig generate --dry-run --verbose
# Force regeneration if files seem stale
mono tsconfig generate --force
# Check what's wrong
mono tsconfig check --verbose
# Validate configurations
mono tsconfig validate --verboseTroubleshooting
My package's tsconfig.json wasn't generated
Check:
- ✅ Does the package have
web.tsconfig.jsonORnode.tsconfig.json? - ✅ Does the package have a
package.json? - ✅ Is there a
packages/config/directory with base configs?
Solution: Add the appropriate marker file (web.tsconfig.json or node.tsconfig.json)
The generated tsconfig.json has too many paths
Symptom: You see paths for packages you don't use
Cause: Those packages might be in your dependencies or devDependencies
Solution:
- Check
package.json- remove unused dependencies - Regenerate:
mono tsconfig generate
The generated tsconfig.json is missing a path I need
Symptom: Import fails but the package isn't in compilerOptions.paths
Cause: The package isn't in your package.json dependencies
Solution:
- Add to
package.json:pnpm add @domain/missing-package - Regenerate:
mono tsconfig generate
I want to customize my tsconfig.json
DON'T: Edit tsconfig.json directly (it gets overwritten)
DO: Add customizations to your marker file:
// web.tsconfig.json or node.tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src*": ["./src/*"],
"@custom/alias": ["./src/custom"]
},
"types": ["vite/client", "custom-types"]
},
"include": ["src", "custom-dir"]
}These overrides will be merged into the generated tsconfig.json.
How do I add a new monorepo package to the base config?
When you create a new shared package like @domain/new-package:
Add the path to
packages/config/base.tsconfig.json:{ "compilerOptions": { "paths": { "@domain/new-package": [ "./packages/new-package/src/index", "../new-package/src/index", "../packages/new-package/src/index", "../../packages/new-package/src/index", "../../../packages/new-package/src/index" ] } } }Regenerate all configs:
mono tsconfig generateThe path will only appear in packages that have
@domain/new-packagein theirpackage.json
Configuration is valid but TypeScript still has errors
Symptom: mono tsconfig validate passes but tsc reports errors
Possible causes:
Extends chain is broken in a non-obvious way
- Run:
mono tsconfig checkto detect broken chains
- Run:
Paths are correct but files don't exist
- Verify the actual files exist at the paths specified
Build order issues
- Some packages may need to be built before others can resolve types
Stale configuration
- Try:
mono tsconfig generate --force
- Try:
Scripts
| Script | Description |
|--------|-------------|
| build | unbuild |
| lint | eslint . |
| lint:fix | eslint --fix . |
| types | tsc -p tsconfig.typecheck.json |
