1+ import fs from 'node:fs' ;
2+ import path from 'node:path' ;
3+ import process from 'node:process' ;
4+ import { fileURLToPath , pathToFileURL , URL } from 'node:url' ;
5+
6+ const componentParameters = [
7+ { repository : 'llvm_repo' , commit : 'llvm_commit' } ,
8+ { repository : 'lldb_mi_repo' , commit : 'lldb_mi_commit' }
9+ ] ;
10+
11+ function parseScalarParameter ( template , parameterName ) {
12+ const matches = [ ...template . matchAll ( new RegExp ( `^ ${ parameterName } :\\s*(.*?)\\s*$` , 'gm' ) ) ] ;
13+ if ( matches . length !== 1 ) {
14+ throw new Error ( `Expected ${ parameterName } to appear exactly once in the LLDB-MI template.` ) ;
15+ }
16+
17+ const serializedValue = matches [ 0 ] [ 1 ] ;
18+ if ( serializedValue . startsWith ( '"' ) ) {
19+ const value = JSON . parse ( serializedValue ) ;
20+ if ( typeof value !== 'string' ) {
21+ throw new Error ( `Expected ${ parameterName } to be a string.` ) ;
22+ }
23+ return value ;
24+ }
25+ if ( serializedValue . startsWith ( "'" ) ) {
26+ if ( ! serializedValue . endsWith ( "'" ) ) {
27+ throw new Error ( `Expected ${ parameterName } to be a valid scalar value.` ) ;
28+ }
29+ return serializedValue . slice ( 1 , - 1 ) . replaceAll ( "''" , "'" ) ;
30+ }
31+ if ( ! serializedValue || / \s / . test ( serializedValue ) ) {
32+ throw new Error ( `Expected ${ parameterName } to be a non-empty scalar value.` ) ;
33+ }
34+ return serializedValue ;
35+ }
36+
37+ function normalizeRepositoryUrl ( repositoryUrl ) {
38+ const normalizedUrl = new URL ( repositoryUrl ) ;
39+ normalizedUrl . hash = '' ;
40+ normalizedUrl . search = '' ;
41+ normalizedUrl . pathname = normalizedUrl . pathname . replace ( / \/ + $ / , '' ) . replace ( / \. g i t $ / i, '' ) ;
42+ return normalizedUrl . href . replace ( / \/ $ / , '' ) ;
43+ }
44+
45+ function getGitRegistrations ( manifest ) {
46+ if ( ! Array . isArray ( manifest . registrations ) ) {
47+ throw new Error ( 'Component manifest does not contain a registrations array.' ) ;
48+ }
49+
50+ const registrations = new Map ( ) ;
51+ for ( const registration of manifest . registrations ) {
52+ const component = registration ?. component ;
53+ if ( component ?. type !== 'git' ) {
54+ continue ;
55+ }
56+
57+ const repositoryUrl = component . git ?. repositoryUrl ;
58+ const commitHash = component . git ?. commitHash ;
59+ if ( typeof repositoryUrl !== 'string' || typeof commitHash !== 'string' ) {
60+ throw new Error ( 'Git component registrations require repositoryUrl and commitHash strings.' ) ;
61+ }
62+
63+ const normalizedRepositoryUrl = normalizeRepositoryUrl ( repositoryUrl ) ;
64+ if ( registrations . has ( normalizedRepositoryUrl ) ) {
65+ throw new Error ( `Component manifest contains duplicate registrations for ${ repositoryUrl } .` ) ;
66+ }
67+ registrations . set ( normalizedRepositoryUrl , { repositoryUrl, commitHash } ) ;
68+ }
69+ return registrations ;
70+ }
71+
72+ function validateLldbMiComponentManifest ( template , manifest ) {
73+ const registrations = getGitRegistrations ( manifest ) ;
74+ const errors = [ ] ;
75+
76+ for ( const parameters of componentParameters ) {
77+ const repositoryUrl = parseScalarParameter ( template , parameters . repository ) ;
78+ const commitHash = parseScalarParameter ( template , parameters . commit ) ;
79+ if ( ! / ^ [ 0 - 9 a - f ] { 40 } $ / . test ( commitHash ) ) {
80+ errors . push ( `${ parameters . commit } must be a 40-character lowercase Git commit hash.` ) ;
81+ continue ;
82+ }
83+
84+ const registration = registrations . get ( normalizeRepositoryUrl ( repositoryUrl ) ) ;
85+ if ( ! registration ) {
86+ errors . push ( `${ parameters . repository } references ${ repositoryUrl } , which is missing from the component manifest.` ) ;
87+ } else if ( registration . commitHash !== commitHash ) {
88+ errors . push ( `${ parameters . commit } is ${ commitHash } , but the component manifest registers ${ registration . commitHash } for ${ registration . repositoryUrl } .` ) ;
89+ }
90+ }
91+
92+ if ( errors . length > 0 ) {
93+ throw new Error ( `LLDB-MI component manifest validation failed:\n${ errors . map ( error => ` ${ error } ` ) . join ( '\n' ) } ` ) ;
94+ }
95+ }
96+
97+ const invokedUrl = process . argv [ 1 ] ? pathToFileURL ( path . resolve ( process . argv [ 1 ] ) ) . href : undefined ;
98+ if ( invokedUrl === import . meta. url ) {
99+ const extensionRoot = path . resolve ( path . dirname ( fileURLToPath ( import . meta. url ) ) , '..' ) ;
100+ const manifestPath = process . argv [ 2 ] ?? path . join ( extensionRoot , 'cgmanifest.json' ) ;
101+ const templatePath = process . argv [ 3 ] ?? path . join ( extensionRoot , '..' , 'Build' , 'lldb-mi' , 'lldb-mi.template.yml' ) ;
102+
103+ try {
104+ const manifest = JSON . parse ( fs . readFileSync ( manifestPath , 'utf8' ) ) ;
105+ const template = fs . readFileSync ( templatePath , 'utf8' ) ;
106+ validateLldbMiComponentManifest ( template , manifest ) ;
107+ } catch ( error ) {
108+ process . stderr . write ( `${ error instanceof Error ? error . message : String ( error ) } \n` ) ;
109+ process . exitCode = 1 ;
110+ }
111+ }
112+
113+ export { normalizeRepositoryUrl , parseScalarParameter , validateLldbMiComponentManifest } ;
0 commit comments