DEV Community

Nikola Stojaković
Nikola Stojaković

Posted on • Originally published at nikolastojakovic.com

Automate boring tasks with scripts in a Node project

One of many things I like about Rails is it's CLI. Scaffolding, generating migrations, creating various types of resources for the application and other stuff is so neat and it's no wonder many frameworks copied it shamelessly.

Once project grows beyond certain point, doing routine tasks becomes tedious. When this happens, I write scripts to automate various jobs. It really helps in maintaining productivity and focusing on important stuff.

Here is a quick way how to do this on any Node project if your framework of choice doesn't already provide some way to write custom commands for the CLI. After we're finished you would be able to do something like this:

npm run script generate migration <migration_name>
Enter fullscreen mode Exit fullscreen mode
  • Create shell script which will serve as an entry point. I typically put it in src/scripts along with the modules of the scripts I'm going to create. Let's call it entry.sh. Don't forget to allow execution of this script with chmod.
#!/usr/bin/env bash
/usr/bin/env node --no-warnings --experimental-specifier-resolution=node ./src/scripts/entry.js "$@"
Enter fullscreen mode Exit fullscreen mode
  • In your package.json add NPM script which will be used for running our custom scripts.
{
  "scripts": {
    "script": "./src/scripts/entry.sh" // our NPM script
  },
}
Enter fullscreen mode Exit fullscreen mode
  • Create entry.js file which will serve as the processor for arguments we'll pass.
import { createMigration } from './migrationManager';

// First two arguments are path to the Node executable and path of this file respectively; let's just ditch them
const usefulArguments = process.argv.slice(2);
const [first, second, ...rest] = usefulArguments;

if (['g', 'generate'].includes(first)) {
  if (second === 'migration') {
    createMigration(...rest);
  }
}
Enter fullscreen mode Exit fullscreen mode

I'm keeping things very simple intentionally; once your list of scripts grows you'd probably like to use some arguments parser for better organization and documentation (e.g. commander, yargs, argparse...).

Support for TypeScript and path aliases

If you've been concerned about the .js extension because your project is using TypeScript don't worry - there is a way to do this with support for TypeScript and path aliases (if you're using them in your project).

  • We'll need to install few development dependencies before we continue with the process.
npm install ts-node tsconfig-paths -D
Enter fullscreen mode Exit fullscreen mode
  • Create a scriptsLoader.js file in the root of your project. This will ensure that your scripts written in TypeScript could be executed and that paths using aliases defined in tsconfig.json will be properly resolved.
import { resolve as resolveTs } from 'ts-node/esm';
import * as tsConfigPaths from 'tsconfig-paths';
import { pathToFileURL } from 'url';

const { absoluteBaseUrl, paths } = tsConfigPaths.loadConfig();
const matchPath = tsConfigPaths.createMatchPath(absoluteBaseUrl, paths);

export function resolve(specifier, ctx, defaultResolve) {
  const match = matchPath(specifier);
  return match
    ? resolveTs(pathToFileURL(`${match}`).href, ctx, defaultResolve)
    : resolveTs(specifier, ctx, defaultResolve);
}

export { load, transformSource } from 'ts-node/esm';
Enter fullscreen mode Exit fullscreen mode
  • Update NPM script so we can pass path to the loader we've just made.
{
  "scripts": {
    "script": "./src/scripts/entry.sh ./scriptsLoader.js"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Update src/scripts/entry.sh to pass path to the loader.
#!/usr/bin/env bash

all_args=("$@")
loader_path="$1"
rest_args=("${all_args[@]:1}")

/usr/bin/env node --no-warnings --experimental-specifier-resolution=node --loader "$loader_path" ./src/scripts/entry.ts "${rest_args[@]}"
Enter fullscreen mode Exit fullscreen mode

And that's it!

Top comments (0)