@@ -77,16 +77,16 @@ function readStdin(): string {
7777}
7878
7979/**
80- * Resolves a JSON flag's argument, which may name a file instead of carrying
81- * the document inline.
80+ * Resolves a flag argument that may name a file instead of carrying its value
81+ * inline.
8282 *
8383 * `@path` reads the file and `@-` reads stdin, the curl convention. A workflow
8484 * export is hundreds of lines, and the shell makes passing that literally
8585 * unpleasant — unquoted `$(cat f.json)` word-splits into broken JSON, and the
86- * quoted form is easy to get wrong. `@` cannot collide with a real value
87- * because JSON only ever starts with `{ [ " -`, a digit, or t/f/n .
86+ * quoted form is easy to get wrong. JSON never starts with `@`; primitive list
87+ * flags reserve it for this explicit file-input form .
8888 */
89- function readJsonArgument ( raw : string , flagName : string ) : { text : string ; from : string } {
89+ function readArgumentSource ( raw : string , flagName : string ) : { text : string ; from : string } {
9090 if ( ! raw . startsWith ( '@' ) ) return { text : raw , from : '' }
9191
9292 const path = raw . slice ( 1 )
@@ -108,6 +108,42 @@ function readJsonArgument(raw: string, flagName: string): { text: string; from:
108108 }
109109}
110110
111+ /** Reads a primitive list from argv or a newline-delimited file. */
112+ function readListValues ( raw : unknown , flagName : string ) : string [ ] {
113+ const arguments_ = Array . isArray ( raw ) ? raw : [ raw ]
114+ const values = arguments_ . flatMap ( ( argument ) => {
115+ if ( typeof argument !== 'string' ) {
116+ throw new SimApiError ( `--${ flagName } values must be strings` , 0 )
117+ }
118+
119+ if ( ! argument . startsWith ( '@' ) ) return [ argument ]
120+
121+ const source = readArgumentSource ( argument , flagName )
122+ const lines = source . text . split ( / \r ? \n / )
123+ if ( lines . at ( - 1 ) === '' ) lines . pop ( )
124+ if ( lines . length === 0 ) {
125+ throw new SimApiError ( `--${ flagName } ${ source . from } contains no values` , 0 )
126+ }
127+
128+ return lines . map ( ( line , index ) => {
129+ const value = line . trim ( )
130+ if ( ! value ) {
131+ throw new SimApiError (
132+ `--${ flagName } ${ source . from } has an empty value on line ${ index + 1 } ` ,
133+ 0
134+ )
135+ }
136+ return value
137+ } )
138+ } )
139+
140+ return values . map ( ( value ) => {
141+ const trimmed = value . trim ( )
142+ if ( ! trimmed ) throw new SimApiError ( `--${ flagName } values cannot be empty` , 0 )
143+ return trimmed
144+ } )
145+ }
146+
111147/**
112148 * Points at `@` when a value that failed to parse looks like a filename.
113149 *
@@ -144,13 +180,13 @@ export function coerce(raw: unknown, field: FieldSpec, flag: FlagSpec, flagName:
144180 * or failed validation outright.
145181 */
146182 if ( flag . list ) {
147- const values = Array . isArray ( raw ) ? raw : [ raw ]
183+ const values = readListValues ( raw , flagName )
148184 return field . kind === 'string' ? values . join ( ',' ) : values
149185 }
150186
151187 if ( takesJson ( field , flag ) ) {
152188 if ( typeof raw !== 'string' ) return raw
153- const source = readJsonArgument ( raw , flagName )
189+ const source = readArgumentSource ( raw , flagName )
154190 try {
155191 return JSON . parse ( source . text )
156192 } catch ( error ) {
0 commit comments