@@ -180,7 +180,7 @@ export namespace BoardSearch {
180180 'Partner' ,
181181 'Arduino@Heart' ,
182182 ] as const ;
183- export type Type = typeof TypeLiterals [ number ] ;
183+ export type Type = ( typeof TypeLiterals ) [ number ] ;
184184 export namespace Type {
185185 export function is ( arg : unknown ) : arg is Type {
186186 return typeof arg === 'string' && TypeLiterals . includes ( arg as Type ) ;
@@ -347,7 +347,7 @@ export namespace Port {
347347
348348 export namespace Protocols {
349349 export const KnownProtocolLiterals = [ 'serial' , 'network' ] as const ;
350- export type KnownProtocol = typeof KnownProtocolLiterals [ number ] ;
350+ export type KnownProtocol = ( typeof KnownProtocolLiterals ) [ number ] ;
351351 export namespace KnownProtocol {
352352 export function is ( protocol : unknown ) : protocol is KnownProtocol {
353353 return (
@@ -476,29 +476,109 @@ export namespace ConfigOption {
476476 fqbn : string ,
477477 configOptions : ConfigOption [ ]
478478 ) : string {
479+ const failInvalidFQBN = ( ) : never => {
480+ throw new Error ( `Invalid FQBN: ${ fqbn } ` ) ;
481+ } ;
482+
483+ const [ vendor , arch , id , rest ] = fqbn . split ( ':' ) ;
484+ if ( ! vendor || ! arch || ! id ) {
485+ return failInvalidFQBN ( ) ;
486+ }
487+
479488 if ( ! configOptions . length ) {
480489 return fqbn ;
481490 }
482491
483- const toValue = ( values : ConfigValue [ ] ) => {
492+ const existingOptions : Record < string , string > = { } ;
493+ if ( rest ) {
494+ // If rest exists, it must have the key=value(,key=value)* format. Otherwise, fail.
495+ const tuples = rest . split ( ',' ) ;
496+ for ( const tuple of tuples ) {
497+ const segments = tuple . split ( '=' ) ;
498+ if ( segments . length !== 2 ) {
499+ failInvalidFQBN ( ) ;
500+ }
501+ const [ option , value ] = segments ;
502+ if ( ! option || ! value ) {
503+ failInvalidFQBN ( ) ;
504+ }
505+ if ( existingOptions [ option ] ) {
506+ console . warn (
507+ `Config value already exists for '${ option } ' on FQBN. Skipping it. Existing value: ${ existingOptions [ option ] } , new value: ${ value } , FQBN: ${ fqbn } `
508+ ) ;
509+ } else {
510+ existingOptions [ option ] = value ;
511+ }
512+ }
513+ }
514+
515+ const newOptions : Record < string , string > = { } ;
516+ for ( const configOption of configOptions ) {
517+ const { option, values } = configOption ;
518+ if ( ! option ) {
519+ console . warn (
520+ `Detected empty option on config options. Skipping it. ${ JSON . stringify (
521+ configOption
522+ ) } `
523+ ) ;
524+ continue ;
525+ }
484526 const selectedValue = values . find ( ( { selected } ) => selected ) ;
485- if ( ! selectedValue ) {
527+ if ( selectedValue ) {
528+ const { value } = selectedValue ;
529+ if ( ! value ) {
530+ console . warn (
531+ `Detected empty selected value on config options. Skipping it. ${ JSON . stringify (
532+ configOption
533+ ) } `
534+ ) ;
535+ continue ;
536+ }
537+ if ( newOptions [ option ] ) {
538+ console . warn (
539+ `Config value already exists for '${ option } ' in config options. Skipping it. Existing value: ${
540+ newOptions [ option ]
541+ } , new value: ${ value } , config option: ${ JSON . stringify (
542+ configOption
543+ ) } `
544+ ) ;
545+ } else {
546+ newOptions [ option ] = value ;
547+ }
548+ } else {
486549 console . warn (
487- `None of the config values was selected. Values were : ${ JSON . stringify (
488- values
550+ `None of the config values was selected. Config options was : ${ JSON . stringify (
551+ configOption
489552 ) } `
490553 ) ;
491- return undefined ;
492554 }
493- return selectedValue . value ;
494- } ;
495- const options = configOptions
496- . map ( ( { option, values } ) => [ option , toValue ( values ) ] )
497- . filter ( ( [ , value ] ) => ! ! value )
555+ }
556+
557+ // Collect all options from the FQBN. Call them existing.
558+ // Collect all incoming options to decorate the FQBN with. Call them new.
559+ // To keep the order, iterate through the existing ones and append to FQBN.
560+ // If a new(er) value exists for the same option, use the new value.
561+ // If there is a new value, "mark" it as visited by deleting it from new. Otherwise, use the existing value.
562+ // Append all new ones to the FQBN.
563+ const mergedOptions : Record < string , string > = { } ;
564+ for ( const existing of Object . entries ( existingOptions ) ) {
565+ const [ option , value ] = existing ;
566+ const newValue = newOptions [ option ] ;
567+ if ( newValue ) {
568+ mergedOptions [ option ] = newValue ;
569+ delete newOptions [ option ] ;
570+ } else {
571+ mergedOptions [ option ] = value ;
572+ }
573+ }
574+ Array . from ( Object . entries ( newOptions ) ) . forEach (
575+ ( [ option , value ] ) => ( mergedOptions [ option ] = value )
576+ ) ;
577+
578+ const configSuffix = Object . entries ( mergedOptions )
498579 . map ( ( [ option , value ] ) => `${ option } =${ value } ` )
499580 . join ( ',' ) ;
500-
501- return `${ fqbn } :${ options } ` ;
581+ return `${ vendor } :${ arch } :${ id } :${ configSuffix } ` ;
502582 }
503583
504584 export class ConfigOptionError extends Error {
0 commit comments