Setting the file. One moment.
List Components · Expo UI · expo/skills · Skills Docs
ContentsBack to the top of the page scripts/list-components.js
scripts/ list-components.js
JavaScript · 193 lines · 7 KB
const
fs
=
require
(
'fs'
);
17 const path = require ( 'path' );
18
19 const projectPath = process.argv[ 2 ];
20 const withDocs = process.argv. includes ( '--docs' );
21
22 if ( ! projectPath) {
23 console. error ( 'Usage: node list-components.js <project-path> [--docs]' );
24 process. exit ( 1 );
25 }
26
27 const pkgRoot = path. join (projectPath, 'node_modules' , '@expo' , 'ui' );
28 if ( ! fs. existsSync (pkgRoot)) {
29 console. error ( `@expo/ui not found in ${ projectPath }/node_modules` );
30 process. exit ( 1 );
31 }
32
33 // Read installed version from package.json
34 let version = 'unknown' ;
35 try {
36 const pkg = JSON . parse (fs. readFileSync (path. join (pkgRoot, 'package.json' ), 'utf8' ));
37 version = pkg.version || 'unknown' ;
38 } catch (_) {}
39
40 // ---------------------------------------------------------------------------
41 // Component extraction — parse `export * from './Name'` in an index.d.ts
42 // ---------------------------------------------------------------------------
43 // TypeScript type names that are not components — skip anything matching these suffixes
44 const TYPE_SUFFIX = /(?:Props | Ref | Handle | Params | Config | Options | Type | Types | Value | Values | Colors | Style | Styles | Event | Events | Alignment | Animation | Spec) $ / ;
45
46 function extractComponents ( indexFile ) {
47 if ( ! fs. existsSync (indexFile)) return [];
48 const src = fs. readFileSync (indexFile, 'utf8' );
49 const names = [];
50 for ( const line of src. split ( ' \n ' )) {
51 // export * from './ComponentName' or export * from './ComponentName/index'
52 const m = line. match ( / ^ export \* from ['"] \.\/ ( [ ^ /'"] + )/ );
53 if (m) {
54 const name = m[ 1 ];
55 // Skip non-component re-exports (types, utils, state internals)
56 if ( / ^ (types | utils | index | State | hooks | colors | layout-types | MaterialSymbols)/ . test (name)) continue ;
57 if ( TYPE_SUFFIX . test (name)) continue ;
58 names. push (name);
59 }
60 // export { Name, ... } from './Something' — pick up named re-exports too
61 const n = line. match ( / ^ export \{ ( [ ^ }] + ) \} / );
62 if (n) {
63 for ( const part of n[ 1 ]. split ( ',' )) {
64 // Skip `type Foo` re-exports
65 if ( / ^ \s * type \s / . test (part)) continue ;
66 const id = part. trim (). split ( / \s + as \s + / )[ 0 ]. trim ();
67 if (id && / ^ [A-Z] / . test (id) && ! TYPE_SUFFIX . test (id)) names. push (id);
68 }
69 }
70 }
71 return [ ...new Set (names)]. sort ();
72 }
73
74 // ---------------------------------------------------------------------------
75 // Modifier extraction — parse `export declare const/function <name>` in
76 // a flat modifiers/index.d.ts, optionally with the preceding JSDoc summary.
77 // ---------------------------------------------------------------------------
78 function extractModifiers ( modifiersFile ) {
79 if ( ! fs. existsSync (modifiersFile)) return [];
80 const src = fs. readFileSync (modifiersFile, 'utf8' );
81 const lines = src. split ( ' \n ' );
82 const results = [];
83 const seen = new Set ();
84
85 for ( let i = 0 ; i < lines. length ; i ++ ) {
86 const line = lines[i];
87 const m = line. match ( / ^ export declare (?:const | function) ( [a-zA-Z_][a-zA-Z0-9_] * )/ );
88 if ( ! m) continue ;
89 const name = m[ 1 ];
90 // Skip type-only helpers and internal symbols
91 if ( / ^ (is | filter | create | type | export)/ . test (name) && name !== 'frame' ) continue ;
92 if (seen. has (name)) continue ;
93 seen. add (name);
94
95 if ( ! withDocs) {
96 results. push ({ name });
97 continue ;
98 }
99
100 // Find /** that opens the JSDoc block immediately preceding this export,
101 // then scan forward through the block for the first plain-prose summary.
102 let jsdocStart = - 1 ;
103 for ( let j = i - 1 ; j >= 0 ; j -- ) {
104 const jl = lines[j]. trim ();
105 if (jl === '/**' ) { jsdocStart = j; break ; }
106 if (jl !== '' && jl !== '*/' && ! jl. startsWith ( '*' )) break ;
107 }
108
109 let summary = '' ;
110 let deprecated = false ;
111 if (jsdocStart >= 0 ) {
112 let inCodeBlock = false ;
113 for ( let k = jsdocStart + 1 ; k < i; k ++ ) {
114 const kl = lines[k]. trim ();
115 if (kl. startsWith ( '* ```' )) { inCodeBlock = ! inCodeBlock; continue ; }
116 if (inCodeBlock) continue ;
117 // Check @deprecated anywhere in the block (may appear after @param)
118 if (kl. startsWith ( '* @deprecated' )) { deprecated = true ; continue ; }
119 // Extract summary from opening prose only — stop at first non-deprecated tag
120 if ( ! summary) {
121 if (kl. startsWith ( '* @' )) continue ; // skip tags while looking for prose
122 if (kl === '*/' || kl === '/**' || kl === '*' ) continue ;
123 if (kl. startsWith ( '* ' )) {
124 const text = kl. slice ( 2 ). trim ();
125 if ( ! text. startsWith ( '-' ) && ! text. startsWith ( '<' )) {
126 summary = text. replace ( / \. $ / , '' );
127 }
128 }
129 }
130 }
131 }
132 results. push ({ name, summary, deprecated });
133 }
134
135 return results;
136 }
137
138 // ---------------------------------------------------------------------------
139 // Format helpers
140 // ---------------------------------------------------------------------------
141 function formatNames ( names ) {
142 return names. join ( ', ' );
143 }
144
145 function formatModifiers ( mods ) {
146 if ( ! withDocs) return mods. map ( m => m.name). join ( ', ' );
147 const lines = [];
148 for ( const m of mods) {
149 const dep = m.deprecated ? ' [deprecated]' : '' ;
150 const desc = m.summary ? ` — ${ m . summary }` : '' ;
151 lines. push ( ` ${ m . name }${ dep }${ desc }` );
152 }
153 return lines. join ( ' \n ' );
154 }
155
156 // ---------------------------------------------------------------------------
157 // Main
158 // ---------------------------------------------------------------------------
159 const buildRoot = path. join (pkgRoot, 'build' );
160
161 // Universal
162 const universalComponents = extractComponents (path. join (buildRoot, 'universal' , 'index.d.ts' ));
163
164 // Swift-UI (iOS only)
165 const swiftuiComponents = extractComponents (path. join (buildRoot, 'swift-ui' , 'index.d.ts' ));
166 const swiftuiModifiers = extractModifiers (path. join (buildRoot, 'swift-ui' , 'modifiers' , 'index.d.ts' ));
167
168 // Jetpack Compose (Android only)
169 const composeComponents = extractComponents (path. join (buildRoot, 'jetpack-compose' , 'index.d.ts' ));
170 const composeModifiers = extractModifiers (path. join (buildRoot, 'jetpack-compose' , 'modifiers' , 'index.d.ts' ));
171
172 const docsNote = withDocs ? ' (with descriptions)' : ' (names only — run with --docs for descriptions)' ;
173 console. log ( `@expo/ui ${ version }${ docsNote } \n ` );
174
175 console. log ( `@expo/ui — universal (iOS + Android + web)` );
176 console. log ( ` Components: ${ formatNames ( universalComponents ) } \n ` );
177
178 console. log ( `@expo/ui/swift-ui — iOS ONLY (crashes on Android)` );
179 console. log ( ` Components: ${ formatNames ( swiftuiComponents ) }` );
180 if (withDocs) {
181 console. log ( ` Modifiers: \n ${ formatModifiers ( swiftuiModifiers ) }` );
182 } else {
183 console. log ( ` Modifiers: ${ formatModifiers ( swiftuiModifiers ) }` );
184 }
185 console. log ();
186
187 console. log ( `@expo/ui/jetpack-compose — Android ONLY (crashes on iOS)` );
188 console. log ( ` Components: ${ formatNames ( composeComponents ) }` );
189 if (withDocs) {
190 console. log ( ` Modifiers: \n ${ formatModifiers ( composeModifiers ) }` );
191 } else {
192 console. log ( ` Modifiers: ${ formatModifiers ( composeModifiers ) }` );
193 }