import { promises as fs } from "fs" ; import { dirname , join , resolve } from "path" ; import { FileReadError , PatternNotFoundError } from "./errors" ; import { promisify } from "util" ; import { exec } from "child_process" ; import { createProgram , escapeLeadingUnderscores } from "typescript" ; const execAsync = promisify ( exec ) ; const patterns = { widgetFileName : new RegExp ( '<widgetFile path="(.*\\.xml)"\\s*/>' ) , id : new RegExp ( `id="(.+?)"` ) , pluginWidget : new RegExp ( `pluginWidget="(.+?)"` ) , offlineCapable : new RegExp ( `offlineCapable="(.+?)"` ) , supportedPlatform : new RegExp ( `supportedPlatform="(.+?)"` ) } ; main ( ) . catch ( e => { console . error ( e ) ; process . exit ( 1 ) ; } ) ; async function main ( ) { const { stdout : lernaPackages } = await execAsync ( "npx lerna ls --json --all" ) ; const locations = JSON . parse ( lernaPackages . trim ( ) ) . map ( lernaPackage => lernaPackage . location ) ; const packages = await Promise . all ( locations . filter ( location => location . match ( /(pluggable|custom)Widgets/ ) ) . map ( async ( packagePath ) => { try { const { widgetFileName } = await extractTextFromFile ( packagePath , "src/package.xml" , { widgetFileName : [ patterns . widgetFileName ] } ) ; const { id , pluginWidget , offlineCapable , supportedPlatform } = await extractTextFromFile ( packagePath , `src/${ widgetFileName }` , { id : [ patterns . id ] , pluginWidget : [ patterns . pluginWidget , false ] , offlineCapable : [ patterns . offlineCapable , false ] , supportedPlatform : [ patterns . supportedPlatform , false ] } ) ; const hasStructurePreview = await fs . readdir ( join ( packagePath , "src" ) ) . then ( files => files . filter ( path => path . match ( /.*\.editorConfig\.[jt]sx?/ ) ) . some ( path => moduleExports ( join ( packagePath , "src" , path ) , "getPreview" ) ) ) ; const hasDesignModePreview = await fs . readdir ( join ( packagePath , "src" ) ) . then ( files => files . filter ( path => path . match ( /.*\.editorPreview\.[jt]sx?$/ ) ) . some ( path => moduleExports ( join ( packagePath , "src" , path ) , "preview" ) ) ) ; const hasTileIcons = await fs . readdir ( join ( packagePath , "src" ) ) . then ( files => files . some ( path => path . endsWith ( ".tile.png" ) ) ) ; const hasDarkModeIcons = await fs . readdir ( join ( packagePath , "src" ) ) . then ( files => files . some ( path => path . endsWith ( ".dark.png" ) ) ) ; return { id , pluginWidget : pluginWidget === "true" , offlineCapable : offlineCapable === "true" , supportedPlatform : supportedPlatform ?. toLowerCase ( ) ?? "web" , hasStructurePreview , hasDesignModePreview , hasTileIcons , hasDarkModeIcons } ; } catch ( e ) { if ( e instanceof Error && ( e . name === "FileReadError" || e . name === "ValueNotFoundError" ) ) { console . warn ( e . message ) ; return undefined ; } throw e ; } } ) ) ; const result = packages . filter ( isDefined ) ; await writeFile ( resolve ( __dirname , "../../data/widgets.json" ) , JSON . stringify ( result , null , "\t" ) ) ; } function isDefined < T > ( val ) { return val !== undefined && val !== null ; } async function readPackageFile ( packagePath , filePath ) { try { const fullPath = join ( packagePath , filePath ) ; const fileBuffer = await fs . readFile ( fullPath ) ; return fileBuffer . toString ( ) ; } catch ( e ) { throw new FileReadError ( packagePath , filePath ) ; } } async function writeFile ( path , content ) { const dir = dirname ( path ) ; try { await fs . access ( dir ) ; } catch ( _ ) { await fs . mkdir ( dir , { recursive : true } ) ; } await fs . writeFile ( path , content , { flag : "w+" } ) ; } type Patterns = { [ key ] : [ RegExp , boolean ? ] ; } ; type Values < P extends Patterns > = { [ key in keyof P ] : P [ key ] [ 1 ] extends true | undefined ? string : string | undefined ; } ; async function extractTextFromFile < P extends Patterns > ( packagePath , filePath , patterns ) { const content = await readPackageFile ( packagePath , filePath ) ; return Object . entries ( patterns ) . reduce < Values < P > > ( ( result , [ key , [ pattern , required = true ] ] ) => { const [ , value ] = content . match ( pattern ) ?? [ ] ; if ( ! value ) { if ( required ) { throw new PatternNotFoundError ( packagePath , filePath , key ) ; } console . warn ( `Could not find pattern ${ pattern } in ${ join ( packagePath , filePath ) }` ) ; } return value ? { ... result , [ key ] : value } : result ; } , { } as Values < P > ) ; } function moduleExports ( fileName , exportName ) { const program = createProgram ( [ fileName ] , { } ) ; const sourceFile = program . getSourceFile ( fileName ) ; if ( sourceFile ) { const fileSymbol = program . getTypeChecker ( ) . getSymbolAtLocation ( sourceFile ) ; return fileSymbol ?. exports ?. get ( escapeLeadingUnderscores ( exportName ) ) !== undefined ; } return false ; }