@@ -25,14 +25,24 @@ export async function finalizeAddOns(
2525
2626 for ( const addOnID of finalAddOnIDs ) {
2727 let addOn : AddOn | undefined
28- const localAddOn = addOns . find ( ( a ) => a . id === addOnID )
28+ const localAddOn =
29+ addOns . find ( ( a ) => a . id === addOnID ) ??
30+ addOns . find ( ( a ) => a . id . toLowerCase ( ) === addOnID . toLowerCase ( ) )
2931 if ( localAddOn ) {
3032 addOn = loadAddOn ( localAddOn )
33+ if ( localAddOn . id !== addOnID ) {
34+ // Replace the mistyped ID with the canonical one
35+ finalAddOnIDs . delete ( addOnID )
36+ finalAddOnIDs . add ( localAddOn . id )
37+ }
3138 } else if ( addOnID . startsWith ( 'http' ) ) {
3239 addOn = await loadRemoteAddOn ( addOnID )
3340 addOns . push ( addOn )
3441 } else {
35- throw new Error ( `Add-on ${ addOnID } not found` )
42+ const suggestion = findClosestAddOn ( addOnID , addOns )
43+ throw new Error (
44+ `Add-on ${ addOnID } not found${ suggestion ? `. Did you mean "${ suggestion } "?` : '' } ` ,
45+ )
3646 }
3747
3848 for ( const dependsOn of addOn . dependsOn || [ ] ) {
@@ -55,6 +65,48 @@ function loadAddOn(addOn: AddOn): AddOn {
5565 return addOn
5666}
5767
68+ function findClosestAddOn (
69+ input : string ,
70+ addOns : Array < AddOn > ,
71+ ) : string | undefined {
72+ const inputLower = input . toLowerCase ( )
73+ let bestMatch : string | undefined
74+ let bestDistance = Infinity
75+
76+ for ( const addOn of addOns ) {
77+ const d = levenshtein ( inputLower , addOn . id . toLowerCase ( ) )
78+ if ( d < bestDistance ) {
79+ bestDistance = d
80+ bestMatch = addOn . id
81+ }
82+ }
83+
84+ // Only suggest if the distance is reasonable (less than half the input length)
85+ if ( bestMatch && bestDistance <= Math . max ( Math . floor ( input . length / 2 ) , 2 ) ) {
86+ return bestMatch
87+ }
88+ return undefined
89+ }
90+
91+ function levenshtein ( a : string , b : string ) : number {
92+ const m = a . length
93+ const n = b . length
94+ let prev = Array . from ( { length : n + 1 } , ( _ , j ) => j )
95+
96+ for ( let i = 1 ; i <= m ; i ++ ) {
97+ const curr = [ i ]
98+ for ( let j = 1 ; j <= n ; j ++ ) {
99+ curr [ j ] =
100+ a [ i - 1 ] === b [ j - 1 ]
101+ ? prev [ j - 1 ]
102+ : 1 + Math . min ( prev [ j ] , curr [ j - 1 ] , prev [ j - 1 ] )
103+ }
104+ prev = curr
105+ }
106+
107+ return prev [ n ]
108+ }
109+
58110export function populateAddOnOptionsDefaults (
59111 chosenAddOns : Array < AddOn > ,
60112) : Record < string , Record < string , any > > {
0 commit comments