Episode 8: Understanding Bin Scripts and Their Use in Yarn Workspaces

What Are Bin Scripts?

Bin scripts are executable files defined within a package that can be invoked:

  • From other packages in the workspace.

  • Globally when installed as a dependency.

They’re commonly used for:

  1. CLIs (Command Line Interfaces), such as Babel, ESLint, or Create React App.

  2. Custom scripts for automating tasks within projects.


How Bin Scripts Work

  1. Definition
    Bin scripts are specified in the package.json of a package under the bin property.

    Example for moduleB:

     {
       "name": "moduleB",
       "version": "1.0.0",
       "bin": {
         "my-script": "./index.js"
       }
     }
    
    • The key (my-script) becomes the command name.

    • The value (./index.js) points to the script file that will run.

  2. Installation

    • Run yarn install (with --force if necessary) to link the bin script.

    • The script will appear in the root node_modules/.bin/ folder as a symlink to the defined file.

  3. Usage

    • The bin script can be run from:

      • The root package.json:

          {
            "scripts": {
              "start": "my-script"
            }
          }
        
      • Other packages in the workspace:

          {
            "scripts": {
              "start": "my-script"
            }
          }
        

Steps to Create and Use a Bin Script

1. Define a Bin Script

In the package.json of the package (e.g., moduleB):

{
  "name": "moduleB",
  "version": "1.0.0",
  "bin": {
    "moduleB-script": "./index.js"
  }
}

2. Add a Shebang to the Script File

At the start of index.js, include the shebang for Node.js:

#!/usr/bin/env node

console.log("Hello from moduleB!");

This tells the terminal to use Node.js to execute the script.


Run:

yarn install --force

This updates the node_modules/.bin/ directory in the root with a symlink for moduleB-script.


4. Run the Script

From the Root package.json

Add a start script:

{
  "scripts": {
    "start": "moduleB-script"
  }
}

Run:

yarn start

From Another Package (e.g., moduleA)

In moduleA/package.json:

{
  "scripts": {
    "start": "moduleB-script"
  }
}

Run from the workspace root:

yarn workspace moduleA start

Customizing Bin Script Names and Adding Multiple Scripts

  1. Custom Command Names
    Instead of using the package name as the default command, specify a custom name:

     {
       "bin": {
         "custom-command": "./index.js"
       }
     }
    
  2. Multiple Commands
    Provide an object with multiple keys and corresponding script paths:

     {
       "bin": {
         "command-one": "./script-one.js",
         "command-two": "./script-two.js"
       }
     }
    

Run yarn install --force again to update the bin directory.


Using Bin Scripts Across the Workspace

Bin scripts are symlinked to the root node_modules/.bin directory, making them accessible:

  • Globally across the monorepo.

  • Without needing to install the package multiple times.

Accessing from Other Packages

Packages in the workspace can directly call the bin script in their own scripts, such as yarn workspace moduleA start.


Troubleshooting

Bin Script Errors

  1. Missing Shebang
    Ensure #!/usr/bin/env node is at the top of the file to specify Node.js for execution.

  2. No Action on yarn install
    Use the --force flag to ensure Yarn recognizes changes to the bin property.

  3. Conflicting Names
    Choose unique command names to avoid conflicts.


Summary

  • Bin scripts enable CLI functionality within and across workspace packages.

  • They are easy to set up using the bin property in package.json.

  • Use the shebang to run JavaScript as a script.

  • Symlinks in node_modules/.bin make scripts accessible globally within the monorepo.

  • You can define custom command names and add multiple bin scripts per package.

0
Subscribe to my newsletter

Read articles from Muhammad Sufiyan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Muhammad Sufiyan
Muhammad Sufiyan

As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021. With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences. In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS. I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.