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.