1+ import fs from 'node:fs' ;
2+ import {
3+ bundleAndLoadRuleset
4+ } from '@stoplight/spectral-ruleset-bundler/with-loader' ;
5+ import { EntryType } from './collector.js' ;
6+
7+ export function loadOpenAPIFile ( filePath ) {
8+ try {
9+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
10+ return JSON . parse ( content ) ;
11+ } catch ( error ) {
12+ throw new Error ( `Failed to load OpenAPI file: ${ error . message } ` ) ;
13+ }
14+ }
15+
16+ export function getSeverityPerRule ( ruleset ) {
17+ const rules = ruleset . rules || { } ;
18+ const map = { } ;
19+ for ( const [ name , ruleObject ] of Object . entries ( rules ) ) {
20+ map [ name ] = ruleObject . definition . severity ;
21+ }
22+ return map ;
23+ }
24+
25+ export async function loadRuleset ( rulesetPath , spectral ) {
26+ try {
27+ const ruleset = await bundleAndLoadRuleset ( rulesetPath , { fs, fetch } ) ;
28+ await spectral . setRuleset ( ruleset ) ;
29+ return ruleset ;
30+ } catch ( error ) {
31+ throw new Error ( `Failed to load ruleset: ${ error . message } ` ) ;
32+ }
33+ }
34+
35+ export function extractTeamOwnership ( oasContent ) {
36+ const ownerTeams = { } ;
37+ const paths = oasContent . paths || { } ;
38+
39+ for ( const [ path , pathItem ] of Object . entries ( paths ) ) {
40+ for ( const [ , operation ] of Object . entries ( pathItem ) ) {
41+ const ownerTeam = operation [ 'x-xgen-owner-team' ] ;
42+
43+ if ( ownerTeam ) {
44+ if ( ! ownerTeams [ path ] ) {
45+ ownerTeams [ path ] = ownerTeam ;
46+ } else if ( ownerTeams [ path ] !== ownerTeam ) {
47+ console . warn ( `Conflict on path ${ path } : ${ ownerTeams [ path ] } vs ${ ownerTeam } ` ) ;
48+ }
49+ }
50+ }
51+ }
52+
53+ return ownerTeams ;
54+ }
55+
56+ export function loadCollectorResults ( collectorResultsFilePath ) {
57+ try {
58+ const content = fs . readFileSync ( collectorResultsFilePath , 'utf8' ) ;
59+ const contentParsed = JSON . parse ( content ) ;
60+ return {
61+ [ EntryType . VIOLATION ] : contentParsed [ EntryType . VIOLATION ] ,
62+ [ EntryType . ADOPTION ] : contentParsed [ EntryType . ADOPTION ] ,
63+ [ EntryType . EXCEPTION ] : contentParsed [ EntryType . EXCEPTION ] ,
64+ } ;
65+ } catch ( error ) {
66+ throw new Error ( `Failed to load Collector Results file: ${ error . message } ` ) ;
67+ }
68+ }
69+
70+ function getIPAFromIPARule ( ipaRule ) {
71+ const pattern = / I P A - \d { 3 } / ;
72+ const match = ipaRule . match ( pattern ) ;
73+ if ( match ) {
74+ return match [ 0 ] ;
75+ }
76+ }
77+
78+ export function merge ( spectralResults , ownershipData , collectorResults , ruleSeverityMap ) {
79+ const results = [ ] ;
80+
81+ function addEntry ( entryType , adoptionStatus ) {
82+ for ( const entry of collectorResults [ entryType ] ) {
83+ const existing = results . find ( ( result ) =>
84+ result . component_id === entry . componentId && result . ipa_rule === entry . ruleName
85+ ) ;
86+
87+ if ( existing ) {
88+ console . warn ( 'Duplicate entries found' , existing ) ;
89+ continue ;
90+ }
91+
92+ results . push ( {
93+ component_id : entry . componentId ,
94+ ipa_rule : entry . ruleName ,
95+ ipa : getIPAFromIPARule ( entry . ruleName ) ,
96+ severity_level : ruleSeverityMap [ entry . ruleName ] ,
97+ adoption_status : adoptionStatus ,
98+ exception_reason : entryType === EntryType . EXCEPTION ? entry . exceptionReason : null ,
99+ owner_team : entry . ownerTeam || null ,
100+ timestamp : new Date ( ) . toISOString ( ) ,
101+ } ) ;
102+ }
103+ }
104+
105+ addEntry ( EntryType . VIOLATION , 'violated' ) ;
106+ addEntry ( EntryType . ADOPTION , 'adopted' ) ;
107+ addEntry ( EntryType . EXCEPTION , 'exempted' ) ;
108+
109+ return results ;
110+ }
0 commit comments