char/ngx
git clone https://git.t4t.associates/char/ngx
257f894
main
1const NGX_VERSION = "0.2.2" ; 2 3type ConfigNode = ConfigStatement | ConfigBlock | ConfigBreak | ConfigFile ; 4 5class ConfigBuildable { 6build () :string { 7throw new Error ( "build(..) not implemented!" ); 8} 9} 10 11class ConfigBlock extends ConfigBuildable { 12value :string ; 13children :ConfigNode []; 14 15constructor ( value :string , children :ConfigNode []) { 16super (); 17 18this . value = value ; 19this . children = children ; 20} 21 22override build () :string { 23let output = this . value ; 24output += " {\n " ; 25output += this . children 26. map (( child ) => child . build (). split ( "\n" ). join ( "\n " )) 27. join ( "\n " ); 28output += "\n}" ; 29return output ; 30} 31} 32 33class ConfigStatement extends ConfigBuildable { 34value :string ; 35constructor ( value :string ) { 36super (); 37 38this . value = value ; 39} 40 41override build () :string { 42return this . value + ";" ; 43} 44} 45 46class ConfigBreak extends ConfigBuildable { 47override build () :string { 48return "" ; 49} 50} 51 52class ConfigFile extends ConfigBuildable { 53nodes :ConfigNode []; 54constructor ( nodes :ConfigNode []) { 55super (); 56 57this . nodes = nodes ; 58} 59 60override build () :string { 61return this . nodes . map (( n ) => n . build ()). join ( "\n" ); 62} 63} 64 65type LooseConfigNode = ConfigNode | string | LooseConfigNode []; 66 67function conform ( looseNode :LooseConfigNode ) :ConfigNode [] { 68if ( typeof looseNode === "string" ) { 69return [ new ConfigStatement ( looseNode )]; 70} 71if ( typeof looseNode === "object" && looseNode instanceof Array ) { 72if ( looseNode . length === 0 ) { 73return [ new ConfigBreak ()]; 74} else if ( looseNode . length === 1 ) { 75return conform ( looseNode [ 0 ]); 76} else { 77return looseNode 78. map (( n ) => conform ( n )) 79. reduceRight (( b , a ) => 80a . length === 1 ?[ ...a , ...b ] :[ ...a , new ConfigBreak (), ...b ], 81); 82} 83} 84return [ looseNode ]; 85} 86 87/** 88* create nginx config nodes. behavior depends on arguments: 89* 90* - no args - ConfigBreak 91* - value only - ConfigStatement 92* - value + children - ConfigBlock 93* - children only - ConfigFile 94* 95* accept strings, ConfigNodes, or nested arrays that flatten automatically. 96* 97* ```ts 98* ngx() // ConfigBreak 99* ngx("worker_processes auto") // ConfigStatement 100* ngx("location /", ["proxy_pass http://backend"]) // ConfigBlock 101* ngx(["server { ... }", "server { ... }"]) // ConfigFile 102* ``` 103*/ 104export function ngx ( value ?:string , children ?:LooseConfigNode []) :ConfigNode { 105const hasValue = value !== undefined && value !== "" ; 106const hasChildren = children !== undefined ; 107 108if ( ! hasValue && ! hasChildren ) { 109return new ConfigBreak (); 110} else if ( hasValue && ! hasChildren ) { 111return new ConfigStatement ( value ); 112} else if ( hasValue && hasChildren ) { 113return new ConfigBlock ( value , conform ( children )); 114} else if ( ! hasValue && hasChildren ) { 115return new ConfigFile ( conform ( children )); 116} 117 118throw new Error ( "unreachable" ); 119} 120 121/** 122* create ssl listen directives for ipv4/ipv6 with http/2 enabled. 123* 124* extras modify the listen directives. 125* 126* ```ts 127* listen() // listen 443 ssl, listen [::]:443 ssl, http2 on 128* listen("default_server") // includes "default_server" 129* ``` 130*/ 131export const listen = ( ...extras :string []) => 132conform ([ 133`listen 443 ${[ "ssl" , ... extras ]. join ( " " )} ` , 134`listen [::]:443 ${[ "ssl" , ... extras ]. join ( " " )} ` , 135`http2 on` , 136]); 137 138/** 139* create http/3 (quic) listen directives (+ supporting alt-svc header). 140*/ 141export const http3 = ( 142opts :{ 143/** max age for alt-svc header */ ma ?:number ; 144} = {}, 145) => 146conform ([ 147"listen 443 quic reuseport" , 148"listen [::]:443 quic reuseport" , 149`add_header Alt-Svc 'h3=":443"; ma= ${ opts . ma ?? 86400 } '` , 150]); 151 152/** 153* create a server_name nginx directive. 154* 155* ```ts 156* serverName("example.com") // "server_name example.com;" 157* ``` 158*/ 159export const serverName = ( name :string ) => 160new ConfigStatement ( `server_name ${ name } ` ); 161 162/** 163* create ssl certificate directives for let's encrypt certificates. 164* 165* ```ts 166* letsEncrypt("example.com") 167* // ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem 168* // ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem 169* ``` 170*/ 171export const letsEncrypt = ( 172domain :string , 173liveDir = "/etc/letsencrypt/live" , 174) => 175conform ([ 176`ssl_certificate ${ liveDir } / ${ domain } /fullchain.pem` , 177`ssl_certificate_key ${ liveDir } / ${ domain } /privkey.pem` , 178]); 179 180// the default export is both the ngx function and a namespace: 181/** 182* use as a function to create config nodes, or access helpers. 183* 184* ```ts 185* import ngx from './ngx'; 186* const config = ngx("location /", ["proxy_pass http://backend"]); 187* console.log(ngx.NGX_VERSION); 188* const ssl = ngx.letsEncrypt("example.com"); 189* ``` 190*/ 191export default Object . assign ( 192( value ?:string , children ?:LooseConfigNode []) => ngx ( value , children ), 193{ NGX_VERSION , listen, letsEncrypt, serverName, http3}, 194);