@@ -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,105 @@ export namespace ConfigOption {
476476 fqbn : string ,
477477 configOptions : ConfigOption [ ]
478478 ) : string {
479- if ( ! configOptions . length ) {
480- return fqbn ;
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+
488+ const existingOptions : Record < string , string > = { } ;
489+ if ( rest ) {
490+ // If rest exists, it must have the key=value(,key=value)* format. Otherwise, fail.
491+ const tuples = rest . split ( ',' ) ;
492+ for ( const tuple of tuples ) {
493+ const segments = tuple . split ( '=' ) ;
494+ if ( segments . length !== 2 ) {
495+ failInvalidFQBN ( ) ;
496+ }
497+ const [ option , value ] = segments ;
498+ if ( ! option || ! value ) {
499+ failInvalidFQBN ( ) ;
500+ }
501+ if ( existingOptions [ option ] ) {
502+ console . warn (
503+ `Config value already exists for '${ option } ' on FQBN. Skipping it. Existing value: ${ existingOptions [ option ] } , new value: ${ value } , FQBN: ${ fqbn } `
504+ ) ;
505+ } else {
506+ existingOptions [ option ] = value ;
507+ }
508+ }
481509 }
482510
483- const toValue = ( values : ConfigValue [ ] ) => {
511+ const newOptions : Record < string , string > = { } ;
512+ for ( const configOption of configOptions ) {
513+ const { option, values } = configOption ;
514+ if ( ! option ) {
515+ console . warn (
516+ `Detected empty option on config options. Skipping it. ${ JSON . stringify (
517+ configOption
518+ ) } `
519+ ) ;
520+ continue ;
521+ }
484522 const selectedValue = values . find ( ( { selected } ) => selected ) ;
485- if ( ! selectedValue ) {
523+ if ( selectedValue ) {
524+ const { value } = selectedValue ;
525+ if ( ! value ) {
526+ console . warn (
527+ `Detected empty selected value on config options. Skipping it. ${ JSON . stringify (
528+ configOption
529+ ) } `
530+ ) ;
531+ continue ;
532+ }
533+ if ( newOptions [ option ] ) {
534+ console . warn (
535+ `Config value already exists for '${ option } ' in config options. Skipping it. Existing value: ${
536+ newOptions [ option ]
537+ } , new value: ${ value } , config option: ${ JSON . stringify (
538+ configOption
539+ ) } `
540+ ) ;
541+ } else {
542+ newOptions [ option ] = value ;
543+ }
544+ } else {
486545 console . warn (
487- `None of the config values was selected. Values were : ${ JSON . stringify (
488- values
546+ `None of the config values was selected. Config options was : ${ JSON . stringify (
547+ configOption
489548 ) } `
490549 ) ;
491- return undefined ;
492550 }
493- return selectedValue . value ;
494- } ;
495- const options = configOptions
496- . map ( ( { option, values } ) => [ option , toValue ( values ) ] )
497- . filter ( ( [ , value ] ) => ! ! value )
551+ }
552+
553+ // Collect all options from the FQBN. Call them existing.
554+ // Collect all incoming options to decorate the FQBN with. Call them new.
555+ // To keep the order, iterate through the existing ones and append to FQBN.
556+ // If a new(er) value exists for the same option, use the new value.
557+ // If there is a new value, "mark" it as visited by deleting it from new. Otherwise, use the existing value.
558+ // Append all new ones to the FQBN.
559+ const mergedOptions : Record < string , string > = { } ;
560+ for ( const existing of Object . entries ( existingOptions ) ) {
561+ const [ option , value ] = existing ;
562+ const newValue = newOptions [ option ] ;
563+ if ( newValue ) {
564+ mergedOptions [ option ] = newValue ;
565+ delete newOptions [ option ] ;
566+ } else {
567+ mergedOptions [ option ] = value ;
568+ }
569+ }
570+ Array . from ( Object . entries ( newOptions ) ) . forEach (
571+ ( [ option , value ] ) => ( mergedOptions [ option ] = value )
572+ ) ;
573+
574+ const configSuffix = Object . entries ( mergedOptions )
498575 . map ( ( [ option , value ] ) => `${ option } =${ value } ` )
499576 . join ( ',' ) ;
500-
501- return `${ fqbn } :${ options } ` ;
577+ return `${ vendor } :${ arch } :${ id } :${ configSuffix } ` ;
502578 }
503579
504580 export class ConfigOptionError extends Error {
0 commit comments