@@ -9,7 +9,7 @@ const VERSION = typeof PORTFORWARD_VERSION !== "undefined" ? PORTFORWARD_VERSION
99const REPO = process . env . PORTFORWARD_REPO
1010 ?? ( typeof PORTFORWARD_REPO !== "undefined" ? PORTFORWARD_REPO : "edenlabllc/portforward" ) ;
1111
12- type ServiceConfig = {
12+ export type ServiceConfig = {
1313 name : string ;
1414 namespace : string ;
1515 pod ?: string ;
@@ -19,7 +19,7 @@ type ServiceConfig = {
1919 remotePort : number ;
2020} ;
2121
22- type AppConfig = {
22+ export type AppConfig = {
2323 name ?: string ;
2424 services : ServiceConfig [ ] ;
2525} ;
@@ -48,13 +48,15 @@ async function main() {
4848 const config = await loadConfig ( configPath ) ;
4949 if ( config . services . length === 0 ) fail ( `No services configured in ${ configPath } ` ) ;
5050
51+ const selected = selectServices ( config . services , getOptions ( args , "--svc" ) ) ;
52+
5153 console . log ( `portforward: ${ config . name ?? configPath } ` ) ;
5254 console . log ( `config: ${ configPath } ` ) ;
53- console . log ( `services: ${ config . services . map ( ( service ) => service . name ) . join ( ", " ) } ` ) ;
55+ console . log ( `services: ${ selected . map ( ( service ) => service . name ) . join ( ", " ) } ` ) ;
5456 console . log ( "Press Ctrl+C to stop." ) ;
5557
5658 installSignalHandlers ( ) ;
57- await Promise . all ( config . services . map ( ( service ) => keepAlive ( service ) ) ) ;
59+ await Promise . all ( selected . map ( ( service ) => keepAlive ( service ) ) ) ;
5860 return ;
5961 }
6062
@@ -97,15 +99,42 @@ function printHelp() {
9799Usage:
98100 portforward init [--config portforward.yaml] [--force]
99101 portforward check [--config portforward.yaml]
100- portforward start [--config portforward.yaml]
102+ portforward start [--config portforward.yaml] [--svc NAME]...
101103 portforward upgrade download and install the latest release
102104 portforward version
103105
106+ --svc NAME run only matching services (substring match, repeatable);
107+ omit to start all. e.g. --svc minio --svc postgres
108+
104109Config files are searched in this order:
105110 ${ CONFIG_FILES . join ( "\n " ) }
106111` ) ;
107112}
108113
114+ function getOptions ( args : string [ ] , name : string ) {
115+ const values : string [ ] = [ ] ;
116+ for ( let index = 0 ; index < args . length ; index ++ ) {
117+ if ( args [ index ] === name && args [ index + 1 ] !== undefined ) values . push ( args [ index + 1 ] ! ) ;
118+ }
119+ return values ;
120+ }
121+
122+ export function selectServices ( services : ServiceConfig [ ] , terms : string [ ] ) {
123+ if ( terms . length === 0 ) return services ;
124+
125+ const needles = terms . map ( ( term ) => term . toLowerCase ( ) ) ;
126+ const matches = ( service : ServiceConfig ) =>
127+ needles . some ( ( needle ) => service . name . toLowerCase ( ) . includes ( needle ) ) ;
128+ const unmatched = needles . filter ( ( needle ) =>
129+ ! services . some ( ( service ) => service . name . toLowerCase ( ) . includes ( needle ) ) ) ;
130+
131+ if ( unmatched . length ) {
132+ fail ( `no service matches: ${ unmatched . join ( ", " ) } . available: ${ services . map ( ( service ) => service . name ) . join ( ", " ) } ` ) ;
133+ }
134+
135+ return services . filter ( matches ) ;
136+ }
137+
109138function getOption ( args : string [ ] , name : string ) {
110139 const index = args . indexOf ( name ) ;
111140 if ( index === - 1 ) return undefined ;
@@ -179,13 +208,13 @@ async function loadConfig(path: string): Promise<AppConfig> {
179208 return parsed ;
180209}
181210
182- function finalizeConfig ( config : AppConfig ) {
211+ export function finalizeConfig ( config : AppConfig ) {
183212 for ( const service of config . services ) {
184213 service . pod ??= service . name ;
185214 }
186215}
187216
188- function parseConfig ( text : string ) : AppConfig {
217+ export function parseConfig ( text : string ) : AppConfig {
189218 const trimmed = text . trimStart ( ) ;
190219 if ( trimmed . startsWith ( "{" ) ) return parseJsonConfig ( trimmed ) ;
191220 return parseYamlConfig ( text ) ;
@@ -337,7 +366,7 @@ function stripInlineComment(line: string) {
337366 return line . trimEnd ( ) ;
338367}
339368
340- function validateConfig ( config : AppConfig , path : string ) {
369+ export function validateConfig ( config : AppConfig , path : string ) {
341370 for ( const service of config . services ) {
342371 if ( ! service . name ) fail ( `Invalid config ${ path } : service without name.` ) ;
343372 if ( ! service . namespace ) fail ( `Invalid config ${ path } : ${ service . name } .namespace is required.` ) ;
@@ -522,12 +551,12 @@ async function streamLines(stream: ReadableStream | null, onLine: (line: string)
522551 }
523552}
524553
525- function isBenignForwardError ( line : string ) {
554+ export function isBenignForwardError ( line : string ) {
526555 return line . includes ( "error copying from local connection to remote stream" )
527556 || line . includes ( "error copying from remote stream to local connection" ) ;
528557}
529558
530- function nextReconnectDelay ( current : number ) {
559+ export function nextReconnectDelay ( current : number ) {
531560 return Math . min ( current * 2 , 30000 ) ;
532561}
533562
@@ -554,8 +583,12 @@ function sleep(ms: number) {
554583}
555584
556585function fail ( message : string ) : never {
557- console . error ( `portforward: ${ message } ` ) ;
558- process . exit ( 1 ) ;
586+ throw new Error ( message ) ;
559587}
560588
561- main ( ) . catch ( ( error ) => fail ( error instanceof Error ? error . message : String ( error ) ) ) ;
589+ if ( import . meta. main ) {
590+ main ( ) . catch ( ( error ) => {
591+ console . error ( `portforward: ${ error instanceof Error ? error . message : String ( error ) } ` ) ;
592+ process . exit ( 1 ) ;
593+ } ) ;
594+ }
0 commit comments