@@ -180,7 +180,7 @@ export namespace BoardSearch {
180
180
'Partner' ,
181
181
'Arduino@Heart' ,
182
182
] as const ;
183
- export type Type = typeof TypeLiterals [ number ] ;
183
+ export type Type = ( typeof TypeLiterals ) [ number ] ;
184
184
export namespace Type {
185
185
export function is ( arg : unknown ) : arg is Type {
186
186
return typeof arg === 'string' && TypeLiterals . includes ( arg as Type ) ;
@@ -347,7 +347,7 @@ export namespace Port {
347
347
348
348
export namespace Protocols {
349
349
export const KnownProtocolLiterals = [ 'serial' , 'network' ] as const ;
350
- export type KnownProtocol = typeof KnownProtocolLiterals [ number ] ;
350
+ export type KnownProtocol = ( typeof KnownProtocolLiterals ) [ number ] ;
351
351
export namespace KnownProtocol {
352
352
export function is ( protocol : unknown ) : protocol is KnownProtocol {
353
353
return (
@@ -476,29 +476,109 @@ export namespace ConfigOption {
476
476
fqbn : string ,
477
477
configOptions : ConfigOption [ ]
478
478
) : 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
+
479
488
if ( ! configOptions . length ) {
480
489
return fqbn ;
481
490
}
482
491
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
+ }
484
526
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 {
486
549
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
489
552
) } `
490
553
) ;
491
- return undefined ;
492
554
}
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 )
498
579
. map ( ( [ option , value ] ) => `${ option } =${ value } ` )
499
580
. join ( ',' ) ;
500
-
501
- return `${ fqbn } :${ options } ` ;
581
+ return `${ vendor } :${ arch } :${ id } :${ configSuffix } ` ;
502
582
}
503
583
504
584
export class ConfigOptionError extends Error {
0 commit comments