DEV Community

Cover image for Make your package.json readable again by humans: organizing npm scripts with shell files
Al Hermanson-Albers
Al Hermanson-Albers

Posted on

Make your package.json readable again by humans: organizing npm scripts with shell files

In Node.js projects, the package.json file often serves as a convenient repository for our npm scripts. While one-liners within the scripts section work perfectly fine for simple tasks, the situation becomes less ideal when scripts evolve into complex sequences. Think about scenarios involving multiple steps, conditional logic, robust error handling, or the need for comprehensive logging.

The problem with one-liners

Let's consider a common example: a deploy script within your package.json.

{
  "name": "example-01",
  "description": "A really long one-line deploy script."
  "version": "1.0.0",
  "scripts": {
    "deploy": "rm -rf dist && mkdir dist && cp -r src/* dist && cd dist && zip -r ../my-project.zip * && cd .. && scp my-project.zip user@server:/path/to/deploy"
  }
}
Enter fullscreen mode Exit fullscreen mode

At first glance, this deploy script seems to get the job done. However, it quickly becomes unwieldy. The entire process is crammed into a single line, making it a challenge to read, understand, and maintain.

The problem escalates when you try to introduce error checks and logging:

{
  "name": "example-02",
  "description": "Attempt to add error checks and logging to the one-line deploy script."
  "version": "1.0.0",
  "scripts": {
    "deploy": "[ -d \"dist\" ] || (echo \"Error: 'dist' directory not found.\" && exit 1) && rm -rf dist && echo \"Removed dist directory\" || (echo \"Failed to remove dist directory\" && exit 1) && mkdir dist && echo \"Created dist directory\" || (echo \"Failed to create dist directory\" && exit 1) && cp -r src/* dist && echo \"Copied files to dist\" || (echo \"Failed to copy files to dist\" && exit 1) && cd dist && echo \"Changed directory to dist\" || (echo \"Failed to change directory to dist\" && exit 1) && zip -r ../my-project.zip * && echo \"Created zip archive\" || (echo \"Failed to create zip archive\" && exit 1) && cd .. && echo \"Changed directory back to project root\" || (echo \"Failed to change directory back to project root\" && exit 1) && scp my-project.zip user@server:/path/to/deploy && echo \"Deployed to server\" || (echo \"Failed to deploy to server\" && exit 1)"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, you're dealing with a visually cluttered script filled with escaped double quotes. Even worse, the exit 1 statements within the chained && commands won't actually halt the entire script's execution as intended. This creates a false sense of security and can lead to unexpected issues during deployment.

A cleaner solution: dedicated shell scripts

A straightforward and effective strategy for managing complex npm scripts is to move them out of your package.json and into dedicated shell files. Let's refactor our deploy script to demonstrate this approach:

1. Create a scripts directory

If you don't already have one, create a scripts directory within your project root.

2. Move the script into its own file

Cut the one-liner out of package.json and create a new file named deploy.sh inside the scripts directory:

#!/bin/sh

# Check if the 'dist' directory exists
if [ ! -d "dist" ]; then
    echo "Error: 'dist' directory not found."
    exit 1
fi

# Remove the 'dist' directory
rm -rf dist
if [ $? -ne 0 ]; then
    echo "Failed to remove dist directory"
    exit 1
else
    echo "Removed dist directory"
fi

# Create a new 'dist' directory
mkdir dist
if [ $? -ne 0 ]; then
    echo "Failed to create dist directory"
    exit 1
else
    echo "Created dist directory"
fi

# Copy files from 'src' to 'dist'
cp -r src/* dist
if [ $? -ne 0 ]; then
    echo "Failed to copy files to dist"
    exit 1
else
    echo "Copied files to dist"
fi

# Change directory to 'dist'
cd dist
if [ $? -ne 0 ]; then
    echo "Failed to change directory to dist"
    exit 1
else
    echo "Changed directory to dist"
fi

# Create a zip archive of the 'dist' directory
zip -r ../my-project.zip *
if [ $? -ne 0 ]; then
    echo "Failed to create zip archive"
    exit 1
else
    echo "Created zip archive"
fi

# Change directory back to the project root
cd ..
if [ $? -ne 0 ]; then
    echo "Failed to change directory back to project root"
    exit 1
else
    echo "Changed directory back to project root"
fi

# Deploy the zip archive to the server
scp my-project.zip user@server:/path/to/deploy
if [ $? -ne 0 ]; then
    echo "Failed to deploy to server"
    exit 1
else
    echo "Deployed to server"
fi
Enter fullscreen mode Exit fullscreen mode

3. Update your package.json

Replace the full one-liner script with a call to execute your new deploy.sh file.

{
    "name": "my-react-app",
    "version": "1.0.0",
    "scripts": {
        "deploy": "sh scripts/deploy.sh"
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of this Approach

  • Readability and Maintainability: The shell script is well-organized with proper indentation and comments, making it easier to read, understand, and modify in the future.
  • Robust Error Handling: The exit 1 statements now function correctly, ensuring that the script stops immediately if an error occurs.
  • Separation of Concerns: By keeping complex scripts in separate files, you maintain a cleaner package.json and promote better code organization within your project.

In Conclusion

While package.json is a handy place for simple npm scripts, don't hesitate to leverage dedicated shell scripts for complex tasks. This approach enhances code clarity, maintainability, and error handling, contributing to a smoother development experience.

Top comments (0)