@bfra.me/workspace-analyzer
Comprehensive monorepo static analysis for workspace configuration, dependencies, and architecture.
Features
- Configuration Analysis: Detect inconsistencies across
package.json, tsconfig.json, eslint.config.ts, and tsup.config.ts files
- Dependency Analysis: Identify unused dependencies and circular imports with full path reporting
- Architectural Validation: Validate against configurable rule sets (layer violations, barrel export misuse)
- Performance Analysis: Identify tree-shaking blockers, large dependencies, and dead code
- Modern CLI: Interactive TUI using
@clack/prompts with progress reporting
- Incremental Analysis: Caching support for fast re-analysis of large codebases
- Multiple Output Formats: Console, JSON, and Markdown report generation
Installation
pnpm add -D @bfra.me/workspace-analyzer
Quick Start
npx workspace-analyzer
npx workspace-analyzer --interactive
npx workspace-analyzer --json > analysis-report.json
CLI Usage
The CLI provides a modern, interactive experience powered by @clack/prompts:
workspace-analyzer [path]
workspace-analyzer --config workspace-analyzer.config.ts
workspace-analyzer --json
workspace-analyzer --markdown
workspace-analyzer --min-severity warning
workspace-analyzer --categories dependency,architecture
workspace-analyzer --no-cache
workspace-analyzer --verbose
workspace-analyzer --dry-run
CLI Options
--config <path> | Path to configuration file |
--json | Output results as JSON |
--markdown | Output results as Markdown |
--min-severity <level> | Minimum severity to report (info, warning, error, critical) |
--categories <list> | Comma-separated categories to analyze |
--include <patterns> | Glob patterns for files to include |
--exclude <patterns> | Glob patterns for files to exclude |
--no-cache | Disable incremental analysis caching |
--cache-dir <path> | Custom cache directory |
--concurrency <n> | Maximum parallel operations |
--verbose | Enable verbose logging |
--quiet | Suppress non-essential output |
--dry-run | Preview analysis without executing |
--interactive | Interactive package selection |
--fix | Apply auto-fixes where available (placeholder) |
Visualization Command
Generate interactive dependency graph visualizations with the visualize command:
workspace-analyzer visualize
workspace-analyzer visualize --format json --output graph.json
workspace-analyzer visualize --format mermaid --output graph.mmd
workspace-analyzer visualize --interactive
workspace-analyzer visualize --title "My Project" --max-nodes 100
workspace-analyzer visualize --format both
Visualization Options
--output <path> | Output file path | workspace-graph.html |
--format <format> | Output format: html, json, mermaid, both | html |
--no-open | Disable auto-opening in browser | false (opens by default) |
--title <title> | Visualization title | Workspace Dependency Graph |
--max-nodes <number> | Maximum nodes to render (performance) | 1000 |
--include-type-imports | Include type-only imports in graph | false |
--interactive | Interactive mode for options selection | false |
Export Formats
HTML - Self-contained interactive visualization with D3.js:
- Force-directed graph layout with zoom/pan controls
- Filter by layer, severity, package scope
- View modes: all, cycles-only, violations-only
- Node tooltips with violation details
- No external network requests (fully offline)
JSON - Structured data for external tools and custom processing:
{
"nodes": [
{
"id": "src/utils/helpers.ts",
"name": "helpers.ts",
"filePath": "src/utils/helpers.ts",
"packageName": "@myorg/utils",
"layer": "infrastructure",
"importsCount": 5,
"importedByCount": 12,
"isInCycle": false,
"violations": [
{
"id": "v1",
"message": "Layer violation: infrastructure importing from domain",
"severity": "error",
"ruleId": "layer-dependency"
}
],
"highestViolationSeverity": "error"
}
],
"edges": [
{
"source": "src/utils/helpers.ts",
"target": "src/core/types.ts",
"type": "static",
"isInCycle": false
}
],
"cycles": [
{
"id": "cycle-1",
"nodes": ["src/a.ts", "src/b.ts"],
"edges": [
{"from": "src/a.ts", "to": "src/b.ts"},
{"from": "src/b.ts", "to": "src/a.ts"}
],
"length": 2,
"description": "Circular dependency: a.ts → b.ts → a.ts"
}
],
"statistics": {
"totalNodes": 250,
"totalEdges": 380,
"totalCycles": 3,
"nodesByLayer": {
"domain": 50,
"application": 80,
"infrastructure": 120
},
"violationsBySeverity": {
"critical": 0,
"error": 5,
"warning": 15,
"info": 2
},
"packagesAnalyzed": 12,
"filesAnalyzed": 250
},
"metadata": {
"workspacePath": "/path/to/workspace",
"generatedAt": "2025-12-10T00:00:00Z",
"analyzerVersion": "0.1.0"
}
}
Mermaid - Diagram markup for documentation and external rendering:
graph LR
src_utils_helpers_ts["helpers.ts"]:::class-error
src_core_types_ts["types.ts"]:::class-normal
src_utils_helpers_ts --> src_core_types_ts
classDef class-critical fill:#ff4444,stroke:#cc0000,stroke-width:3px,color:#fff
classDef class-error fill:#ff8844,stroke:#cc4400,stroke-width:2px,color:#fff
classDef class-warning fill:#ffcc44,stroke:#ccaa00,stroke-width:2px,color:#000
classDef class-info fill:#4488ff,stroke:#0044cc,stroke-width:1px,color:#fff
classDef class-normal fill:#88ccff,stroke:#0088cc,stroke-width:1px,color:#000
Common Visualization Scenarios
Investigate Circular Dependencies:
workspace-analyzer visualize --title "Circular Dependencies" --output cycles.html
workspace-analyzer visualize --format json --output cycles.json
workspace-analyzer visualize --format mermaid --output cycles.mmd
Analyze Layer Architecture:
workspace-analyzer visualize --title "Architecture Review" --output architecture.html
workspace-analyzer visualize --title "Core Packages" --output core.html
Performance Analysis:
workspace-analyzer visualize --max-nodes 200 --output critical-paths.html
workspace-analyzer visualize --format json --output metrics.json
CI/CD Integration:
workspace-analyzer visualize --format both --no-open --output build/graph
workspace-analyzer visualize --format json | jq '.statistics.totalCycles == 0'
Integration with External Tools
The JSON export format is designed for integration with custom analysis pipelines:
import {readFile} from 'node:fs/promises'
const data = JSON.parse(await readFile('graph.json', 'utf-8'))
const criticalFiles = data.nodes.filter(
node => node.highestViolationSeverity === 'critical'
)
const largeCycles = data.cycles
.filter(cycle => cycle.length > 5)
.sort((a, b) => b.length - a.length)
const avgImportsPerFile = data.statistics.totalEdges / data.statistics.totalNodes
const cycleRate = data.statistics.totalCycles / data.statistics.totalNodes
const layerViolations = data.nodes
.filter(node => node.violations.some(v => v.ruleId === 'layer-dependency'))
.map(node => ({
file: node.filePath,
layer: node.layer,
violations: node.violations.length,
}))
.sort((a, b) => b.violations - a.violations)
const highlyConnected = data.nodes
.filter(node => node.importsCount + node.importedByCount > 20)
.sort((a, b) =>
(b.importsCount + b.importedByCount) - (a.importsCount + a.importedByCount)
)
Programmatic API
analyzeWorkspace(path, options)
Analyzes an entire workspace for issues.
import {analyzeWorkspace} from '@bfra.me/workspace-analyzer'
const result = await analyzeWorkspace('./my-monorepo', {
minSeverity: 'warning',
categories: ['dependency', 'architecture'],
onProgress: (progress) => {
console.log(`${progress.phase}: ${progress.processed}/${progress.total}`)
},
})
if (result.success) {
console.log(`Found ${result.data.summary.totalIssues} issues`)
for (const issue of result.data.issues) {
console.log(`[${issue.severity}] ${issue.title}`)
console.log(` ${issue.location.filePath}:${issue.location.line}`)
if (issue.suggestion) {
console.log(` Fix: ${issue.suggestion}`)
}
}
} else {
console.error(`Analysis failed: ${result.error.message}`)
}
analyzePackages(path, packageNames, options)
Analyzes specific packages within a workspace.
import {analyzePackages} from '@bfra.me/workspace-analyzer'
const result = await analyzePackages('./my-monorepo', [
'@myorg/core',
'@myorg/utils',
])
if (result.success) {
console.log(`Analyzed ${result.data.summary.packagesAnalyzed} packages`)
}
defineConfig(config)
Type-safe configuration helper.
import {defineConfig} from '@bfra.me/workspace-analyzer'
export default defineConfig({
minSeverity: 'warning',
categories: ['dependency', 'architecture'],
})
Configuration
Create a workspace-analyzer.config.ts file in your workspace root:
import {defineConfig} from '@bfra.me/workspace-analyzer'
export default defineConfig({
include: ['**/*.ts', '**/*.tsx'],
exclude: ['**/node_modules/**', '**/lib/**', '**/dist/**'],
minSeverity: 'warning',
categories: ['dependency', 'configuration', 'architecture'],
cache: true,
cacheDir: '.workspace-analyzer-cache',
concurrency: 4,
packagePatterns: ['packages/*', 'apps/*'],
analyzers: {
'circular-import': {enabled: true, severity: 'error'},
'unused-dependency': {enabled: true, severity: 'warning'},
'config-consistency': {enabled: true},
'tree-shaking-blocker': {enabled: true},
'dead-code': {enabled: false},
},
architecture: {
layers: [
{name: 'domain', patterns: ['**/domain/**'], allowedImports: []},
{name: 'application', patterns: ['**/application/**'], allowedImports: ['domain']},
{name: 'infrastructure', patterns: ['**/infrastructure/**'], allowedImports: ['domain', 'application']},
],
allowBarrelExports: ['**/index.ts'],
enforcePublicApi: true,
},
})
Configuration File Names
The analyzer searches for configuration in these files (in order):
workspace-analyzer.config.ts
workspace-analyzer.config.js
workspace-analyzer.config.mjs
workspace-analyzer.config.cjs
Built-in Analyzers
Configuration Analyzers
package-json | Validates package.json structure, exports, and scripts |
tsconfig | Checks TypeScript configuration consistency |
eslint-config | Validates ESLint configuration patterns |
build-config | Analyzes tsup/build configuration |
config-consistency | Cross-validates configuration across files |
version-alignment | Checks dependency version consistency across packages |
exports-field | Validates exports field against actual source files |
Dependency Analyzers
unused-dependency | Detects dependencies declared but not imported |
circular-import | Finds circular import chains using Tarjan's algorithm |
peer-dependency | Validates peer dependency declarations |
duplicate-dependency | Finds duplicate dependencies across packages |
Architectural Analyzers
architectural | Enforces layer boundaries and import restrictions |
Performance Analyzers
tree-shaking-blocker | Detects patterns that prevent tree-shaking |
dead-code | Finds unreachable/unused exports |
duplicate-code | Identifies similar code blocks via AST fingerprinting |
large-dependency | Flags large dependencies that may impact bundle size |
Issue Categories
configuration | Package.json, tsconfig.json, and build config issues |
dependency | Unused, duplicate, or misversioned dependencies |
architecture | Layer violations, barrel export misuse, public API issues |
performance | Tree-shaking blockers, large dependencies |
circular-import | Circular dependency chains |
unused-export | Exports not used within the workspace |
type-safety | TypeScript configuration issues |
Severity Levels
info | Informational findings, suggestions for improvement |
warning | Potential issues that should be reviewed |
error | Problems that should be fixed |
critical | Severe issues requiring immediate attention |
Architectural Rules
Layer Violation Rule
Enforces architectural layer boundaries:
defineConfig({
architecture: {
layers: [
{name: 'domain', patterns: ['**/domain/**'], allowedImports: []},
{name: 'application', patterns: ['**/app/**'], allowedImports: ['domain']},
{name: 'infrastructure', patterns: ['**/infra/**'], allowedImports: ['domain', 'application']},
],
},
})
Barrel Export Rule
Controls export * usage:
defineConfig({
architecture: {
allowBarrelExports: ['**/index.ts'],
},
})
Public API Rule
Enforces explicit public API surface:
defineConfig({
architecture: {
enforcePublicApi: true,
},
})
Output Formats
Console (Default)
Colorized terminal output with severity highlighting and file locations.
JSON
Machine-readable output for CI integration:
workspace-analyzer --json > report.json
{
"issues": [],
"summary": {
"totalIssues": 5,
"bySeverity": {"warning": 3, "error": 2},
"byCategory": {"dependency": 2, "architecture": 3},
"packagesAnalyzed": 8,
"filesAnalyzed": 150,
"durationMs": 2500
}
}
Markdown
Human-readable report for documentation or PR comments:
workspace-analyzer --markdown > ANALYSIS.md
Caching
The analyzer supports incremental caching for improved performance:
defineConfig({
cache: true,
cacheDir: '.workspace-analyzer-cache',
maxCacheAge: 7 * 24 * 60 * 60 * 1000,
hashAlgorithm: 'sha256',
})
Cache is automatically invalidated when:
- Source files change (content hash)
- Configuration files change
- Analyzer versions change
CI Integration
GitHub Actions
- name: Run workspace analysis
run: pnpm workspace-analyzer --json > analysis-report.json
- name: Check for errors
run: |
if jq -e '.summary.bySeverity.error > 0' analysis-report.json; then
echo "Analysis found errors"
exit 1
fi
Pre-commit Hook
{
"lint-staged": {
"*.{ts,tsx}": ["workspace-analyzer --categories dependency,architecture"]
}
}
Development
pnpm --filter @bfra.me/workspace-analyzer build
pnpm --filter @bfra.me/workspace-analyzer test
pnpm --filter @bfra.me/workspace-analyzer type-check
Related Packages
License
MIT © Marcus R. Brown