@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,183 @@
1
+ /**
2
+ * Default configuration values for workspace analyzer.
3
+ *
4
+ * Provides sensible defaults that work well for typical TypeScript monorepo projects,
5
+ * with all options explicitly documented and type-safe.
6
+ */
7
+
8
+ import type {IssueCategory, Severity} from '../types/index'
9
+
10
+ /**
11
+ * Default glob patterns for including source files in analysis.
12
+ */
13
+ export const DEFAULT_INCLUDE_PATTERNS: readonly string[] = [
14
+ '**/*.ts',
15
+ '**/*.tsx',
16
+ '**/*.js',
17
+ '**/*.jsx',
18
+ '**/*.mts',
19
+ '**/*.cts',
20
+ ]
21
+
22
+ /**
23
+ * Default glob patterns for excluding files from analysis.
24
+ */
25
+ export const DEFAULT_EXCLUDE_PATTERNS: readonly string[] = [
26
+ '**/node_modules/**',
27
+ '**/dist/**',
28
+ '**/lib/**',
29
+ '**/build/**',
30
+ '**/coverage/**',
31
+ '**/*.d.ts',
32
+ '**/*.test.ts',
33
+ '**/*.test.tsx',
34
+ '**/*.spec.ts',
35
+ '**/*.spec.tsx',
36
+ '**/__tests__/**',
37
+ '**/__mocks__/**',
38
+ ]
39
+
40
+ /**
41
+ * Default package location patterns for monorepo scanning.
42
+ */
43
+ export const DEFAULT_PACKAGE_PATTERNS: readonly string[] = ['packages/*', 'apps/*']
44
+
45
+ /**
46
+ * Default severity threshold for issue reporting.
47
+ */
48
+ export const DEFAULT_MIN_SEVERITY: Severity = 'info'
49
+
50
+ /**
51
+ * Default categories to analyze (all categories when empty).
52
+ */
53
+ export const DEFAULT_CATEGORIES: readonly IssueCategory[] = []
54
+
55
+ /**
56
+ * Default concurrency limit for parallel analysis.
57
+ */
58
+ export const DEFAULT_CONCURRENCY = 4
59
+
60
+ /**
61
+ * Default cache directory name.
62
+ */
63
+ export const DEFAULT_CACHE_DIR = '.workspace-analyzer-cache'
64
+
65
+ /**
66
+ * Default maximum cache age in milliseconds (7 days).
67
+ */
68
+ export const DEFAULT_MAX_CACHE_AGE = 7 * 24 * 60 * 60 * 1000
69
+
70
+ /**
71
+ * Default hash algorithm for file content hashing.
72
+ */
73
+ export const DEFAULT_HASH_ALGORITHM = 'sha256' as const
74
+
75
+ /**
76
+ * Complete default analyzer configuration.
77
+ */
78
+ export interface DefaultAnalyzerConfig {
79
+ /** Glob patterns for files to include */
80
+ readonly include: readonly string[]
81
+ /** Glob patterns for files to exclude */
82
+ readonly exclude: readonly string[]
83
+ /** Minimum severity level to report */
84
+ readonly minSeverity: Severity
85
+ /** Categories of issues to check (empty means all) */
86
+ readonly categories: readonly IssueCategory[]
87
+ /** Whether to enable caching */
88
+ readonly cache: boolean
89
+ /** Custom rules configuration */
90
+ readonly rules: Readonly<Record<string, unknown>>
91
+ }
92
+
93
+ /**
94
+ * Default analyzer configuration values.
95
+ */
96
+ export const DEFAULT_ANALYZER_CONFIG: DefaultAnalyzerConfig = {
97
+ include: DEFAULT_INCLUDE_PATTERNS,
98
+ exclude: DEFAULT_EXCLUDE_PATTERNS,
99
+ minSeverity: DEFAULT_MIN_SEVERITY,
100
+ categories: DEFAULT_CATEGORIES,
101
+ cache: true,
102
+ rules: {},
103
+ }
104
+
105
+ /**
106
+ * Default workspace analyzer options.
107
+ */
108
+ export interface DefaultWorkspaceOptions {
109
+ /** Glob patterns for package locations */
110
+ readonly packagePatterns: readonly string[]
111
+ /** Maximum parallel analysis operations */
112
+ readonly concurrency: number
113
+ /** Cache directory path */
114
+ readonly cacheDir: string
115
+ /** Maximum cache age in milliseconds */
116
+ readonly maxCacheAge: number
117
+ /** Hash algorithm for file content */
118
+ readonly hashAlgorithm: 'sha256' | 'md5'
119
+ }
120
+
121
+ /**
122
+ * Default workspace analyzer options values.
123
+ */
124
+ export const DEFAULT_WORKSPACE_OPTIONS: DefaultWorkspaceOptions = {
125
+ packagePatterns: DEFAULT_PACKAGE_PATTERNS,
126
+ concurrency: DEFAULT_CONCURRENCY,
127
+ cacheDir: DEFAULT_CACHE_DIR,
128
+ maxCacheAge: DEFAULT_MAX_CACHE_AGE,
129
+ hashAlgorithm: DEFAULT_HASH_ALGORITHM,
130
+ }
131
+
132
+ /**
133
+ * Complete default configuration combining analyzer and workspace options.
134
+ */
135
+ export interface DefaultConfig extends DefaultAnalyzerConfig, DefaultWorkspaceOptions {
136
+ /** Configuration file path (optional) */
137
+ readonly configPath?: string
138
+ }
139
+
140
+ /**
141
+ * Get the complete default configuration.
142
+ */
143
+ export function getDefaultConfig(): DefaultConfig {
144
+ return {
145
+ ...DEFAULT_ANALYZER_CONFIG,
146
+ ...DEFAULT_WORKSPACE_OPTIONS,
147
+ configPath: undefined,
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Built-in analyzer IDs that are enabled by default.
153
+ */
154
+ export const DEFAULT_ENABLED_ANALYZERS = [
155
+ 'package-json',
156
+ 'tsconfig',
157
+ 'eslint-config',
158
+ 'build-config',
159
+ 'config-consistency',
160
+ 'version-alignment',
161
+ 'exports-field',
162
+ 'unused-dependency',
163
+ 'circular-import',
164
+ 'peer-dependency',
165
+ 'duplicate-dependency',
166
+ 'architectural',
167
+ 'dead-code',
168
+ 'duplicate-code',
169
+ 'large-dependency',
170
+ 'tree-shaking-blocker',
171
+ ] as const
172
+
173
+ /**
174
+ * Built-in rule IDs that are enabled by default.
175
+ */
176
+ export const DEFAULT_ENABLED_RULES = [
177
+ 'layer-violation',
178
+ 'barrel-export',
179
+ 'public-api',
180
+ 'side-effect',
181
+ 'package-boundary',
182
+ 'path-alias',
183
+ ] as const
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Configuration module for workspace analyzer.
3
+ *
4
+ * Provides configuration loading, validation, merging, and the defineConfig() helper
5
+ * for workspace-analyzer.config.ts files.
6
+ */
7
+
8
+ // Default configuration values
9
+ export {
10
+ DEFAULT_ANALYZER_CONFIG,
11
+ DEFAULT_CACHE_DIR,
12
+ DEFAULT_CATEGORIES,
13
+ DEFAULT_CONCURRENCY,
14
+ DEFAULT_ENABLED_ANALYZERS,
15
+ DEFAULT_ENABLED_RULES,
16
+ DEFAULT_EXCLUDE_PATTERNS,
17
+ DEFAULT_HASH_ALGORITHM,
18
+ DEFAULT_INCLUDE_PATTERNS,
19
+ DEFAULT_MAX_CACHE_AGE,
20
+ DEFAULT_MIN_SEVERITY,
21
+ DEFAULT_PACKAGE_PATTERNS,
22
+ DEFAULT_WORKSPACE_OPTIONS,
23
+ getDefaultConfig,
24
+ } from './defaults'
25
+
26
+ export type {DefaultAnalyzerConfig, DefaultConfig, DefaultWorkspaceOptions} from './defaults'
27
+
28
+ // Configuration file loader
29
+ export {CONFIG_FILE_NAMES, defineConfig, findConfigFile, loadConfig, loadConfigFile} from './loader'
30
+
31
+ export type {
32
+ ConfigLoadError,
33
+ ConfigLoadErrorCode,
34
+ ConfigLoadResult,
35
+ WorkspaceAnalyzerConfig,
36
+ } from './loader'
37
+
38
+ // Configuration merging
39
+ export {
40
+ getAnalyzerOptions,
41
+ getDefaultMergedConfig,
42
+ mergeAnalyzerConfigs,
43
+ mergeConfig,
44
+ } from './merger'
45
+
46
+ export type {MergedConfig} from './merger'
47
+
48
+ // Zod schemas and validation
49
+ export {
50
+ analyzerConfigSchema,
51
+ analyzerOptionsSchema,
52
+ analyzersConfigSchema,
53
+ analyzeWorkspaceOptionsSchema,
54
+ architecturalRulesSchema,
55
+ categorySchema,
56
+ layerConfigSchema,
57
+ parseAnalyzerConfig,
58
+ parseWorkspaceAnalyzerConfig,
59
+ ruleConfigSchema,
60
+ safeParseAnalyzeOptions,
61
+ safeParseWorkspaceAnalyzerConfig,
62
+ severitySchema,
63
+ workspaceAnalyzerConfigSchema,
64
+ workspaceOptionsSchema,
65
+ } from './schema'
66
+
67
+ export type {
68
+ AnalyzerConfigInput,
69
+ AnalyzerConfigOutput,
70
+ AnalyzeWorkspaceOptionsInput,
71
+ AnalyzeWorkspaceOptionsOutput,
72
+ CategoryInput,
73
+ CategoryOutput,
74
+ SafeParseResult,
75
+ SeverityInput,
76
+ SeverityOutput,
77
+ WorkspaceAnalyzerConfigInput,
78
+ WorkspaceAnalyzerConfigOutput,
79
+ WorkspaceOptionsInput,
80
+ WorkspaceOptionsOutput,
81
+ } from './schema'
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Configuration file loader for workspace-analyzer.config.ts files.
3
+ *
4
+ * Provides functionality to discover, load, and parse TypeScript configuration files
5
+ * using the defineConfig() pattern similar to other tools in the ecosystem.
6
+ */
7
+
8
+ import type {Result} from '../types/result'
9
+ import type {WorkspaceAnalyzerConfigOutput} from './schema'
10
+
11
+ import fs from 'node:fs/promises'
12
+ import path from 'node:path'
13
+ import {pathToFileURL} from 'node:url'
14
+
15
+ import {err, ok} from '@bfra.me/es/result'
16
+
17
+ import {safeParseWorkspaceAnalyzerConfig} from './schema'
18
+
19
+ /**
20
+ * Configuration file names to search for, in order of precedence.
21
+ */
22
+ export const CONFIG_FILE_NAMES = [
23
+ 'workspace-analyzer.config.ts',
24
+ 'workspace-analyzer.config.js',
25
+ 'workspace-analyzer.config.mjs',
26
+ 'workspace-analyzer.config.mts',
27
+ ] as const
28
+
29
+ /**
30
+ * Error codes for configuration loading operations.
31
+ */
32
+ export type ConfigLoadErrorCode =
33
+ | 'FILE_NOT_FOUND'
34
+ | 'IMPORT_ERROR'
35
+ | 'VALIDATION_ERROR'
36
+ | 'INVALID_EXPORT'
37
+
38
+ /**
39
+ * Error that occurred during configuration loading.
40
+ */
41
+ export interface ConfigLoadError {
42
+ readonly code: ConfigLoadErrorCode
43
+ readonly message: string
44
+ readonly filePath?: string
45
+ readonly cause?: unknown
46
+ }
47
+
48
+ /**
49
+ * Result of loading a configuration file.
50
+ */
51
+ export interface ConfigLoadResult {
52
+ /** Loaded and validated configuration */
53
+ readonly config: WorkspaceAnalyzerConfigOutput
54
+ /** Path to the loaded configuration file */
55
+ readonly filePath: string
56
+ }
57
+
58
+ /**
59
+ * User-facing configuration interface for defineConfig().
60
+ * Allows partial configuration that will be merged with defaults.
61
+ */
62
+ export interface WorkspaceAnalyzerConfig {
63
+ /** Glob patterns for files to include */
64
+ include?: readonly string[]
65
+ /** Glob patterns for files to exclude */
66
+ exclude?: readonly string[]
67
+ /** Minimum severity level to report */
68
+ minSeverity?: 'info' | 'warning' | 'error' | 'critical'
69
+ /** Categories of issues to check (empty means all) */
70
+ categories?: readonly (
71
+ | 'configuration'
72
+ | 'dependency'
73
+ | 'architecture'
74
+ | 'performance'
75
+ | 'circular-import'
76
+ | 'unused-export'
77
+ | 'type-safety'
78
+ )[]
79
+ /** Enable incremental analysis caching */
80
+ cache?: boolean
81
+ /** Custom rules configuration */
82
+ rules?: Record<string, unknown>
83
+ /** Glob patterns for package locations */
84
+ packagePatterns?: readonly string[]
85
+ /** Maximum parallel analysis operations */
86
+ concurrency?: number
87
+ /** Directory for analysis cache files */
88
+ cacheDir?: string
89
+ /** Maximum cache age in milliseconds */
90
+ maxCacheAge?: number
91
+ /** Hash algorithm for file content */
92
+ hashAlgorithm?: 'sha256' | 'md5'
93
+ /** Per-analyzer configuration */
94
+ analyzers?: Record<
95
+ string,
96
+ {
97
+ enabled?: boolean
98
+ severity?: 'info' | 'warning' | 'error' | 'critical'
99
+ options?: Record<string, unknown>
100
+ }
101
+ >
102
+ /** Architectural analysis rules */
103
+ architecture?: {
104
+ layers?: {
105
+ name: string
106
+ patterns: string[]
107
+ allowedImports: string[]
108
+ }[]
109
+ allowBarrelExports?: boolean | string[]
110
+ enforcePublicApi?: boolean
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Helper function for defining configuration with type safety.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * // workspace-analyzer.config.ts
120
+ * import {defineConfig} from '@bfra.me/workspace-analyzer'
121
+ *
122
+ * export default defineConfig({
123
+ * minSeverity: 'warning',
124
+ * categories: ['dependency', 'architecture'],
125
+ * analyzers: {
126
+ * 'circular-import': {enabled: true},
127
+ * 'unused-dependency': {enabled: true},
128
+ * },
129
+ * })
130
+ * ```
131
+ */
132
+ export function defineConfig(config: WorkspaceAnalyzerConfig): WorkspaceAnalyzerConfig {
133
+ return config
134
+ }
135
+
136
+ /**
137
+ * Finds a configuration file in the given directory or its parents.
138
+ *
139
+ * @param startDir - Directory to start searching from
140
+ * @param stopAt - Directory to stop searching at (defaults to filesystem root)
141
+ * @returns Path to the found configuration file, or undefined if not found
142
+ */
143
+ export async function findConfigFile(
144
+ startDir: string,
145
+ stopAt?: string,
146
+ ): Promise<string | undefined> {
147
+ let currentDir = path.resolve(startDir)
148
+ const root = stopAt == null ? path.parse(currentDir).root : path.resolve(stopAt)
149
+
150
+ while (currentDir !== root && currentDir !== path.dirname(currentDir)) {
151
+ for (const fileName of CONFIG_FILE_NAMES) {
152
+ const filePath = path.join(currentDir, fileName)
153
+ try {
154
+ await fs.access(filePath)
155
+ return filePath
156
+ } catch {
157
+ // File doesn't exist, continue searching
158
+ }
159
+ }
160
+ currentDir = path.dirname(currentDir)
161
+ }
162
+
163
+ // Check the root directory as well
164
+ for (const fileName of CONFIG_FILE_NAMES) {
165
+ const filePath = path.join(root, fileName)
166
+ try {
167
+ await fs.access(filePath)
168
+ return filePath
169
+ } catch {
170
+ // File doesn't exist
171
+ }
172
+ }
173
+
174
+ return undefined
175
+ }
176
+
177
+ /**
178
+ * Loads and validates a configuration file.
179
+ *
180
+ * @param filePath - Path to the configuration file
181
+ * @returns Result containing the loaded config or an error
182
+ */
183
+ export async function loadConfigFile(
184
+ filePath: string,
185
+ ): Promise<Result<ConfigLoadResult, ConfigLoadError>> {
186
+ const absolutePath = path.resolve(filePath)
187
+
188
+ // Verify file exists
189
+ try {
190
+ await fs.access(absolutePath)
191
+ } catch {
192
+ return err({
193
+ code: 'FILE_NOT_FOUND',
194
+ message: `Configuration file not found: ${absolutePath}`,
195
+ filePath: absolutePath,
196
+ })
197
+ }
198
+
199
+ // Import the module
200
+ let module: unknown
201
+ try {
202
+ const fileUrl = pathToFileURL(absolutePath).href
203
+ module = await import(fileUrl)
204
+ } catch (error) {
205
+ return err({
206
+ code: 'IMPORT_ERROR',
207
+ message: `Failed to import configuration file: ${(error as Error).message}`,
208
+ filePath: absolutePath,
209
+ cause: error,
210
+ })
211
+ }
212
+
213
+ // Extract the default export
214
+ const moduleObj = module as Record<string, unknown>
215
+ const rawConfig = moduleObj.default ?? module
216
+
217
+ if (rawConfig == null || typeof rawConfig !== 'object') {
218
+ return err({
219
+ code: 'INVALID_EXPORT',
220
+ message: 'Configuration file must export an object as default export',
221
+ filePath: absolutePath,
222
+ })
223
+ }
224
+
225
+ // Validate the configuration
226
+ const parseResult = safeParseWorkspaceAnalyzerConfig(rawConfig)
227
+
228
+ if (!parseResult.success) {
229
+ const issues = parseResult.error.issues
230
+ .map(issue => ` - ${issue.path.join('.')}: ${issue.message}`)
231
+ .join('\n')
232
+ return err({
233
+ code: 'VALIDATION_ERROR',
234
+ message: `Invalid configuration:\n${issues}`,
235
+ filePath: absolutePath,
236
+ cause: parseResult.error,
237
+ })
238
+ }
239
+
240
+ return ok({
241
+ config: parseResult.data,
242
+ filePath: absolutePath,
243
+ })
244
+ }
245
+
246
+ /**
247
+ * Discovers and loads configuration from the workspace.
248
+ *
249
+ * @param workspacePath - Root path of the workspace
250
+ * @param explicitPath - Optional explicit path to config file
251
+ * @returns Result containing the loaded config or an error
252
+ */
253
+ export async function loadConfig(
254
+ workspacePath: string,
255
+ explicitPath?: string,
256
+ ): Promise<Result<ConfigLoadResult | undefined, ConfigLoadError>> {
257
+ // Use explicit path if provided
258
+ if (explicitPath != null) {
259
+ return loadConfigFile(explicitPath)
260
+ }
261
+
262
+ // Search for config file
263
+ const configPath = await findConfigFile(workspacePath)
264
+
265
+ if (configPath == null) {
266
+ return ok(undefined)
267
+ }
268
+
269
+ return loadConfigFile(configPath)
270
+ }