@principal-ai/principal-view-core 0.5.6
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 +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- package/src/utils/YamlParser.ts +75 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VGC Configuration Loader and Validator
|
|
3
|
+
* Handles loading and validating .vgcrc files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PrivuConfig, RuleSeverity, RuleConfig } from './types';
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Constants
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Valid config file names in resolution order
|
|
14
|
+
*/
|
|
15
|
+
export const CONFIG_FILE_NAMES = [
|
|
16
|
+
'.vgcrc.json',
|
|
17
|
+
'.vgcrc.yaml',
|
|
18
|
+
'.vgcrc.yml',
|
|
19
|
+
'vgc.config.json',
|
|
20
|
+
'vgc.config.yaml',
|
|
21
|
+
'vgc.config.yml',
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default include patterns
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_INCLUDE_PATTERNS = ['.vgc/**/*.yaml', '.vgc/**/*.yml', '.vgc/**/*.json'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default exclude patterns
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_EXCLUDE_PATTERNS = ['**/node_modules/**', '**/*.test.*'];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Valid severity values
|
|
36
|
+
*/
|
|
37
|
+
export const VALID_SEVERITIES: readonly (RuleSeverity | false)[] = [
|
|
38
|
+
'off',
|
|
39
|
+
'warn',
|
|
40
|
+
'error',
|
|
41
|
+
0,
|
|
42
|
+
1,
|
|
43
|
+
2,
|
|
44
|
+
false,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* All valid rule IDs
|
|
49
|
+
*/
|
|
50
|
+
export const VALID_RULE_IDS = [
|
|
51
|
+
// Schema rules
|
|
52
|
+
'no-unknown-fields',
|
|
53
|
+
'required-metadata',
|
|
54
|
+
'valid-node-types',
|
|
55
|
+
'valid-edge-types',
|
|
56
|
+
'valid-color-format',
|
|
57
|
+
// Reference rules
|
|
58
|
+
'connection-type-references',
|
|
59
|
+
'state-transition-references',
|
|
60
|
+
// Structure rules
|
|
61
|
+
'minimum-node-sources',
|
|
62
|
+
'orphaned-node-types',
|
|
63
|
+
'orphaned-edge-types',
|
|
64
|
+
'unreachable-states',
|
|
65
|
+
'dead-end-states',
|
|
66
|
+
// Pattern rules
|
|
67
|
+
'valid-action-patterns',
|
|
68
|
+
// Library rules
|
|
69
|
+
'library-node-type-match',
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export type ValidRuleId = (typeof VALID_RULE_IDS)[number];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Rule-specific valid options
|
|
76
|
+
*/
|
|
77
|
+
export const RULE_OPTIONS_SCHEMA: Record<string, string[]> = {
|
|
78
|
+
'minimum-node-sources': ['minimum', 'excludeNodeTypes'],
|
|
79
|
+
'dead-end-states': ['terminalStates'],
|
|
80
|
+
'valid-action-patterns': ['strictMode'],
|
|
81
|
+
'library-node-type-match': ['allowExtra'],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Validation Errors
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export interface ConfigValidationError {
|
|
89
|
+
path: string;
|
|
90
|
+
message: string;
|
|
91
|
+
suggestion?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ConfigValidationResult {
|
|
95
|
+
valid: boolean;
|
|
96
|
+
errors: ConfigValidationError[];
|
|
97
|
+
config?: PrivuConfig;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Validation Functions
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate a VGC configuration object
|
|
106
|
+
*/
|
|
107
|
+
export function validatePrivuConfig(config: unknown): ConfigValidationResult {
|
|
108
|
+
const errors: ConfigValidationError[] = [];
|
|
109
|
+
|
|
110
|
+
if (typeof config !== 'object' || config === null) {
|
|
111
|
+
return {
|
|
112
|
+
valid: false,
|
|
113
|
+
errors: [{ path: '', message: 'Configuration must be an object' }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const configObj = config as Record<string, unknown>;
|
|
118
|
+
|
|
119
|
+
// Validate known top-level fields
|
|
120
|
+
const validTopLevelFields = [
|
|
121
|
+
'$schema',
|
|
122
|
+
'extends',
|
|
123
|
+
'root',
|
|
124
|
+
'library',
|
|
125
|
+
'include',
|
|
126
|
+
'exclude',
|
|
127
|
+
'rules',
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
for (const key of Object.keys(configObj)) {
|
|
131
|
+
if (!validTopLevelFields.includes(key)) {
|
|
132
|
+
const suggestion = findSimilarString(key, validTopLevelFields);
|
|
133
|
+
errors.push({
|
|
134
|
+
path: key,
|
|
135
|
+
message: `Unknown configuration field "${key}"`,
|
|
136
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : undefined,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Validate $schema
|
|
142
|
+
if (configObj.$schema !== undefined && typeof configObj.$schema !== 'string') {
|
|
143
|
+
errors.push({
|
|
144
|
+
path: '$schema',
|
|
145
|
+
message: '$schema must be a string',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validate extends
|
|
150
|
+
if (configObj.extends !== undefined) {
|
|
151
|
+
if (
|
|
152
|
+
typeof configObj.extends !== 'string' &&
|
|
153
|
+
!(
|
|
154
|
+
Array.isArray(configObj.extends) &&
|
|
155
|
+
configObj.extends.every((e) => typeof e === 'string')
|
|
156
|
+
)
|
|
157
|
+
) {
|
|
158
|
+
errors.push({
|
|
159
|
+
path: 'extends',
|
|
160
|
+
message: 'extends must be a string or array of strings',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Validate root
|
|
166
|
+
if (configObj.root !== undefined && typeof configObj.root !== 'boolean') {
|
|
167
|
+
errors.push({
|
|
168
|
+
path: 'root',
|
|
169
|
+
message: 'root must be a boolean',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Validate library
|
|
174
|
+
if (configObj.library !== undefined && typeof configObj.library !== 'string') {
|
|
175
|
+
errors.push({
|
|
176
|
+
path: 'library',
|
|
177
|
+
message: 'library must be a string path',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Validate include
|
|
182
|
+
if (configObj.include !== undefined) {
|
|
183
|
+
if (
|
|
184
|
+
!Array.isArray(configObj.include) ||
|
|
185
|
+
!configObj.include.every((p) => typeof p === 'string')
|
|
186
|
+
) {
|
|
187
|
+
errors.push({
|
|
188
|
+
path: 'include',
|
|
189
|
+
message: 'include must be an array of strings',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Validate exclude
|
|
195
|
+
if (configObj.exclude !== undefined) {
|
|
196
|
+
if (
|
|
197
|
+
!Array.isArray(configObj.exclude) ||
|
|
198
|
+
!configObj.exclude.every((p) => typeof p === 'string')
|
|
199
|
+
) {
|
|
200
|
+
errors.push({
|
|
201
|
+
path: 'exclude',
|
|
202
|
+
message: 'exclude must be an array of strings',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Validate rules
|
|
208
|
+
if (configObj.rules !== undefined) {
|
|
209
|
+
if (typeof configObj.rules !== 'object' || configObj.rules === null) {
|
|
210
|
+
errors.push({
|
|
211
|
+
path: 'rules',
|
|
212
|
+
message: 'rules must be an object',
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
const rulesErrors = validateRulesConfig(configObj.rules as Record<string, unknown>);
|
|
216
|
+
errors.push(...rulesErrors);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (errors.length > 0) {
|
|
221
|
+
return { valid: false, errors };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
valid: true,
|
|
226
|
+
errors: [],
|
|
227
|
+
config: configObj as PrivuConfig,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Validate the rules section of a VGC config
|
|
233
|
+
*/
|
|
234
|
+
function validateRulesConfig(rules: Record<string, unknown>): ConfigValidationError[] {
|
|
235
|
+
const errors: ConfigValidationError[] = [];
|
|
236
|
+
|
|
237
|
+
for (const [ruleId, ruleConfig] of Object.entries(rules)) {
|
|
238
|
+
const path = `rules.${ruleId}`;
|
|
239
|
+
|
|
240
|
+
// Check if rule ID is valid
|
|
241
|
+
if (!VALID_RULE_IDS.includes(ruleId as ValidRuleId)) {
|
|
242
|
+
const suggestion = findSimilarString(ruleId, [...VALID_RULE_IDS]);
|
|
243
|
+
errors.push({
|
|
244
|
+
path,
|
|
245
|
+
message: `Unknown rule ID "${ruleId}"`,
|
|
246
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : undefined,
|
|
247
|
+
});
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Handle false (disabled)
|
|
252
|
+
if (ruleConfig === false) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Handle severity shorthand
|
|
257
|
+
if (typeof ruleConfig === 'string' || typeof ruleConfig === 'number') {
|
|
258
|
+
if (!isValidSeverity(ruleConfig)) {
|
|
259
|
+
errors.push({
|
|
260
|
+
path,
|
|
261
|
+
message: `Invalid severity "${ruleConfig}" for rule "${ruleId}"`,
|
|
262
|
+
suggestion: 'Allowed values: "off", "warn", "error", 0, 1, 2',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle full config object
|
|
269
|
+
if (typeof ruleConfig !== 'object' || ruleConfig === null) {
|
|
270
|
+
errors.push({
|
|
271
|
+
path,
|
|
272
|
+
message: `Rule configuration must be a severity string, number, false, or config object`,
|
|
273
|
+
});
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const ruleConfigObj = ruleConfig as Record<string, unknown>;
|
|
278
|
+
|
|
279
|
+
// Validate config object fields
|
|
280
|
+
const validRuleConfigFields = ['severity', 'options'];
|
|
281
|
+
for (const key of Object.keys(ruleConfigObj)) {
|
|
282
|
+
if (!validRuleConfigFields.includes(key)) {
|
|
283
|
+
errors.push({
|
|
284
|
+
path: `${path}.${key}`,
|
|
285
|
+
message: `Unknown rule config field "${key}"`,
|
|
286
|
+
suggestion: `Allowed fields: ${validRuleConfigFields.join(', ')}`,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Validate severity
|
|
292
|
+
if (ruleConfigObj.severity !== undefined) {
|
|
293
|
+
if (!isValidSeverity(ruleConfigObj.severity)) {
|
|
294
|
+
errors.push({
|
|
295
|
+
path: `${path}.severity`,
|
|
296
|
+
message: `Invalid severity "${ruleConfigObj.severity}"`,
|
|
297
|
+
suggestion: 'Allowed values: "off", "warn", "error", 0, 1, 2',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validate options
|
|
303
|
+
if (ruleConfigObj.options !== undefined) {
|
|
304
|
+
if (typeof ruleConfigObj.options !== 'object' || ruleConfigObj.options === null) {
|
|
305
|
+
errors.push({
|
|
306
|
+
path: `${path}.options`,
|
|
307
|
+
message: 'options must be an object',
|
|
308
|
+
});
|
|
309
|
+
} else {
|
|
310
|
+
// Validate rule-specific options
|
|
311
|
+
const validOptions = RULE_OPTIONS_SCHEMA[ruleId];
|
|
312
|
+
if (validOptions) {
|
|
313
|
+
const optionsObj = ruleConfigObj.options as Record<string, unknown>;
|
|
314
|
+
for (const optionKey of Object.keys(optionsObj)) {
|
|
315
|
+
if (!validOptions.includes(optionKey)) {
|
|
316
|
+
const suggestion = findSimilarString(optionKey, validOptions);
|
|
317
|
+
errors.push({
|
|
318
|
+
path: `${path}.options.${optionKey}`,
|
|
319
|
+
message: `Unknown option "${optionKey}" for rule "${ruleId}"`,
|
|
320
|
+
suggestion: suggestion
|
|
321
|
+
? `Did you mean "${suggestion}"?`
|
|
322
|
+
: `Allowed options: ${validOptions.join(', ')}`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return errors;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if a value is a valid severity
|
|
336
|
+
*/
|
|
337
|
+
function isValidSeverity(value: unknown): value is RuleSeverity {
|
|
338
|
+
return VALID_SEVERITIES.includes(value as RuleSeverity);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Default Config
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get the default VGC configuration
|
|
347
|
+
*/
|
|
348
|
+
export function getDefaultConfig(): PrivuConfig {
|
|
349
|
+
return {
|
|
350
|
+
root: false,
|
|
351
|
+
include: DEFAULT_INCLUDE_PATTERNS,
|
|
352
|
+
exclude: DEFAULT_EXCLUDE_PATTERNS,
|
|
353
|
+
rules: {},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Merge configs with proper precedence (later configs override earlier)
|
|
359
|
+
*/
|
|
360
|
+
export function mergeConfigs(...configs: (PrivuConfig | undefined)[]): PrivuConfig {
|
|
361
|
+
const result = getDefaultConfig();
|
|
362
|
+
|
|
363
|
+
for (const config of configs) {
|
|
364
|
+
if (!config) continue;
|
|
365
|
+
|
|
366
|
+
if (config.root !== undefined) result.root = config.root;
|
|
367
|
+
if (config.library !== undefined) result.library = config.library;
|
|
368
|
+
if (config.include !== undefined) result.include = config.include;
|
|
369
|
+
if (config.exclude !== undefined) result.exclude = config.exclude;
|
|
370
|
+
|
|
371
|
+
// Merge rules
|
|
372
|
+
if (config.rules) {
|
|
373
|
+
result.rules = { ...result.rules, ...config.rules };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Utility Functions
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Find a similar string from a list (for "did you mean" suggestions)
|
|
386
|
+
*/
|
|
387
|
+
function findSimilarString(input: string, candidates: string[]): string | null {
|
|
388
|
+
const inputLower = input.toLowerCase();
|
|
389
|
+
let bestMatch: string | null = null;
|
|
390
|
+
let bestDistance = Infinity;
|
|
391
|
+
|
|
392
|
+
for (const candidate of candidates) {
|
|
393
|
+
const candidateLower = candidate.toLowerCase();
|
|
394
|
+
|
|
395
|
+
// Exact substring match
|
|
396
|
+
if (inputLower.includes(candidateLower) || candidateLower.includes(inputLower)) {
|
|
397
|
+
return candidate;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Calculate Levenshtein distance
|
|
401
|
+
const distance = levenshteinDistance(inputLower, candidateLower);
|
|
402
|
+
|
|
403
|
+
// Accept if distance is small relative to string length
|
|
404
|
+
if (distance <= 3 && distance < bestDistance) {
|
|
405
|
+
bestDistance = distance;
|
|
406
|
+
bestMatch = candidate;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return bestMatch;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Calculate Levenshtein edit distance between two strings
|
|
415
|
+
*/
|
|
416
|
+
function levenshteinDistance(a: string, b: string): number {
|
|
417
|
+
const matrix: number[][] = [];
|
|
418
|
+
|
|
419
|
+
// Initialize matrix
|
|
420
|
+
for (let i = 0; i <= a.length; i++) {
|
|
421
|
+
matrix[i] = [i];
|
|
422
|
+
}
|
|
423
|
+
for (let j = 0; j <= b.length; j++) {
|
|
424
|
+
matrix[0][j] = j;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Fill matrix
|
|
428
|
+
for (let i = 1; i <= a.length; i++) {
|
|
429
|
+
for (let j = 1; j <= b.length; j++) {
|
|
430
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
431
|
+
matrix[i][j] = Math.min(
|
|
432
|
+
matrix[i - 1][j] + 1, // deletion
|
|
433
|
+
matrix[i][j - 1] + 1, // insertion
|
|
434
|
+
matrix[i - 1][j - 1] + cost // substitution
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return matrix[a.length][b.length];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Format config validation errors for display
|
|
444
|
+
*/
|
|
445
|
+
export function formatConfigErrors(errors: ConfigValidationError[]): string {
|
|
446
|
+
const lines: string[] = [];
|
|
447
|
+
|
|
448
|
+
for (const error of errors) {
|
|
449
|
+
lines.push(` error ${error.path || '(root)'}: ${error.message}`);
|
|
450
|
+
if (error.suggestion) {
|
|
451
|
+
lines.push(` → ${error.suggestion}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
lines.push('');
|
|
456
|
+
lines.push(` ✖ ${errors.length} configuration error${errors.length === 1 ? '' : 's'}`);
|
|
457
|
+
|
|
458
|
+
return lines.join('\n');
|
|
459
|
+
}
|