@bfra.me/workspace-analyzer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +402 -0
  2. package/lib/chunk-4LSFAAZW.js +1 -0
  3. package/lib/chunk-JDF7DQ4V.js +27 -0
  4. package/lib/chunk-WOJ4C7N7.js +7122 -0
  5. package/lib/cli.d.ts +1 -0
  6. package/lib/cli.js +318 -0
  7. package/lib/index.d.ts +3701 -0
  8. package/lib/index.js +1262 -0
  9. package/lib/types/index.d.ts +146 -0
  10. package/lib/types/index.js +28 -0
  11. package/package.json +89 -0
  12. package/src/analyzers/analyzer.ts +201 -0
  13. package/src/analyzers/architectural-analyzer.ts +304 -0
  14. package/src/analyzers/build-config-analyzer.ts +334 -0
  15. package/src/analyzers/circular-import-analyzer.ts +463 -0
  16. package/src/analyzers/config-consistency-analyzer.ts +335 -0
  17. package/src/analyzers/dead-code-analyzer.ts +565 -0
  18. package/src/analyzers/duplicate-code-analyzer.ts +626 -0
  19. package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
  20. package/src/analyzers/eslint-config-analyzer.ts +281 -0
  21. package/src/analyzers/exports-field-analyzer.ts +324 -0
  22. package/src/analyzers/index.ts +388 -0
  23. package/src/analyzers/large-dependency-analyzer.ts +535 -0
  24. package/src/analyzers/package-json-analyzer.ts +349 -0
  25. package/src/analyzers/peer-dependency-analyzer.ts +275 -0
  26. package/src/analyzers/tree-shaking-analyzer.ts +623 -0
  27. package/src/analyzers/tsconfig-analyzer.ts +382 -0
  28. package/src/analyzers/unused-dependency-analyzer.ts +356 -0
  29. package/src/analyzers/version-alignment-analyzer.ts +308 -0
  30. package/src/api/analyze-workspace.ts +245 -0
  31. package/src/api/index.ts +11 -0
  32. package/src/cache/cache-manager.ts +495 -0
  33. package/src/cache/cache-schema.ts +247 -0
  34. package/src/cache/change-detector.ts +169 -0
  35. package/src/cache/file-hasher.ts +65 -0
  36. package/src/cache/index.ts +47 -0
  37. package/src/cli/commands/analyze.ts +240 -0
  38. package/src/cli/commands/index.ts +5 -0
  39. package/src/cli/index.ts +61 -0
  40. package/src/cli/types.ts +65 -0
  41. package/src/cli/ui.ts +213 -0
  42. package/src/cli.ts +9 -0
  43. package/src/config/defaults.ts +183 -0
  44. package/src/config/index.ts +81 -0
  45. package/src/config/loader.ts +270 -0
  46. package/src/config/merger.ts +229 -0
  47. package/src/config/schema.ts +263 -0
  48. package/src/core/incremental-analyzer.ts +462 -0
  49. package/src/core/index.ts +34 -0
  50. package/src/core/orchestrator.ts +416 -0
  51. package/src/graph/dependency-graph.ts +408 -0
  52. package/src/graph/index.ts +19 -0
  53. package/src/index.ts +417 -0
  54. package/src/parser/config-parser.ts +491 -0
  55. package/src/parser/import-extractor.ts +340 -0
  56. package/src/parser/index.ts +54 -0
  57. package/src/parser/typescript-parser.ts +95 -0
  58. package/src/performance/bundle-estimator.ts +444 -0
  59. package/src/performance/index.ts +27 -0
  60. package/src/reporters/console-reporter.ts +355 -0
  61. package/src/reporters/index.ts +49 -0
  62. package/src/reporters/json-reporter.ts +273 -0
  63. package/src/reporters/markdown-reporter.ts +349 -0
  64. package/src/reporters/reporter.ts +399 -0
  65. package/src/rules/builtin-rules.ts +709 -0
  66. package/src/rules/index.ts +52 -0
  67. package/src/rules/rule-engine.ts +409 -0
  68. package/src/scanner/index.ts +18 -0
  69. package/src/scanner/workspace-scanner.ts +403 -0
  70. package/src/types/index.ts +176 -0
  71. package/src/types/result.ts +19 -0
  72. package/src/utils/index.ts +7 -0
  73. package/src/utils/pattern-matcher.ts +48 -0
@@ -0,0 +1,146 @@
1
+ import { Result } from '@bfra.me/es/result';
2
+ export { Err, Ok, Result, err, flatMap, fromPromise, fromThrowable, isErr, isOk, map, mapErr, ok, unwrap, unwrapOr } from '@bfra.me/es/result';
3
+
4
+ /**
5
+ * Core type definitions for workspace analysis.
6
+ *
7
+ * These types define the structure of analysis results, issues, severity levels,
8
+ * and configuration options used throughout the analyzer.
9
+ */
10
+
11
+ /**
12
+ * Severity levels for analysis issues, from informational to critical errors.
13
+ */
14
+ type Severity = 'info' | 'warning' | 'error' | 'critical';
15
+ /**
16
+ * Categories of analysis issues for grouping and filtering.
17
+ */
18
+ type IssueCategory = 'configuration' | 'dependency' | 'architecture' | 'performance' | 'circular-import' | 'unused-export' | 'type-safety';
19
+ /**
20
+ * Location information for precise issue reporting.
21
+ */
22
+ interface IssueLocation {
23
+ /** Absolute file path where the issue was detected */
24
+ readonly filePath: string;
25
+ /** Starting line number (1-indexed) */
26
+ readonly line?: number;
27
+ /** Starting column number (1-indexed) */
28
+ readonly column?: number;
29
+ /** Ending line number (1-indexed) */
30
+ readonly endLine?: number;
31
+ /** Ending column number (1-indexed) */
32
+ readonly endColumn?: number;
33
+ }
34
+ /**
35
+ * Represents a single issue detected during analysis.
36
+ */
37
+ interface Issue {
38
+ /** Unique identifier for the issue type (e.g., 'circular-import', 'unused-dep') */
39
+ readonly id: string;
40
+ /** Human-readable title summarizing the issue */
41
+ readonly title: string;
42
+ /** Detailed description explaining the problem and potential impact */
43
+ readonly description: string;
44
+ /** Severity level determining how critical this issue is */
45
+ readonly severity: Severity;
46
+ /** Category for grouping related issues */
47
+ readonly category: IssueCategory;
48
+ /** Location where the issue was detected */
49
+ readonly location: IssueLocation;
50
+ /** Additional locations related to this issue (e.g., cycle participants) */
51
+ readonly relatedLocations?: readonly IssueLocation[];
52
+ /** Suggested fix or action to resolve the issue */
53
+ readonly suggestion?: string;
54
+ /** Additional metadata for machine processing */
55
+ readonly metadata?: Readonly<Record<string, unknown>>;
56
+ }
57
+ /**
58
+ * Summary statistics for an analysis run.
59
+ */
60
+ interface AnalysisSummary {
61
+ /** Total number of issues found */
62
+ readonly totalIssues: number;
63
+ /** Issues grouped by severity */
64
+ readonly bySeverity: Readonly<Record<Severity, number>>;
65
+ /** Issues grouped by category */
66
+ readonly byCategory: Readonly<Record<IssueCategory, number>>;
67
+ /** Number of packages analyzed */
68
+ readonly packagesAnalyzed: number;
69
+ /** Number of source files analyzed */
70
+ readonly filesAnalyzed: number;
71
+ /** Analysis duration in milliseconds */
72
+ readonly durationMs: number;
73
+ }
74
+ /**
75
+ * Complete result of a workspace analysis run.
76
+ */
77
+ interface AnalysisResult {
78
+ /** All issues detected during analysis */
79
+ readonly issues: readonly Issue[];
80
+ /** Summary statistics */
81
+ readonly summary: AnalysisSummary;
82
+ /** Workspace root path that was analyzed */
83
+ readonly workspacePath: string;
84
+ /** Timestamp when analysis started */
85
+ readonly startedAt: Date;
86
+ /** Timestamp when analysis completed */
87
+ readonly completedAt: Date;
88
+ }
89
+ /**
90
+ * Configuration for controlling analyzer behavior.
91
+ */
92
+ interface AnalyzerConfig {
93
+ /** Glob patterns for files to include in analysis */
94
+ readonly include?: readonly string[];
95
+ /** Glob patterns for files to exclude from analysis */
96
+ readonly exclude?: readonly string[];
97
+ /** Minimum severity level to report */
98
+ readonly minSeverity?: Severity;
99
+ /** Categories of issues to check */
100
+ readonly categories?: readonly IssueCategory[];
101
+ /** Whether to enable caching for incremental analysis */
102
+ readonly cache?: boolean;
103
+ /** Custom rules configuration */
104
+ readonly rules?: Readonly<Record<string, unknown>>;
105
+ }
106
+ /**
107
+ * Options for the analyzeWorkspace function.
108
+ */
109
+ interface AnalyzeWorkspaceOptions extends AnalyzerConfig {
110
+ /** Path to workspace-analyzer.config.ts file */
111
+ readonly configPath?: string;
112
+ /** Callback for progress reporting */
113
+ readonly onProgress?: (progress: AnalysisProgress) => void;
114
+ }
115
+ /**
116
+ * Progress information during analysis.
117
+ */
118
+ interface AnalysisProgress {
119
+ /** Current phase of analysis */
120
+ readonly phase: 'scanning' | 'parsing' | 'analyzing' | 'reporting';
121
+ /** Current item being processed */
122
+ readonly current?: string;
123
+ /** Number of items processed so far */
124
+ readonly processed: number;
125
+ /** Total number of items to process (if known) */
126
+ readonly total?: number;
127
+ }
128
+ /**
129
+ * Represents an error that occurred during analysis.
130
+ */
131
+ interface AnalysisError {
132
+ /** Error code for programmatic handling */
133
+ readonly code: string;
134
+ /** Human-readable error message */
135
+ readonly message: string;
136
+ /** Stack trace if available */
137
+ readonly stack?: string;
138
+ /** Additional context about the error */
139
+ readonly context?: Readonly<Record<string, unknown>>;
140
+ }
141
+ /**
142
+ * Type alias for analysis operations that may fail.
143
+ */
144
+ type AnalysisResultType<T> = Result<T, AnalysisError>;
145
+
146
+ export type { AnalysisError, AnalysisProgress, AnalysisResult, AnalysisResultType, AnalysisSummary, AnalyzeWorkspaceOptions, AnalyzerConfig, Issue, IssueCategory, IssueLocation, Severity };
@@ -0,0 +1,28 @@
1
+ import "../chunk-4LSFAAZW.js";
2
+ import {
3
+ err,
4
+ flatMap,
5
+ fromPromise,
6
+ fromThrowable,
7
+ isErr,
8
+ isOk,
9
+ map,
10
+ mapErr,
11
+ ok,
12
+ unwrap,
13
+ unwrapOr
14
+ } from "../chunk-JDF7DQ4V.js";
15
+ export {
16
+ err,
17
+ flatMap,
18
+ fromPromise,
19
+ fromThrowable,
20
+ isErr,
21
+ isOk,
22
+ map,
23
+ mapErr,
24
+ ok,
25
+ unwrap,
26
+ unwrapOr
27
+ };
28
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@bfra.me/workspace-analyzer",
3
+ "version": "0.1.0",
4
+ "description": "Comprehensive monorepo static analysis for workspace configuration, dependencies, and architecture",
5
+ "keywords": [
6
+ "analyzer",
7
+ "architecture",
8
+ "ast",
9
+ "bfra.me",
10
+ "circular-dependencies",
11
+ "dependency-analysis",
12
+ "monorepo",
13
+ "static-analysis",
14
+ "typescript",
15
+ "works"
16
+ ],
17
+ "homepage": "https://github.com/bfra-me/works/tree/main/packages/workspace-analyzer#readme",
18
+ "bugs": "https://github.com/bfra-me/works/issues",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/bfra-me/works.git",
22
+ "directory": "packages/workspace-analyzer"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Marcus R. Brown <contact@bfra.me>",
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./lib/index.d.ts",
30
+ "source": "./src/index.ts",
31
+ "import": "./lib/index.js"
32
+ },
33
+ "./types": {
34
+ "types": "./lib/types/index.d.ts",
35
+ "source": "./src/types/index.ts",
36
+ "import": "./lib/types/index.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "main": "./lib/index.js",
41
+ "types": "./lib/index.d.ts",
42
+ "bin": {
43
+ "workspace-analyzer": "./lib/cli.js"
44
+ },
45
+ "files": [
46
+ "!**/*.map",
47
+ "lib",
48
+ "src"
49
+ ],
50
+ "dependencies": {
51
+ "@clack/prompts": "0.11.0",
52
+ "cac": "6.7.14",
53
+ "consola": "3.4.2",
54
+ "zod": "4.1.13",
55
+ "@bfra.me/doc-sync": "0.1.1",
56
+ "@bfra.me/es": "0.1.0"
57
+ },
58
+ "devDependencies": {
59
+ "memfs": "4.51.1",
60
+ "ts-morph": "27.0.2",
61
+ "typescript": "5.9.3",
62
+ "@bfra.me/works": "0.0.0-development"
63
+ },
64
+ "peerDependencies": {
65
+ "ts-morph": ">=27.0.0",
66
+ "typescript": ">=5.0.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "ts-morph": {
70
+ "optional": true
71
+ },
72
+ "typescript": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "publishConfig": {
77
+ "access": "public",
78
+ "provenance": true
79
+ },
80
+ "scripts": {
81
+ "build": "tsup",
82
+ "dev": "tsx src/cli.ts",
83
+ "lint": "eslint",
84
+ "start": "node lib/cli.js",
85
+ "test": "vitest run",
86
+ "test:watch": "vitest",
87
+ "type-check": "tsc --noEmit"
88
+ }
89
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Core analyzer interface and types for workspace analysis.
3
+ *
4
+ * Defines the plugin architecture contract that all analyzers must implement,
5
+ * enabling extensible and composable analysis pipelines.
6
+ */
7
+
8
+ import type {WorkspacePackage} from '../scanner/workspace-scanner'
9
+ import type {Issue, IssueCategory, Severity} from '../types/index'
10
+ import type {Result} from '../types/result'
11
+
12
+ /**
13
+ * Context provided to analyzers during analysis runs.
14
+ * Contains all information needed to perform analysis without side effects.
15
+ */
16
+ export interface AnalysisContext {
17
+ /** Root path of the workspace being analyzed */
18
+ readonly workspacePath: string
19
+ /** All packages discovered in the workspace */
20
+ readonly packages: readonly WorkspacePackage[]
21
+ /** Analyzer-specific configuration options */
22
+ readonly config: AnalyzerOptions
23
+ /** Report progress during long-running analysis */
24
+ readonly reportProgress?: (message: string) => void
25
+ }
26
+
27
+ /**
28
+ * Options for configuring analyzer behavior.
29
+ */
30
+ export interface AnalyzerOptions {
31
+ /** Glob patterns for files to include */
32
+ readonly include?: readonly string[]
33
+ /** Glob patterns for files to exclude */
34
+ readonly exclude?: readonly string[]
35
+ /** Minimum severity level to report */
36
+ readonly minSeverity?: Severity
37
+ /** Categories to analyze (empty means all) */
38
+ readonly categories?: readonly IssueCategory[]
39
+ /** Analyzer-specific rule configurations */
40
+ readonly rules?: Readonly<Record<string, unknown>>
41
+ }
42
+
43
+ /**
44
+ * Error that can occur during analysis.
45
+ */
46
+ export interface AnalyzerError {
47
+ /** Error code for programmatic handling */
48
+ readonly code: string
49
+ /** Human-readable error message */
50
+ readonly message: string
51
+ /** Additional context about the error */
52
+ readonly context?: Readonly<Record<string, unknown>>
53
+ }
54
+
55
+ /**
56
+ * Metadata describing an analyzer implementation.
57
+ */
58
+ export interface AnalyzerMetadata {
59
+ /** Unique identifier for this analyzer */
60
+ readonly id: string
61
+ /** Human-readable name */
62
+ readonly name: string
63
+ /** Description of what this analyzer checks */
64
+ readonly description: string
65
+ /** Categories of issues this analyzer can detect */
66
+ readonly categories: readonly IssueCategory[]
67
+ /** Default severity for issues from this analyzer */
68
+ readonly defaultSeverity: Severity
69
+ }
70
+
71
+ /**
72
+ * Core interface that all analyzers must implement.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const myAnalyzer: Analyzer = {
77
+ * metadata: {
78
+ * id: 'my-analyzer',
79
+ * name: 'My Custom Analyzer',
80
+ * description: 'Checks for custom patterns',
81
+ * categories: ['architecture'],
82
+ * defaultSeverity: 'warning',
83
+ * },
84
+ * async analyze(context) {
85
+ * const issues: Issue[] = []
86
+ * // Perform analysis...
87
+ * return ok(issues)
88
+ * },
89
+ * }
90
+ * ```
91
+ */
92
+ export interface Analyzer {
93
+ /** Metadata describing this analyzer */
94
+ readonly metadata: AnalyzerMetadata
95
+ /**
96
+ * Perform analysis and return discovered issues.
97
+ *
98
+ * @param context - Analysis context with workspace information
99
+ * @returns Result containing issues found or an error
100
+ */
101
+ readonly analyze: (context: AnalysisContext) => Promise<Result<readonly Issue[], AnalyzerError>>
102
+ }
103
+
104
+ /**
105
+ * Factory function signature for creating analyzers.
106
+ */
107
+ export type AnalyzerFactory = (options?: AnalyzerOptions) => Analyzer
108
+
109
+ /**
110
+ * Configuration for a single analyzer in the registry.
111
+ */
112
+ export interface AnalyzerRegistration {
113
+ /** The analyzer instance or factory */
114
+ readonly analyzer: Analyzer | AnalyzerFactory
115
+ /** Whether this analyzer is enabled by default */
116
+ readonly enabled: boolean
117
+ /** Priority for execution order (lower runs first) */
118
+ readonly priority: number
119
+ }
120
+
121
+ /**
122
+ * Creates an issue with consistent defaults.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const issue = createIssue({
127
+ * id: 'missing-types',
128
+ * title: 'Missing types field',
129
+ * description: 'package.json should declare a types entry point',
130
+ * severity: 'warning',
131
+ * category: 'configuration',
132
+ * location: {filePath: '/path/to/package.json'},
133
+ * })
134
+ * ```
135
+ */
136
+ export function createIssue(
137
+ params: Omit<Issue, 'relatedLocations' | 'suggestion' | 'metadata'> & {
138
+ relatedLocations?: Issue['relatedLocations']
139
+ suggestion?: Issue['suggestion']
140
+ metadata?: Issue['metadata']
141
+ },
142
+ ): Issue {
143
+ return {
144
+ id: params.id,
145
+ title: params.title,
146
+ description: params.description,
147
+ severity: params.severity,
148
+ category: params.category,
149
+ location: params.location,
150
+ relatedLocations: params.relatedLocations,
151
+ suggestion: params.suggestion,
152
+ metadata: params.metadata,
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Helper to check if an analyzer should skip a category based on configuration.
158
+ */
159
+ export function shouldAnalyzeCategory(
160
+ category: IssueCategory,
161
+ options: AnalyzerOptions | undefined,
162
+ ): boolean {
163
+ const categories = options?.categories
164
+ if (categories === undefined || categories.length === 0) {
165
+ return true
166
+ }
167
+ return categories.includes(category)
168
+ }
169
+
170
+ /**
171
+ * Helper to check if an issue meets minimum severity threshold.
172
+ */
173
+ export function meetsMinSeverity(severity: Severity, minSeverity: Severity | undefined): boolean {
174
+ if (minSeverity === undefined) {
175
+ return true
176
+ }
177
+
178
+ const severityOrder: Record<Severity, number> = {
179
+ info: 0,
180
+ warning: 1,
181
+ error: 2,
182
+ critical: 3,
183
+ }
184
+
185
+ return severityOrder[severity] >= severityOrder[minSeverity]
186
+ }
187
+
188
+ /**
189
+ * Filters issues based on configuration options.
190
+ */
191
+ export function filterIssues(issues: readonly Issue[], options: AnalyzerOptions): readonly Issue[] {
192
+ return issues.filter(issue => {
193
+ if (!meetsMinSeverity(issue.severity, options.minSeverity)) {
194
+ return false
195
+ }
196
+ if (!shouldAnalyzeCategory(issue.category, options)) {
197
+ return false
198
+ }
199
+ return true
200
+ })
201
+ }