import { readFileSync } from "fs" ; import path from "path" ; import { MiniflareError , OptionMetadata , OptionType , Options , PluginSignatures , } from "@miniflare/shared" ; import mri from "mri" ; import { argName } from "./helpers" ; interface Arg { name : string ; key : string ; meta : OptionMetadata ; } type ArgValue = boolean | number | string ; type ArgValues = ArgValue | ArgValue [ ] | undefined ; export type ParseErrorCode = "ERR_HELP" | "ERR_VERSION" | "ERR_OPTION" | "ERR_VALUE" ; export class ParseError extends MiniflareError < ParseErrorCode > { } export function parseArgv < Plugins extends PluginSignatures > ( plugins , argv ) { let positionalName = "" ; const booleans = [ "help" , "version" ] ; const strings = [ ] ; const aliases = { help : "h" , version : "v" , } ; const args = [ ] ; for ( const plugin of Object . values ( plugins ) ) { if ( plugin . prototype . opts === undefined ) continue ; for ( const [ key , meta ] of plugin . prototype . opts . entries ( ) ) { const type = meta . type ; if ( type === OptionType . NONE || typeof key === "symbol" ) continue ; const name = argName ( key , meta ) ; args . push ( { name , key , meta } ) ; if ( type === OptionType . STRING_POSITIONAL ) { positionalName = name ; continue ; } if ( type === OptionType . BOOLEAN ) { booleans . push ( name ) ; } if ( type === OptionType . STRING || type === OptionType . ARRAY || type === OptionType . OBJECT ) { strings . push ( name ) ; } aliases [ name ] = meta . alias ?? [ ] ; } } const parsed = mri ( argv , { boolean : booleans , string : strings , alias : aliases , unknown ( flag ) { throw new ParseError ( "ERR_OPTION" , `Unexpected option: ${ flag }` ) ; } , } ) ; if ( parsed . help ) throw new ParseError ( "ERR_HELP" ) ; if ( parsed . version ) { const pkgPath = path . join ( __dirname , ".." , ".." , "package.json" ) ; const pkg = JSON . parse ( readFileSync ( pkgPath , "utf8" ) ) ; throw new ParseError ( "ERR_VERSION" , pkg . version ) ; } function unexpected ( name , value , expectedType ) { if ( name !== positionalName ) name = `--${ name }` ; const message = `Unexpected value for ${ name }: ` + JSON . stringify ( value ) + ` (expected ${ expectedType })` ; throw new ParseError ( "ERR_VALUE" , message ) ; } const result = { } as Options < Plugins > ; for ( const { name , key , meta } of args ) { const { type , typeFormat , fromEntries } = meta ; let value = parsed [ name ] ; if ( type === OptionType . STRING_POSITIONAL ) { if ( parsed . _ . length === 1 ) value = parsed . _ [ 0 ] ; else if ( parsed . _ . length > 1 ) value = parsed . _ as string [ ] ; } if ( value === undefined ) continue ; let parsedValue = value ; if ( type === OptionType . BOOLEAN ) { if ( typeof value !== "boolean" ) unexpected ( name , value , "boolean" ) ; } else if ( type === OptionType . NUMBER ) { if ( typeof value !== "number" ) unexpected ( name , value , "number" ) ; } else if ( type === OptionType . STRING || type === OptionType . STRING_POSITIONAL ) { if ( typeof value !== "string" ) unexpected ( name , value , "string" ) ; } else if ( type === OptionType . BOOLEAN_STRING ) { if ( Array . isArray ( value ) ) unexpected ( name , value , "boolean/string" ) ; if ( typeof value === "number" ) parsedValue = value . toString ( ) ; } else if ( type === OptionType . BOOLEAN_NUMBER ) { if ( typeof value !== "boolean" && typeof value !== "number" ) { unexpected ( name , value , "boolean/number" ) ; } } else if ( type === OptionType . ARRAY || type === OptionType . OBJECT ) { if ( ! Array . isArray ( value ) ) value = [ value ] ; const array = value . map < string > ( ( element ) => element . toString ( ) ) ; if ( type === OptionType . OBJECT ) { parsedValue = array . map < [ key : string , value : string ] > ( ( element ) => { const equals = element . indexOf ( "=" ) ; if ( equals === - 1 ) { unexpected ( name , element , typeFormat ?? "KEY=VALUE" ) ; } return [ element . substring ( 0 , equals ) , element . substring ( equals + 1 ) ] ; } ) ; parsedValue = ( fromEntries ?? Object . fromEntries ) ( parsedValue ) ; } else { parsedValue = array ; } } ( result as any ) [ key ] = parsedValue ; } return result ; }