@@ -7,64 +7,248 @@ const fs = require('fs').promises;
77const Bundler = require ( "@hyperjump/json-schema-bundle" ) ;
88
99( async function ( ) {
10- bundle ( `https://json-schema.org/draft/2019-09/schema` , 'draft09 ' ) ;
11- bundle ( `https://json-schema.org/draft/2020-12/schema` , 'draft12 ' ) ;
10+ bundle ( `https://json-schema.org/draft/2019-09/schema` , 'draft-2019-09' , 'https://json-schema.org/draft/2019-09 ') ;
11+ bundle ( `https://json-schema.org/draft/2020-12/schema` , 'draft-2020-12' , 'https://json-schema.org/draft/2020-12 ') ;
1212} ( ) ) ;
1313
14- async function bundle ( uri , filename ) {
15- const metaSchema = await Bundler . get ( uri ) ;
16- const bundle = await Bundler . bundle ( metaSchema ) ;
17- const jsonified = JSON . stringify ( bundle , null , 2 ) . replace ( / " u n d e f i n e d " : " " / g, '"$dynamicAnchor": "meta"' ) ;
18- const jsified = 'export default ' + printObject ( JSON . parse ( jsonified ) ) ;
19- fs . writeFile ( `./${ filename } .json` , jsonified , 'utf8' ) ;
20- fs . writeFile ( `./${ filename } .js` , jsified , 'utf8' ) ;
14+ async function bundle ( uri , filename , derivedURL ) {
15+ const metaSchema = await Bundler . get ( uri ) ;
16+ let bundle = await Bundler . bundle ( metaSchema ) ;
17+ bundle = JSON . parse ( JSON . stringify ( bundle , null , 2 ) . replace ( / " u n d e f i n e d " : " " / g, '"$dynamicAnchor": "meta"' ) ) ;
18+ fs . writeFile ( `./${ filename } .json` , JSON . stringify ( bundle , null , 2 ) , 'utf8' ) ;
19+ bundle = flattenDraftMetaSchema ( bundle ) ;
20+ const jsified = getCopyright ( derivedURL ) + 'export default ' + printObject ( bundle ) ;
21+ fs . writeFile ( `./${ filename } -flat.json` , JSON . stringify ( bundle , null , 2 ) , 'utf8' ) ;
22+ fs . writeFile ( `./src/services/schemas/${ filename } -flat.ts` , jsified , 'utf8' ) ;
2123}
24+ function getCopyright ( derivedURL ) {
25+ return [
26+ '/*---------------------------------------------------------------------------------------------' ,
27+ ' * Copyright (c) Microsoft Corporation. All rights reserved.' ,
28+ ' * Licensed under the MIT License. See License.txt in the project root for license information.' ,
29+ ' *--------------------------------------------------------------------------------------------*/' ,
30+ '' ,
31+ '// This file is generated - do not edit directly!' ,
32+ '// Derived from ' + derivedURL ,
33+ ] . join ( '\n' ) + '\n\n' ;
34+ }
35+
2236
2337function printLiteral ( value ) {
24- if ( typeof value === 'string' ) {
25- return `'${ value } '` ;
26- }
27- return value ;
38+ if ( typeof value === 'string' ) {
39+ return `'${ value } '` ;
40+ }
41+ return value ;
2842}
2943
3044function printKey ( value ) {
31- if ( value . match ( / ^ [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * $ / ) ) {
32- return `${ value } ` ;
33- }
34- return `'${ value } '` ;
45+ if ( value . match ( / ^ [ a - z A - Z _ $ ] [ a - z A - Z 0 - 9 _ $ ] * $ / ) ) {
46+ return `${ value } ` ;
47+ }
48+ return `'${ value } '` ;
3549}
3650
3751function indent ( level ) {
38- return '\t' . repeat ( level ) ;
52+ return '\t' . repeat ( level ) ;
3953}
4054
4155function printObject ( obj , indentLevel = 0 ) {
42- const result = [ ] ;
43- if ( Array . isArray ( obj ) ) {
44- result . push ( `[` ) ;
45- for ( const item of obj ) {
46- if ( typeof item === 'object' && item !== null ) {
47- result . push ( `${ indent ( indentLevel + 1 ) } ${ printObject ( item , indentLevel + 1 ) } ,` ) ;
48- } else {
49- result . push ( `${ indent ( indentLevel + 1 ) } ${ printLiteral ( item ) } ,` ) ;
50- }
51- }
52- result . push ( `${ indent ( indentLevel ) } ]` ) ;
53- return result . join ( '\n' ) ;
54- }
55- if ( obj === null ) {
56- result . push ( `null` ) ;
57- return result . join ( '\n' ) ;
58- }
59-
60- result . push ( `{` ) ;
61- for ( const [ key , value ] of Object . entries ( obj ) ) {
62- if ( typeof value === 'object' && value !== null ) {
63- result . push ( `${ indent ( indentLevel + 1 ) } ${ printKey ( key ) } : ${ printObject ( value , indentLevel + 1 ) } ,` ) ;
64- } else {
65- result . push ( `${ indent ( indentLevel + 1 ) } ${ printKey ( key ) } : ${ printLiteral ( value ) } ,` ) ;
66- }
67- }
68- result . push ( `${ indent ( indentLevel ) } }` ) ;
69- return result . join ( '\n' ) ;
56+ const result = [ ] ;
57+ if ( Array . isArray ( obj ) ) {
58+ result . push ( `[` ) ;
59+ for ( const item of obj ) {
60+ if ( typeof item === 'object' && item !== null ) {
61+ result . push ( `${ indent ( indentLevel + 1 ) } ${ printObject ( item , indentLevel + 1 ) } ,` ) ;
62+ } else {
63+ result . push ( `${ indent ( indentLevel + 1 ) } ${ printLiteral ( item ) } ,` ) ;
64+ }
65+ }
66+ result . push ( `${ indent ( indentLevel ) } ]` ) ;
67+ return result . join ( '\n' ) ;
68+ }
69+ if ( obj === null ) {
70+ result . push ( `null` ) ;
71+ return result . join ( '\n' ) ;
72+ }
73+
74+ result . push ( `{` ) ;
75+ for ( const [ key , value ] of Object . entries ( obj ) ) {
76+ if ( typeof value === 'object' && value !== null ) {
77+ result . push ( `${ indent ( indentLevel + 1 ) } ${ printKey ( key ) } : ${ printObject ( value , indentLevel + 1 ) } ,` ) ;
78+ } else {
79+ result . push ( `${ indent ( indentLevel + 1 ) } ${ printKey ( key ) } : ${ printLiteral ( value ) } ,` ) ;
80+ }
81+ }
82+ result . push ( `${ indent ( indentLevel ) } }` ) ;
83+ return result . join ( '\n' ) ;
84+ }
85+ // flatten
86+
87+ const DEFAULT_ANCHOR = 'meta' ;
88+
89+ function visit ( node , callback ) {
90+ if ( ! node || typeof node !== 'object' ) return ;
91+ if ( Array . isArray ( node ) ) {
92+ for ( const item of node ) {
93+ visit ( item , callback ) ;
94+ }
95+ return ;
96+ }
97+
98+ for ( const key of Object . keys ( node ) ) {
99+ callback ( node , key ) ;
100+ visit ( node [ key ] , callback ) ;
101+ }
102+ }
103+
104+ /** Recursively replace $dynamicRef:#meta with $ref:#meta */
105+ function replaceDynamicRefs ( node , anchorName = DEFAULT_ANCHOR ) {
106+ visit ( node , ( n , k ) => {
107+ const v = n [ k ] ;
108+ if ( k === '$dynamicRef' && v === '#' + anchorName ) {
109+ n [ '$ref' ] = '#' ;
110+ delete n [ '$dynamicRef' ] ;
111+ } ;
112+ } ) ;
113+ }
114+
115+ /** Recursively replace $dynamicRef:#meta with $ref:#meta */
116+ function replaceRecursiveRefs ( node , anchorName = DEFAULT_ANCHOR ) {
117+ visit ( node , ( n , k ) => {
118+ const v = n [ k ] ;
119+ if ( k === '$recursiveRef' ) {
120+ n [ '$ref' ] = v ;
121+ delete n [ '$recursiveRef' ] ;
122+ } ;
123+ } ) ;
124+ }
125+
126+ /** Replace refs that point to a vocabulary */
127+ function replaceOldRefs ( node , anchorName = DEFAULT_ANCHOR ) {
128+ visit ( node , ( n , k ) => {
129+ const v = n [ k ] ;
130+ if ( k === '$ref' && typeof v === 'string' && v . startsWith ( anchorName + '/' ) ) {
131+ const segments = v . split ( '#' ) ;
132+ if ( segments . length === 2 ) {
133+ n [ '$ref' ] = `#${ segments [ 1 ] } ` ;
134+ }
135+ }
136+ } ) ;
137+ }
138+
139+ /** Remove all $dynamicAnchor occurrences (except keep keyword definition property) */
140+ function stripDynamicAnchors ( node ) {
141+ visit ( node , ( n , k ) => {
142+ if ( k === '$dynamicAnchor' ) {
143+ delete n [ k ] ;
144+ }
145+ } ) ;
146+ }
147+
148+ /** Collect vocabulary object definitions from $defs */
149+ function collectVocabularies ( schema ) {
150+ const vocabularies = [ ] ;
151+ const defs = schema . $defs || { } ;
152+ for ( const [ key , value ] of Object . entries ( defs ) ) {
153+ if ( value && typeof value === 'object' && ! Array . isArray ( value ) && value . $id && value . $dynamicAnchor === DEFAULT_ANCHOR && value . properties ) {
154+ vocabularies . push ( value ) ;
155+ }
156+ }
157+ return vocabularies ;
158+ }
159+
160+ /** Merge properties from each vocabulary into root.properties (shallow) */
161+ function mergeVocabularyProperties ( root , vocabularies ) {
162+ if ( ! root . properties ) root . properties = { } ;
163+ replaceOldRefs ( root ) ;
164+ for ( const vocab of vocabularies ) {
165+ for ( const [ propName , propSchema ] of Object . entries ( vocab . properties || { } ) ) {
166+ if ( ! ( propName in root . properties ) ) {
167+ root . properties [ propName ] = propSchema ;
168+ } else {
169+ // Simple heuristic: if both are objects, attempt shallow merge, else keep existing
170+ const existing = root . properties [ propName ] ;
171+ if ( isPlainObject ( existing ) && isPlainObject ( propSchema ) ) {
172+ root . properties [ propName ] = { ...existing , ...propSchema } ;
173+ }
174+ }
175+ }
176+ }
177+ }
178+
179+ function isPlainObject ( o ) {
180+ return ! ! o && typeof o === 'object' && ! Array . isArray ( o ) ;
181+ }
182+
183+ /** Gather unified $defs from vocab $defs (only specific keys) */
184+ function buildUnifiedDefs ( schema , vocabularies ) {
185+ const unified = schema . $defs && ! referencesVocabulary ( schema . $defs ) ? { ...schema . $defs } : { } ;
186+
187+ function harvest ( defsObj ) {
188+ if ( ! defsObj || typeof defsObj !== 'object' ) return ;
189+ for ( const [ k , v ] of Object . entries ( defsObj ) ) {
190+ if ( ! ( k in unified ) ) {
191+ unified [ k ] = v ;
192+ } else {
193+ console . warn ( `Warning: duplicate definition for key ${ k } found while building unified $defs. Keeping the first occurrence.` ) ;
194+ }
195+ }
196+ }
197+
198+ for ( const vocab of vocabularies ) harvest ( vocab . $defs ) ;
199+
200+ // Adjust schemaArray items dynamicRef->ref later with global replacement
201+ return unified ;
202+ }
203+
204+ function referencesVocabulary ( defs ) {
205+ return Object . keys ( defs ) . some ( k => k . startsWith ( 'https://json-schema.org/draft/' ) ) ;
206+ }
207+
208+ function flattenDraftMetaSchema ( original ) {
209+ // Clone to avoid mutating input reference
210+ const schema = JSON . parse ( JSON . stringify ( original ) ) ;
211+
212+ const anchorName = schema . $dynamicAnchor || DEFAULT_ANCHOR ;
213+
214+ // 1. Collect vocabulary schemas
215+ const vocabularies = collectVocabularies ( schema ) ;
216+
217+ // 2. Merge vocabulary properties into root
218+ mergeVocabularyProperties ( schema , vocabularies ) ;
219+
220+ // 3. Build unified $defs
221+ const unifiedDefs = buildUnifiedDefs ( schema , vocabularies ) ;
222+
223+ // 4. Remove top-level allOf (flatten composition)
224+ delete schema . allOf ;
225+
226+ // 5. Remove vocabulary objects from $defs
227+ if ( schema . $defs ) {
228+ for ( const k of Object . keys ( schema . $defs ) ) {
229+ if ( schema . $defs [ k ] && schema . $defs [ k ] . $id && schema . $defs [ k ] . $dynamicAnchor === anchorName ) {
230+ delete schema . $defs [ k ] ;
231+ }
232+ }
233+ }
234+
235+ // 6. Assign unified defs
236+ schema . $defs = unifiedDefs ;
237+
238+ // 7. Convert dynamic recursion markers
239+ replaceDynamicRefs ( schema , anchorName ) ;
240+ replaceRecursiveRefs ( schema , anchorName ) ;
241+ stripDynamicAnchors ( schema ) ;
242+
243+ // 8. Add static anchor at root
244+ delete schema . $dynamicAnchor ;
245+
246+ // 9. Update title to signal flattening
247+ if ( schema . title ) {
248+ schema . title = '(Flattened static) ' + schema . title ;
249+ } else {
250+ schema . title = 'Flattened Draft 2020-12 meta-schema' ;
251+ }
252+
253+ return schema ;
70254}
0 commit comments