import { OptionMetadata , OptionType , PluginSignatures , spaceCase , } from "@miniflare/shared" ; import { bold , grey } from "kleur/colors" ; import { argName } from "./helpers" ; interface HelpLine { alias : string | undefined ; name : string ; description : string ; typeName : string ; } const helpMeta = { type : OptionType . BOOLEAN , alias : "h" , description : "Show help" , } ; const versionMeta = { type : OptionType . BOOLEAN , alias : "v" , description : "Show version number" , } ; internal export function _wrapLines ( text , length ) { const lines = [ text ] ; let lastLine ; while ( ( lastLine = lines [ lines . length - 1 ] ) . length > length ) { const spaceIndex = lastLine . lastIndexOf ( " " , length ) ; if ( spaceIndex === - 1 ) break ; lines [ lines . length - 1 ] = lastLine . substring ( 0 , spaceIndex ) ; lines . push ( lastLine . substring ( spaceIndex + 1 ) ) ; } return lines ; } export function buildHelp < Plugins extends PluginSignatures > ( plugins , exec , columns = process . stdout . columns ) { let positionalName = "" ; let maxNameLength = 0 ; let maxDescriptionLength = 0 ; let maxTypeLength = 0 ; const sections = [ ] ; for ( const [ pluginName , plugin ] of Object . entries ( plugins ) ) { if ( plugin . prototype . opts === undefined ) continue ; const pluginLines = [ ] ; const sectionName = spaceCase ( pluginName . replace ( "Plugin" , "Options" ) ) ; const entries = [ ... plugin . prototype . opts . entries ( ) ] ; if ( sectionName === "Core Options" ) { entries . unshift ( [ "help" , helpMeta ] , [ "version" , versionMeta ] ) ; } for ( const [ key , meta ] of entries ) { const { type , typeFormat , alias , description = "" , negatable } = meta ; if ( type === OptionType . NONE || typeof key === "symbol" ) continue ; let name = argName ( key , meta ) ; if ( negatable ) name = `(no-)${ name }` ; if ( type === OptionType . STRING_POSITIONAL ) { positionalName = name ; continue ; } let typeName = OptionType [ type ] . toLowerCase ( ) ; if ( type === OptionType . BOOLEAN_STRING ) typeName = "boolean/string" ; else if ( type === OptionType . BOOLEAN_NUMBER ) typeName = "boolean/number" ; else if ( type === OptionType . OBJECT ) { typeName = `array:${ typeFormat ?? "KEY=VALUE" }` ; } if ( name . length > maxNameLength ) { maxNameLength = name . length ; } if ( description . length > maxDescriptionLength ) { maxDescriptionLength = description . length ; } if ( typeName . length > maxTypeLength ) { maxTypeLength = typeName . length ; } pluginLines . push ( { alias , name , description , typeName } ) ; } sections . push ( [ sectionName , pluginLines ] ) ; } const leftPaddingLength = maxNameLength + 8 ; const leftPadding = "" . padEnd ( leftPaddingLength , " " ) ; columns = Math . min ( leftPaddingLength + maxDescriptionLength + maxTypeLength + 3 , columns ) ; let out = `${ bold ( "Usage:" ) } ${ exec } [${ positionalName }] [options]\n` ; for ( const [ sectionName , pluginLines ] of sections ) { if ( pluginLines . length > 0 ) out += `\n${ bold ( sectionName + ":" ) }\n` ; for ( const { alias , name , description , typeName } of pluginLines ) { out += grey ( alias ? ` -${ alias }, ` : "     " ) ; out += `--${ name }` . padEnd ( maxNameLength + 3 , " " ) ; const lineLength = columns - ( leftPaddingLength + typeName . length + 3 ) ; const lines = _wrapLines ( description , lineLength ) ; out += lines [ 0 ] . padEnd ( lineLength , " " ) ; out += grey ( ` [${ typeName }]` ) ; for ( let i = 1 ; i < lines . length ; i ++ ) { out += `\n${ leftPadding }${ lines [ i ] }` ; } out += "\n" ; } } return out . trimEnd ( ) ; }