@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.
- package/README.md +402 -0
- package/lib/chunk-4LSFAAZW.js +1 -0
- package/lib/chunk-JDF7DQ4V.js +27 -0
- package/lib/chunk-WOJ4C7N7.js +7122 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +318 -0
- package/lib/index.d.ts +3701 -0
- package/lib/index.js +1262 -0
- package/lib/types/index.d.ts +146 -0
- package/lib/types/index.js +28 -0
- package/package.json +89 -0
- package/src/analyzers/analyzer.ts +201 -0
- package/src/analyzers/architectural-analyzer.ts +304 -0
- package/src/analyzers/build-config-analyzer.ts +334 -0
- package/src/analyzers/circular-import-analyzer.ts +463 -0
- package/src/analyzers/config-consistency-analyzer.ts +335 -0
- package/src/analyzers/dead-code-analyzer.ts +565 -0
- package/src/analyzers/duplicate-code-analyzer.ts +626 -0
- package/src/analyzers/duplicate-dependency-analyzer.ts +381 -0
- package/src/analyzers/eslint-config-analyzer.ts +281 -0
- package/src/analyzers/exports-field-analyzer.ts +324 -0
- package/src/analyzers/index.ts +388 -0
- package/src/analyzers/large-dependency-analyzer.ts +535 -0
- package/src/analyzers/package-json-analyzer.ts +349 -0
- package/src/analyzers/peer-dependency-analyzer.ts +275 -0
- package/src/analyzers/tree-shaking-analyzer.ts +623 -0
- package/src/analyzers/tsconfig-analyzer.ts +382 -0
- package/src/analyzers/unused-dependency-analyzer.ts +356 -0
- package/src/analyzers/version-alignment-analyzer.ts +308 -0
- package/src/api/analyze-workspace.ts +245 -0
- package/src/api/index.ts +11 -0
- package/src/cache/cache-manager.ts +495 -0
- package/src/cache/cache-schema.ts +247 -0
- package/src/cache/change-detector.ts +169 -0
- package/src/cache/file-hasher.ts +65 -0
- package/src/cache/index.ts +47 -0
- package/src/cli/commands/analyze.ts +240 -0
- package/src/cli/commands/index.ts +5 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/types.ts +65 -0
- package/src/cli/ui.ts +213 -0
- package/src/cli.ts +9 -0
- package/src/config/defaults.ts +183 -0
- package/src/config/index.ts +81 -0
- package/src/config/loader.ts +270 -0
- package/src/config/merger.ts +229 -0
- package/src/config/schema.ts +263 -0
- package/src/core/incremental-analyzer.ts +462 -0
- package/src/core/index.ts +34 -0
- package/src/core/orchestrator.ts +416 -0
- package/src/graph/dependency-graph.ts +408 -0
- package/src/graph/index.ts +19 -0
- package/src/index.ts +417 -0
- package/src/parser/config-parser.ts +491 -0
- package/src/parser/import-extractor.ts +340 -0
- package/src/parser/index.ts +54 -0
- package/src/parser/typescript-parser.ts +95 -0
- package/src/performance/bundle-estimator.ts +444 -0
- package/src/performance/index.ts +27 -0
- package/src/reporters/console-reporter.ts +355 -0
- package/src/reporters/index.ts +49 -0
- package/src/reporters/json-reporter.ts +273 -0
- package/src/reporters/markdown-reporter.ts +349 -0
- package/src/reporters/reporter.ts +399 -0
- package/src/rules/builtin-rules.ts +709 -0
- package/src/rules/index.ts +52 -0
- package/src/rules/rule-engine.ts +409 -0
- package/src/scanner/index.ts +18 -0
- package/src/scanner/workspace-scanner.ts +403 -0
- package/src/types/index.ts +176 -0
- package/src/types/result.ts +19 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/pattern-matcher.ts +48 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules module exports for architectural analysis.
|
|
3
|
+
*
|
|
4
|
+
* Provides the rule engine infrastructure and built-in rules for detecting
|
|
5
|
+
* architectural anti-patterns and enforcing best practices.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
BarrelExportRuleOptions,
|
|
10
|
+
LayerViolationRuleOptions,
|
|
11
|
+
PackageBoundaryRuleOptions,
|
|
12
|
+
PathAliasRuleOptions,
|
|
13
|
+
PublicApiRuleOptions,
|
|
14
|
+
SideEffectRuleOptions,
|
|
15
|
+
} from './builtin-rules'
|
|
16
|
+
export {
|
|
17
|
+
barrelExportRuleMetadata,
|
|
18
|
+
createBarrelExportRule,
|
|
19
|
+
createLayerViolationRule,
|
|
20
|
+
createPackageBoundaryRule,
|
|
21
|
+
createPathAliasRule,
|
|
22
|
+
createPublicApiRule,
|
|
23
|
+
createSideEffectRule,
|
|
24
|
+
layerViolationRuleMetadata,
|
|
25
|
+
packageBoundaryRuleMetadata,
|
|
26
|
+
pathAliasRuleMetadata,
|
|
27
|
+
publicApiRuleMetadata,
|
|
28
|
+
sideEffectRuleMetadata,
|
|
29
|
+
} from './builtin-rules'
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
LayerConfiguration,
|
|
33
|
+
LayerDefinition,
|
|
34
|
+
LayerPattern,
|
|
35
|
+
Rule,
|
|
36
|
+
RuleContext,
|
|
37
|
+
RuleEngine,
|
|
38
|
+
RuleEngineError,
|
|
39
|
+
RuleFactory,
|
|
40
|
+
RuleMetadata,
|
|
41
|
+
RuleOptions,
|
|
42
|
+
RuleRegistration,
|
|
43
|
+
RuleResult,
|
|
44
|
+
RuleViolation,
|
|
45
|
+
} from './rule-engine'
|
|
46
|
+
export {
|
|
47
|
+
BUILTIN_RULE_IDS,
|
|
48
|
+
createRuleEngine,
|
|
49
|
+
DEFAULT_LAYER_CONFIG,
|
|
50
|
+
getFileLayer,
|
|
51
|
+
isLayerImportAllowed,
|
|
52
|
+
} from './rule-engine'
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architectural rule engine for validating code patterns and enforcing best practices.
|
|
3
|
+
*
|
|
4
|
+
* Provides a plugin architecture for extensible analysis rules that detect
|
|
5
|
+
* architectural violations, anti-patterns, and best practice deviations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {SourceFile} from 'ts-morph'
|
|
9
|
+
|
|
10
|
+
import type {WorkspacePackage} from '../scanner/workspace-scanner'
|
|
11
|
+
import type {Issue, IssueLocation, Severity} from '../types/index'
|
|
12
|
+
import type {Result} from '../types/result'
|
|
13
|
+
|
|
14
|
+
import {ok} from '@bfra.me/es/result'
|
|
15
|
+
|
|
16
|
+
import {matchPattern, normalizePath} from '../utils/pattern-matcher'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Context provided to rules during evaluation.
|
|
20
|
+
*/
|
|
21
|
+
export interface RuleContext {
|
|
22
|
+
/** The source file being evaluated */
|
|
23
|
+
readonly sourceFile: SourceFile
|
|
24
|
+
/** The package containing the source file */
|
|
25
|
+
readonly pkg: WorkspacePackage
|
|
26
|
+
/** Root path of the workspace */
|
|
27
|
+
readonly workspacePath: string
|
|
28
|
+
/** All packages in the workspace for cross-package analysis */
|
|
29
|
+
readonly allPackages: readonly WorkspacePackage[]
|
|
30
|
+
/** Resolved tsconfig paths for alias validation */
|
|
31
|
+
readonly tsconfigPaths?: Readonly<Record<string, readonly string[]>>
|
|
32
|
+
/** Layer configuration for architectural validation */
|
|
33
|
+
readonly layerConfig?: LayerConfiguration
|
|
34
|
+
/** Report progress during evaluation */
|
|
35
|
+
readonly reportProgress?: (message: string) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Layer configuration for enforcing architectural boundaries.
|
|
40
|
+
*/
|
|
41
|
+
export interface LayerConfiguration {
|
|
42
|
+
/** Layer definitions with allowed dependencies */
|
|
43
|
+
readonly layers: readonly LayerDefinition[]
|
|
44
|
+
/** File patterns to layer mapping */
|
|
45
|
+
readonly patterns: readonly LayerPattern[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Defines an architectural layer with its allowed dependencies.
|
|
50
|
+
*/
|
|
51
|
+
export interface LayerDefinition {
|
|
52
|
+
/** Layer name (e.g., 'domain', 'application', 'infrastructure') */
|
|
53
|
+
readonly name: string
|
|
54
|
+
/** Other layers this layer can import from */
|
|
55
|
+
readonly allowedDependencies: readonly string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Maps file patterns to architectural layers.
|
|
60
|
+
*/
|
|
61
|
+
export interface LayerPattern {
|
|
62
|
+
/** Glob pattern to match files */
|
|
63
|
+
readonly pattern: string
|
|
64
|
+
/** Layer this pattern belongs to */
|
|
65
|
+
readonly layer: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Result of a single rule violation.
|
|
70
|
+
*/
|
|
71
|
+
export interface RuleViolation {
|
|
72
|
+
/** Rule that was violated */
|
|
73
|
+
readonly ruleId: string
|
|
74
|
+
/** Location of the violation */
|
|
75
|
+
readonly location: IssueLocation
|
|
76
|
+
/** Human-readable message explaining the violation */
|
|
77
|
+
readonly message: string
|
|
78
|
+
/** Suggested fix for the violation */
|
|
79
|
+
readonly suggestion?: string
|
|
80
|
+
/** Related locations (e.g., the imported module) */
|
|
81
|
+
readonly relatedLocations?: readonly IssueLocation[]
|
|
82
|
+
/** Additional metadata for machine processing */
|
|
83
|
+
readonly metadata?: Readonly<Record<string, unknown>>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Result of evaluating a rule on a source file.
|
|
88
|
+
*/
|
|
89
|
+
export interface RuleResult {
|
|
90
|
+
/** Violations found */
|
|
91
|
+
readonly violations: readonly RuleViolation[]
|
|
92
|
+
/** Whether the evaluation completed successfully */
|
|
93
|
+
readonly success: boolean
|
|
94
|
+
/** Error message if evaluation failed */
|
|
95
|
+
readonly error?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Metadata describing a rule.
|
|
100
|
+
*/
|
|
101
|
+
export interface RuleMetadata {
|
|
102
|
+
/** Unique identifier for the rule */
|
|
103
|
+
readonly id: string
|
|
104
|
+
/** Human-readable name */
|
|
105
|
+
readonly name: string
|
|
106
|
+
/** Description of what the rule checks */
|
|
107
|
+
readonly description: string
|
|
108
|
+
/** Default severity for violations */
|
|
109
|
+
readonly defaultSeverity: Severity
|
|
110
|
+
/** Category for grouping */
|
|
111
|
+
readonly category: 'layer-violation' | 'barrel-export' | 'public-api' | 'side-effect' | 'boundary'
|
|
112
|
+
/** Documentation URL */
|
|
113
|
+
readonly docsUrl?: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Configuration options for a rule.
|
|
118
|
+
*/
|
|
119
|
+
export interface RuleOptions {
|
|
120
|
+
/** Whether the rule is enabled */
|
|
121
|
+
readonly enabled?: boolean
|
|
122
|
+
/** Severity override */
|
|
123
|
+
readonly severity?: Severity
|
|
124
|
+
/** File patterns to include */
|
|
125
|
+
readonly include?: readonly string[]
|
|
126
|
+
/** File patterns to exclude */
|
|
127
|
+
readonly exclude?: readonly string[]
|
|
128
|
+
/** Rule-specific options */
|
|
129
|
+
readonly options?: Readonly<Record<string, unknown>>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Core interface that all architectural rules must implement.
|
|
134
|
+
*/
|
|
135
|
+
export interface Rule {
|
|
136
|
+
/** Metadata describing the rule */
|
|
137
|
+
readonly metadata: RuleMetadata
|
|
138
|
+
/**
|
|
139
|
+
* Evaluate the rule against a source file.
|
|
140
|
+
*
|
|
141
|
+
* @param context - Rule evaluation context
|
|
142
|
+
* @returns Result containing violations found
|
|
143
|
+
*/
|
|
144
|
+
readonly evaluate: (context: RuleContext) => Promise<RuleResult>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Factory function signature for creating rules.
|
|
149
|
+
*/
|
|
150
|
+
export type RuleFactory = (options?: RuleOptions) => Rule
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Registration entry for a rule in the engine.
|
|
154
|
+
*/
|
|
155
|
+
export interface RuleRegistration {
|
|
156
|
+
/** The rule instance or factory */
|
|
157
|
+
readonly rule: Rule | RuleFactory
|
|
158
|
+
/** Whether the rule is enabled */
|
|
159
|
+
readonly enabled: boolean
|
|
160
|
+
/** Priority for execution order (lower runs first) */
|
|
161
|
+
readonly priority: number
|
|
162
|
+
/** Configuration options */
|
|
163
|
+
readonly options?: RuleOptions
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Rule engine for managing and executing architectural rules.
|
|
168
|
+
*/
|
|
169
|
+
export interface RuleEngine {
|
|
170
|
+
/** Register a rule */
|
|
171
|
+
readonly register: (id: string, registration: RuleRegistration) => void
|
|
172
|
+
/** Unregister a rule */
|
|
173
|
+
readonly unregister: (id: string) => boolean
|
|
174
|
+
/** Get a registered rule */
|
|
175
|
+
readonly get: (id: string) => RuleRegistration | undefined
|
|
176
|
+
/** Get all registered rules */
|
|
177
|
+
readonly getAll: () => Map<string, RuleRegistration>
|
|
178
|
+
/** Get enabled rules sorted by priority */
|
|
179
|
+
readonly getEnabled: () => Rule[]
|
|
180
|
+
/** Check if a rule is registered */
|
|
181
|
+
readonly has: (id: string) => boolean
|
|
182
|
+
/** Evaluate all enabled rules against a source file */
|
|
183
|
+
readonly evaluateFile: (
|
|
184
|
+
context: RuleContext,
|
|
185
|
+
) => Promise<Result<readonly Issue[], RuleEngineError>>
|
|
186
|
+
/** Evaluate all enabled rules against multiple source files */
|
|
187
|
+
readonly evaluateFiles: (
|
|
188
|
+
contexts: readonly RuleContext[],
|
|
189
|
+
) => Promise<Result<readonly Issue[], RuleEngineError>>
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Error from rule engine operations.
|
|
194
|
+
*/
|
|
195
|
+
export interface RuleEngineError {
|
|
196
|
+
/** Error code for programmatic handling */
|
|
197
|
+
readonly code: 'RULE_EXECUTION_ERROR' | 'RULE_NOT_FOUND' | 'INVALID_CONFIGURATION'
|
|
198
|
+
/** Human-readable error message */
|
|
199
|
+
readonly message: string
|
|
200
|
+
/** Rule ID that caused the error (if applicable) */
|
|
201
|
+
readonly ruleId?: string
|
|
202
|
+
/** Additional context about the error */
|
|
203
|
+
readonly context?: Readonly<Record<string, unknown>>
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolves a rule registration to a Rule instance.
|
|
208
|
+
*/
|
|
209
|
+
function resolveRule(registration: RuleRegistration): Rule {
|
|
210
|
+
if (typeof registration.rule === 'function') {
|
|
211
|
+
return registration.rule(registration.options)
|
|
212
|
+
}
|
|
213
|
+
return registration.rule
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Converts a rule violation to an Issue.
|
|
218
|
+
*/
|
|
219
|
+
function violationToIssue(violation: RuleViolation, rule: Rule): Issue {
|
|
220
|
+
return {
|
|
221
|
+
id: violation.ruleId,
|
|
222
|
+
title: `${rule.metadata.name}: ${violation.message.slice(0, 60)}${violation.message.length > 60 ? '...' : ''}`,
|
|
223
|
+
description: violation.message,
|
|
224
|
+
severity: rule.metadata.defaultSeverity,
|
|
225
|
+
category: 'architecture',
|
|
226
|
+
location: violation.location,
|
|
227
|
+
relatedLocations: violation.relatedLocations,
|
|
228
|
+
suggestion: violation.suggestion,
|
|
229
|
+
metadata: violation.metadata,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Creates a new rule engine instance.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* const engine = createRuleEngine()
|
|
239
|
+
*
|
|
240
|
+
* engine.register('no-barrel-exports', {
|
|
241
|
+
* rule: createBarrelExportRule(),
|
|
242
|
+
* enabled: true,
|
|
243
|
+
* priority: 10,
|
|
244
|
+
* })
|
|
245
|
+
*
|
|
246
|
+
* const issues = await engine.evaluateFile(context)
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export function createRuleEngine(): RuleEngine {
|
|
250
|
+
const registrations = new Map<string, RuleRegistration>()
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
register(id: string, registration: RuleRegistration): void {
|
|
254
|
+
registrations.set(id, registration)
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
unregister(id: string): boolean {
|
|
258
|
+
return registrations.delete(id)
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
get(id: string): RuleRegistration | undefined {
|
|
262
|
+
return registrations.get(id)
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
getAll(): Map<string, RuleRegistration> {
|
|
266
|
+
return new Map(registrations)
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
getEnabled(): Rule[] {
|
|
270
|
+
return Array.from(registrations.entries())
|
|
271
|
+
.filter(([, reg]) => reg.enabled)
|
|
272
|
+
.sort((a, b) => a[1].priority - b[1].priority)
|
|
273
|
+
.map(([, reg]) => resolveRule(reg))
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
has(id: string): boolean {
|
|
277
|
+
return registrations.has(id)
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
async evaluateFile(context: RuleContext): Promise<Result<readonly Issue[], RuleEngineError>> {
|
|
281
|
+
const issues: Issue[] = []
|
|
282
|
+
const enabledRules = this.getEnabled()
|
|
283
|
+
|
|
284
|
+
for (const rule of enabledRules) {
|
|
285
|
+
try {
|
|
286
|
+
const result = await rule.evaluate(context)
|
|
287
|
+
if (result.success) {
|
|
288
|
+
for (const violation of result.violations) {
|
|
289
|
+
issues.push(violationToIssue(violation, rule))
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: {
|
|
296
|
+
code: 'RULE_EXECUTION_ERROR',
|
|
297
|
+
message: `Rule ${rule.metadata.id} threw an error: ${error instanceof Error ? error.message : String(error)}`,
|
|
298
|
+
ruleId: rule.metadata.id,
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return ok(issues)
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
async evaluateFiles(
|
|
308
|
+
contexts: readonly RuleContext[],
|
|
309
|
+
): Promise<Result<readonly Issue[], RuleEngineError>> {
|
|
310
|
+
const allIssues: Issue[] = []
|
|
311
|
+
|
|
312
|
+
for (const context of contexts) {
|
|
313
|
+
const result = await this.evaluateFile(context)
|
|
314
|
+
if (!result.success) {
|
|
315
|
+
return result
|
|
316
|
+
}
|
|
317
|
+
allIssues.push(...result.data)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return ok(allIssues)
|
|
321
|
+
},
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Default layer configuration for typical TypeScript projects.
|
|
327
|
+
*/
|
|
328
|
+
export const DEFAULT_LAYER_CONFIG: LayerConfiguration = {
|
|
329
|
+
layers: [
|
|
330
|
+
{name: 'domain', allowedDependencies: []},
|
|
331
|
+
{name: 'application', allowedDependencies: ['domain']},
|
|
332
|
+
{name: 'infrastructure', allowedDependencies: ['domain', 'application']},
|
|
333
|
+
{name: 'presentation', allowedDependencies: ['domain', 'application']},
|
|
334
|
+
{name: 'shared', allowedDependencies: []},
|
|
335
|
+
],
|
|
336
|
+
patterns: [
|
|
337
|
+
{pattern: '**/domain/**', layer: 'domain'},
|
|
338
|
+
{pattern: '**/models/**', layer: 'domain'},
|
|
339
|
+
{pattern: '**/entities/**', layer: 'domain'},
|
|
340
|
+
{pattern: '**/application/**', layer: 'application'},
|
|
341
|
+
{pattern: '**/services/**', layer: 'application'},
|
|
342
|
+
{pattern: '**/use-cases/**', layer: 'application'},
|
|
343
|
+
{pattern: '**/infrastructure/**', layer: 'infrastructure'},
|
|
344
|
+
{pattern: '**/adapters/**', layer: 'infrastructure'},
|
|
345
|
+
{pattern: '**/repositories/**', layer: 'infrastructure'},
|
|
346
|
+
{pattern: '**/presentation/**', layer: 'presentation'},
|
|
347
|
+
{pattern: '**/ui/**', layer: 'presentation'},
|
|
348
|
+
{pattern: '**/components/**', layer: 'presentation'},
|
|
349
|
+
{pattern: '**/shared/**', layer: 'shared'},
|
|
350
|
+
{pattern: '**/utils/**', layer: 'shared'},
|
|
351
|
+
{pattern: '**/lib/**', layer: 'shared'},
|
|
352
|
+
],
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Determines the architectural layer for a file path based on patterns.
|
|
357
|
+
*
|
|
358
|
+
* @param filePath - File path to check
|
|
359
|
+
* @param config - Layer configuration
|
|
360
|
+
* @returns Layer name or undefined if no match
|
|
361
|
+
*/
|
|
362
|
+
export function getFileLayer(filePath: string, config: LayerConfiguration): string | undefined {
|
|
363
|
+
const normalizedPath = normalizePath(filePath)
|
|
364
|
+
|
|
365
|
+
for (const {pattern, layer} of config.patterns) {
|
|
366
|
+
if (matchPattern(normalizedPath, pattern)) {
|
|
367
|
+
return layer
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return undefined
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Checks if an import from one layer to another is allowed.
|
|
376
|
+
*
|
|
377
|
+
* @param sourceLayer - Layer of the importing file
|
|
378
|
+
* @param targetLayer - Layer of the imported module
|
|
379
|
+
* @param config - Layer configuration
|
|
380
|
+
* @returns Whether the import is allowed
|
|
381
|
+
*/
|
|
382
|
+
export function isLayerImportAllowed(
|
|
383
|
+
sourceLayer: string,
|
|
384
|
+
targetLayer: string,
|
|
385
|
+
config: LayerConfiguration,
|
|
386
|
+
): boolean {
|
|
387
|
+
if (sourceLayer === targetLayer) {
|
|
388
|
+
return true
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const sourceLayerDef = config.layers.find(l => l.name === sourceLayer)
|
|
392
|
+
if (sourceLayerDef === undefined) {
|
|
393
|
+
return true
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return sourceLayerDef.allowedDependencies.includes(targetLayer)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Built-in rule IDs.
|
|
401
|
+
*/
|
|
402
|
+
export const BUILTIN_RULE_IDS = {
|
|
403
|
+
LAYER_VIOLATION: 'layer-violation',
|
|
404
|
+
BARREL_EXPORT: 'barrel-export',
|
|
405
|
+
PUBLIC_API: 'public-api',
|
|
406
|
+
SIDE_EFFECT: 'side-effect',
|
|
407
|
+
PATH_ALIAS: 'path-alias',
|
|
408
|
+
PACKAGE_BOUNDARY: 'package-boundary',
|
|
409
|
+
} as const
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace scanning module exports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createWorkspaceScanner,
|
|
7
|
+
filterPackagesByPattern,
|
|
8
|
+
getPackageScope,
|
|
9
|
+
getUnscopedName,
|
|
10
|
+
groupPackagesByScope,
|
|
11
|
+
} from './workspace-scanner'
|
|
12
|
+
export type {
|
|
13
|
+
ScanError,
|
|
14
|
+
WorkspacePackage,
|
|
15
|
+
WorkspacePackageJson,
|
|
16
|
+
WorkspaceScannerOptions,
|
|
17
|
+
WorkspaceScanResult,
|
|
18
|
+
} from './workspace-scanner'
|