1import { Command } from '@expo/commander'; 2import chalk from 'chalk'; 3import { Application, TSConfigReader, TypeDocReader } from 'typedoc'; 4import fs from 'fs-extra'; 5import recursiveOmitBy from 'recursive-omit-by'; 6 7import { EXPO_DIR, PACKAGES_DIR } from '../Constants'; 8import logger from '../Logger'; 9 10type ActionOptions = { 11 packageName: string; 12}; 13 14type EntryPoint = string | string[]; 15 16type CommandAdditionalParams = [ 17 entryPoint: EntryPoint, 18 jsonFileName?: string 19]; 20 21const DATA_PATH = `${EXPO_DIR}/docs/public/static/data/unversioned`; 22const MINIFY_JSON = true; 23 24const executeCommand = async ( 25 packageName: string, 26 entryPoint: EntryPoint = 'index.ts', 27 jsonFileName: string = packageName 28) => { 29 const app = new Application(); 30 31 app.options.addReader(new TSConfigReader()); 32 app.options.addReader(new TypeDocReader()); 33 34 const entriesPath = `${PACKAGES_DIR}/${packageName}/src`; 35 const tsConfigPath = `${PACKAGES_DIR}/${packageName}/tsconfig.json`; 36 const jsonOutputPath = `${DATA_PATH}/${jsonFileName}.json`; 37 38 const entryPoints = Array.isArray(entryPoint) ? ( 39 entryPoint.map(entry => `${entriesPath}/${entry}`) 40 ) : ( 41 [`${entriesPath}/${entryPoint}`] 42 ); 43 44 app.bootstrap({ 45 entryPoints, 46 tsconfig: tsConfigPath, 47 disableSources: true, 48 hideGenerator: true, 49 excludePrivate: true, 50 excludeProtected: true, 51 }); 52 53 const project = app.convert(); 54 55 if (project) { 56 await app.generateJson(project, jsonOutputPath); 57 const output = await fs.readJson(jsonOutputPath); 58 59 if (Array.isArray(entryPoint)) { 60 const filterEntries = entryPoint.map(entry => entry.substring(0, entry.lastIndexOf('.'))); 61 output.children = output.children 62 .filter(entry => filterEntries.includes(entry.name)) 63 .map(entry => entry.children) 64 .flat() 65 .sort((a, b) => a.name.localeCompare(b.name)); 66 } 67 68 if (MINIFY_JSON) { 69 const minifiedJson = recursiveOmitBy( 70 output, 71 ({ key }) => key === 'id' || key === 'groups' 72 ); 73 await fs.writeFile(jsonOutputPath, JSON.stringify(minifiedJson, null, 0)); 74 } else { 75 await fs.writeFile(jsonOutputPath, JSON.stringify(output)); 76 } 77 } else { 78 throw new Error(` Failed to extract API data from source code for '${packageName}' package.`); 79 } 80}; 81 82async function action({ packageName }: ActionOptions) { 83 const packagesMapping: Record<string, CommandAdditionalParams> = { 84 'expo-apple-authentication': [['AppleAuthentication.ts', 'AppleAuthentication.types.ts']], 85 'expo-application': ['Application.ts'], 86 'expo-battery': ['Battery.ts'], 87 'expo-blur': ['index.ts'], 88 'expo-cellular': ['Cellular.ts'], 89 'expo-clipboard': ['Clipboard.ts'], 90 'expo-keep-awake': ['index.ts'], 91 'expo-linear-gradient': ['LinearGradient.tsx'], 92 'expo-mail-composer': ['MailComposer.ts'], 93 'expo-print': ['Print.ts'], 94 'expo-random': ['Random.ts'], 95 'expo-sensors': ['Pedometer.ts', 'expo-pedometer'], 96 'expo-sharing': ['Sharing.ts'], 97 'expo-splash-screen': ['SplashScreen.ts'], 98 'expo-store-review': ['StoreReview.ts'], 99 'expo-task-manager': ['TaskManager.ts'], 100 'expo-tracking-transparency': ['TrackingTransparency.ts'], 101 }; 102 103 try { 104 if (packageName) { 105 const packagesEntries = Object.entries(packagesMapping) 106 .filter(([key, value]) => key == packageName || value.includes(packageName)) 107 .map(([key, value]) => executeCommand(key, ...value)); 108 if (packagesEntries.length) { 109 await Promise.all(packagesEntries); 110 logger.log(chalk.green(`\n Successful extraction of docs API data for the selected package!`)); 111 } else { 112 logger.warn(` Package '${packageName}' API data generation is not supported yet!`); 113 } 114 } else { 115 const packagesEntries = Object.entries(packagesMapping) 116 .map(([key, value]) => executeCommand(key, ...value)); 117 await Promise.all(packagesEntries); 118 logger.log(chalk.green(`\n Successful extraction of docs API data for all available packages!`)); 119 } 120 } catch (error) { 121 logger.error(error); 122 } 123} 124 125export default (program: Command) => { 126 program 127 .command('generate-docs-api-data') 128 .alias('gdad') 129 .description(`Extract API data for docs using TypeDoc.`) 130 .option('-p, --packageName <packageName>', 'Extract API data only for the specific package.') 131 .asyncAction(action); 132}; 133