@@ -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,105 @@ export namespace ConfigOption {
476
476
fqbn : string ,
477
477
configOptions : ConfigOption [ ]
478
478
) : 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
+ }
481
509
}
482
510
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
+ }
484
522
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 {
486
545
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
489
548
) } `
490
549
) ;
491
- return undefined ;
492
550
}
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 )
498
575
. map ( ( [ option , value ] ) => `${ option } =${ value } ` )
499
576
. join ( ',' ) ;
500
-
501
- return `${ fqbn } :${ options } ` ;
577
+ return `${ vendor } :${ arch } :${ id } :${ configSuffix } ` ;
502
578
}
503
579
504
580
export class ConfigOptionError extends Error {
0 commit comments