1- import { fileURLToPath } from 'url' ;
21import { existsSync , mkdirSync , readdirSync , readFileSync , rmSync , writeFileSync } from 'fs' ;
32import { basename , dirname , join , normalize , relative } from 'path' ;
3+ import { fileURLToPath } from 'url' ;
44
55const root = fileURLToPath ( new URL ( '../' , import . meta. url ) ) ;
66const documentation = JSON . parse ( readFileSync ( join ( root , `/src/docs/documentation.json` ) , 'utf8' ) ) ;
77const modulesWithLegacySubmodules = [ 'checkbox' , 'link-list' , 'radio-button' ] ;
8+ const ignoredFolders = [ 'core' ] ;
89
910/**
10- * The configuration object used to merge different `api` files in a single one for the angular project.
11- */
12- const mergeConfigAngular = {
13- alert : [ 'alert' , 'alert-group' ] ,
14- breadcrumb : [ 'breadcrumb' , 'breadcrumb-group' ] ,
15- button : [
16- 'button' ,
17- 'button-link' ,
18- 'button-static' ,
19- 'accent-button' ,
20- 'accent-button-link' ,
21- 'accent-button-static' ,
22- 'secondary-button' ,
23- 'secondary-button-link' ,
24- 'secondary-button-static' ,
25- 'transparent-button' ,
26- 'transparent-button-link' ,
27- 'transparent-button-static' ,
28- 'mini-button' ,
29- 'mini-button-group' ,
30- ] ,
31- card : [ 'card' , 'card-button' , 'card-link' , 'card-badge' ] ,
32- carousel : [ 'carousel' , 'carousel-list' , 'carousel-item' ] ,
33- chip : [ 'chip' , 'chip-group' ] ,
34- container : [ 'container' , 'sticky-bar' ] ,
35- datepicker : [ 'datepicker' , 'datepicker-toggle' , 'datepicker-previous-day' , 'datepicker-next-day' ] ,
36- dialog : [ 'dialog' , 'dialog-title' , 'dialog-content' , 'dialog-actions' , 'dialog-close-button' ] ,
37- 'expansion-panel' : [ 'expansion-panel' , 'expansion-panel-header' , 'expansion-panel-content' ] ,
38- 'file-selector' : [ 'file-selector' , 'file-selector-dropzone' ] ,
39- 'flip-card' : [ 'flip-card' , 'flip-card-summary' , 'flip-card-details' ] ,
40- 'form-field' : [ 'form-field' , 'form-field-clear' , 'error' ] ,
41- header : [ 'header' , 'header-button' , 'header-link' , 'header-environment' ] ,
42- link : [
43- 'link' ,
44- 'link-button' ,
45- 'link-static' ,
46- 'block-link' ,
47- 'block-link-button' ,
48- 'block-link-static' ,
49- ] ,
50- menu : [ 'menu' , 'menu-button' , 'menu-link' ] ,
51- 'mini-calendar' : [ 'mini-calendar' , 'mini-calendar-month' , 'mini-calendar-day' ] ,
52- navigation : [
53- 'navigation' ,
54- 'navigation-section' ,
55- 'navigation-list' ,
56- 'navigation-marker' ,
57- 'navigation-link' ,
58- 'navigation-button' ,
59- ] ,
60- option : [ 'option' , 'optgroup' , 'option-hint' ] ,
61- paginator : [ 'paginator' , 'compact-paginator' ] ,
62- sidebar : [
63- 'sidebar' ,
64- 'sidebar-container' ,
65- 'sidebar-content' ,
66- 'sidebar-title' ,
67- 'sidebar-close-button' ,
68- 'icon-sidebar' ,
69- 'icon-sidebar-container' ,
70- 'icon-sidebar-content' ,
71- 'icon-sidebar-button' ,
72- 'icon-sidebar-link' ,
73- ] ,
74- stepper : [ 'stepper' , 'step' , 'step-label' ] ,
75- table : [ 'table' , 'table-wrapper' , 'sort' ] ,
76- tab : [ 'tab' , 'tab-group' , 'tab-label' ] ,
77- tag : [ 'tag' , 'tag-group' ] ,
78- teaser : [ 'teaser' , 'teaser-hero' , 'teaser-product' , 'teaser-product-static' ] ,
79- 'timetable-form' : [
80- 'timetable-form' ,
81- 'timetable-form-field' ,
82- 'timetable-form-details' ,
83- 'timetable-form-swap-button' ,
84- ] ,
85- timetable : [
86- 'train-formation' ,
87- 'train' ,
88- 'train-wagon' ,
89- 'train-blocked-passage' ,
90- 'timetable-occupancy' ,
91- 'timetable-occupancy-icon' ,
92- ] ,
93- toggle : [ 'toggle' , 'toggle-option' , 'toggle-check' ] ,
94- } ;
95-
96- /**
97- * The configuration object used to merge different `api` files in a single one for the angular-experimental project.
11+ * Reads the module names for a given package from meta.ts.
12+ * For each module listed in PACKAGES (from meta.ts), all api files are merged into a single file.
9813 */
99- const mergeConfigAngularExperimental = {
100- 'autocomplete-grid' : [
101- 'autocomplete-grid' ,
102- 'autocomplete-grid-row' ,
103- 'autocomplete-grid-optgroup' ,
104- 'autocomplete-grid-option' ,
105- 'autocomplete-grid-cell' ,
106- 'autocomplete-grid-button' ,
107- ] ,
108- 'seat-reservation' : [
109- 'seat-reservation-area' ,
110- 'seat-reservation-graphic' ,
111- 'seat-reservation-navigation-coach' ,
112- 'seat-reservation-navigation-services' ,
113- 'seat-reservation-place-control' ,
114- 'seat-reservation-scoped' ,
115- ] ,
116- } ;
117-
118- const mergeConfig : Record < string , Record < string , string [ ] > > = {
119- angular : mergeConfigAngular ,
120- 'angular-experimental' : mergeConfigAngularExperimental ,
14+ const getModuleNamesFromMeta = async ( projectFolder : string ) : Promise < string [ ] > => {
15+ const { PACKAGES } = await import ( '../src/docs/app/shared/meta.js' ) ;
16+ const pkg = PACKAGES [ projectFolder ] ;
17+ if ( ! pkg ) {
18+ return [ ] ;
19+ }
20+ return pkg . sections
21+ . flatMap ( ( s ) => s . entries )
22+ . map ( ( e ) => e . link . split ( '/' ) . at ( - 1 ) ! )
23+ . filter ( Boolean ) ;
12124} ;
12225
12326/**
@@ -130,68 +33,93 @@ const mergeConfig: Record<string, Record<string, string[]>> = {
13033 *
13134 * @param projectFolder the name of the package (angular / angular-experimental / ...)
13235 */
133- const generateApiFiles = ( projectFolder : string ) => {
36+ const generateApiFiles = async ( projectFolder : string ) => {
13437 const projectPath = join ( root , 'src' , projectFolder ) ;
13538 const outputPath = join ( root , 'src/docs/app' , projectFolder , 'api' ) ;
13639 if ( existsSync ( outputPath ) ) {
13740 rmSync ( outputPath , { recursive : true , force : true } ) ;
13841 }
13942 mkdirSync ( outputPath , { recursive : true } ) ;
140- scanFoldersAndWriteFiles ( projectPath , outputPath ) ;
141- mergeFilesInModule ( outputPath , mergeConfig [ projectFolder ] ) ;
43+ await mergeFilesInModule ( projectPath , outputPath , projectFolder ) ;
14244} ;
14345
14446/**
14547 * Recursive function which reaches the innermost folder and creates a `.md` file
14648 * with the documentation of the objects from compodoc that matches the final path.
49+ * Returns the list of generated api file names.
14750 *
14851 * @param projectPath path of the source package
14952 * @param apiFolder path of the output folder
15053 */
151- const scanFoldersAndWriteFiles = ( projectPath : string , apiFolder : string ) => {
54+ const scanFoldersAndWriteFiles = ( projectPath : string , apiFolder : string ) : string [ ] => {
15255 const folders = readdirSync ( projectPath , { withFileTypes : true } ) ;
15356 const subFolders = folders . filter ( ( e ) => e . isDirectory ( ) ) ;
154-
155- // Inner folder reached
156- if (
157- subFolders . length === 0 ||
158- modulesWithLegacySubmodules . some ( ( m ) => projectPath . endsWith ( `/${ m } ` ) )
159- ) {
160- // Scan the documentation file and possibly create the docs file.
57+ const currentName = basename ( projectPath ) ;
58+ const isLegacy = modulesWithLegacySubmodules . some ( ( m ) => projectPath . endsWith ( `/${ m } ` ) ) ;
59+ const hasSameNamedSubFolder = subFolders . some ( ( e ) => e . name === currentName ) ;
60+ const generated : string [ ] = [ ] ;
61+
62+ // Scan the current folder unless it has a same-named sub-folder whose docs would
63+ // duplicate it – except for legacy modules which always own their own docs directly.
64+ if ( isLegacy || ! hasSameNamedSubFolder ) {
16165 const readmeContent = createReadmeAPI ( relative ( root , normalize ( projectPath ) ) ) ;
16266 if ( readmeContent ) {
163- const apiFileName = `${ basename ( projectPath ) } -api.md` ;
164- const outPath = join ( apiFolder , apiFileName ) ;
165- writeFileSync ( outPath , readmeContent , { encoding : 'utf-8' , flag : 'a' } ) ;
67+ const apiFileName = `${ currentName } -api.md` ;
68+ writeFileSync ( join ( apiFolder , apiFileName ) , readmeContent , { encoding : 'utf-8' , flag : 'a' } ) ;
69+ generated . push ( apiFileName ) ;
16670 }
167- return ;
16871 }
16972
170- // Inner folder not reached, go deeper recursively
73+ // Stop recursion at leaf folders or legacy submodule roots
74+ if ( subFolders . length === 0 || isLegacy ) {
75+ return generated ;
76+ }
77+
78+ // Go deeper recursively into all sub-folders, skipping ignored ones
17179 for ( const sub of subFolders ) {
172- const subPath = join ( projectPath , sub . name ) ;
173- scanFoldersAndWriteFiles ( subPath , apiFolder ) ;
80+ if ( ! ignoredFolders . includes ( sub . name ) ) {
81+ generated . push ( ...scanFoldersAndWriteFiles ( join ( projectPath , sub . name ) , apiFolder ) ) ;
82+ }
17483 }
84+
85+ return generated ;
17586} ;
17687
17788/**
178- * Based on the provided `config` object, it creates a single file from several ones.
179- * The config's values are mapped as `<config.value[i]>-api.md` files,
180- * then these files are read and joined as a single file, named as `<config.key>-api.md`.
181- * @param path the project path
182- * @param config the key-values object used to generate the file
89+ * For each module listed in meta.ts for the given package, merges all generated
90+ * `*-api.md` files that belong to that module's folder into a single `<module>-api.md`.
91+ *
92+ * The belonging files are determined by re-scanning `src/<projectFolder>/<moduleName>/`
93+ * – which is identical to what scanFoldersAndWriteFiles already did – so we reuse it
94+ * in dry-run mode (no apiFolder writing needed, we only need the file names).
18395 */
184- const mergeFilesInModule = ( path : string , config : Record < string , string [ ] > ) : void => {
185- Object . entries ( config ) . forEach ( ( [ mainFile , subFiles ] : [ string , string [ ] ] ) => {
186- let outputDoc = '' ;
187- subFiles
188- . map ( ( fileName ) => join ( path , `${ fileName } -api.md` ) )
189- . forEach ( ( file ) => {
190- outputDoc += readFileSync ( file , 'utf8' ) ;
191- rmSync ( file , { force : true } ) ;
192- writeFileSync ( join ( path , `${ mainFile } -api.md` ) , outputDoc , { encoding : 'utf-8' } ) ;
193- } ) ;
194- } ) ;
96+ const mergeFilesInModule = async (
97+ projectPath : string ,
98+ outputPath : string ,
99+ projectFolder : string ,
100+ ) : Promise < void > => {
101+ const moduleNames = await getModuleNamesFromMeta ( projectFolder ) ;
102+
103+ for ( const moduleName of moduleNames ) {
104+ const moduleDir = join ( projectPath , moduleName ) ;
105+ if ( ! existsSync ( moduleDir ) ) {
106+ continue ;
107+ }
108+
109+ // Scan this module's folder – writes the individual api files and returns their names
110+ const belongingFiles = scanFoldersAndWriteFiles ( moduleDir , outputPath ) ;
111+
112+ if ( belongingFiles . length <= 1 ) {
113+ continue ; // nothing to merge
114+ }
115+
116+ const outputDoc = belongingFiles . map ( ( f ) => readFileSync ( join ( outputPath , f ) , 'utf8' ) ) . join ( '' ) ;
117+
118+ for ( const f of belongingFiles ) {
119+ rmSync ( join ( outputPath , f ) , { force : true } ) ;
120+ }
121+ writeFileSync ( join ( outputPath , `${ moduleName } -api.md` ) , outputDoc , { encoding : 'utf-8' } ) ;
122+ }
195123} ;
196124
197125/**
@@ -419,5 +347,5 @@ const createParametersForTable = (args: any[]): string => {
419347 return '-' ;
420348} ;
421349
422- generateApiFiles ( 'angular' ) ;
423- generateApiFiles ( 'angular-experimental' ) ;
350+ await generateApiFiles ( 'angular' ) ;
351+ await generateApiFiles ( 'angular-experimental' ) ;
0 commit comments