@@ -203,9 +203,10 @@ impl PackageManifest {
203203 // If the bin list contains only an empty string, that means `bin` was a string value,
204204 // rather than a map. In that case, to match `npm`s behavior, we use the name of the package
205205 // as the bin name.
206+ // Note: For a scoped package, we should remove the scope and only use the package name
206207 if manifest. bin == [ "" ] {
207208 manifest. bin . pop ( ) ;
208- manifest. bin . push ( manifest. name . clone ( ) ) ;
209+ manifest. bin . push ( default_binary_name ( & manifest. name ) ) ;
209210 }
210211
211212 Ok ( manifest)
@@ -266,3 +267,45 @@ mod serde_bins {
266267 }
267268 }
268269}
270+
271+ /// Determine the default binary name from the package name
272+ ///
273+ /// For non-scoped packages, this is just the package name
274+ /// For scoped packages, to match the behavior of the package managers, we remove the scope and use
275+ /// only the package part, e.g. `@microsoft/rush` would have a default name of `rush`
276+ fn default_binary_name ( package_name : & str ) -> String {
277+ if package_name. starts_with ( '@' ) {
278+ let mut chars = package_name. chars ( ) ;
279+
280+ loop {
281+ match chars. next ( ) {
282+ Some ( '/' ) | None => break ,
283+ _ => { }
284+ }
285+ }
286+
287+ let name = chars. as_str ( ) ;
288+ if name. is_empty ( ) {
289+ package_name. to_string ( )
290+ } else {
291+ name. to_string ( )
292+ }
293+ } else {
294+ package_name. to_string ( )
295+ }
296+ }
297+
298+ #[ cfg( test) ]
299+ mod tests {
300+ use super :: default_binary_name;
301+
302+ #[ test]
303+ fn default_binary_uses_full_name_if_unscoped ( ) {
304+ assert_eq ! ( default_binary_name( "my-package" ) , "my-package" ) ;
305+ }
306+
307+ #[ test]
308+ fn default_binary_removes_scope ( ) {
309+ assert_eq ! ( default_binary_name( "@scope/my-package" ) , "my-package" ) ;
310+ }
311+ }
0 commit comments