0

I wrote a script which updates project's dependencies (only minors and patches). I wanted to automate the upgrading process as all versions are precise and as far as I know there is no way to hint npm update that I want to bump everything regardless.

In a nutshell, it takes an output of running npm outdated, builds a list of packages according to criteria and feeds it to npm install at the very end. Everything works as intended, however I was wondering if it could be written in a more concise way, for example without creating a temporary text file? I'm also looking for some general feedback.

P.S. I'm just starting out with bash scripting, so please spare me :D Your advice is very much appreciated!

Here is a sample output of npm outdated:

Package                          Current  Wanted  Latest  Location
@commitlint/cli                    7.5.0   .....   8.0.0    .....
@commitlint/config-conventional    7.5.0   .....   8.0.0    .....
eslint                            5.13.0   .....   6.0.1    .....
eslint-plugin-jsx-a11y             6.2.0   .....   6.2.3    .....
eslint-plugin-react               7.12.4   .....  7.14.2    .....
eslint-plugin-react-hooks          1.6.0   .....   1.6.1    .....

Here is the code:

# Temporary file to hold output of `npm outdated` command
OUTPUT=updates.log
PACKAGES_TO_UPDATE=()

function get_major_version { echo $(echo $1 | grep -o -E '^[0-9]{1,2}'); }

# https://stackoverflow.com/questions/1527049/how-can-i-join-elements-of-an-array-in-bash
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

# Redirect output once.
{
  npm outdated
} > $OUTPUT

wait

{
  # Skip first line as it contains column headers.
  read

  while read package current wanted latest location
    do
      # Filter out major updates.
      if [ "$(get_major_version $current)" = "$(get_major_version $latest)" ]; then
        PACKAGES_TO_UPDATE+=("${package}@latest")
      fi
  done
} < $OUTPUT

npm install "${PACKAGES_TO_UPDATE[@]}"

rm $OUTPUT
Pa Ye
  • 1,779
  • 12
  • 22
  • This script makes an incorrect assumption that once the package developer released a new major version, that they'd never release another minor/patch fix release for an older major version of their library. – Lie Ryan Jul 08 '19 at 12:00
  • 1
    Why not just generate a package.json that just prepends `^` (pin major version) to all the version specifiers? Then you can just `npm install` that as usual. If you already have an existing package.json, it should be fairly straightforward to write a script to parse that and dump the modified version. – Lie Ryan Jul 08 '19 at 12:17
  • Valid point, @LieRyan. And probably your suggestion would be a more preferred approach. Thanks! – Pa Ye Jul 08 '19 at 14:00

1 Answers1

1

Use Process Substitution to get a more concise syntax:

PACKAGES_TO_UPDATE=()

function get_major_version { echo $(echo $1 | grep -o -E '^[0-9]{1,2}'); }
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

while read package current wanted latest location; do
  if [ "$(get_major_version $current)" = "$(get_major_version $latest)" ]; then
    PACKAGES_TO_UPDATE+=("${package}@latest")
  fi
done < <(npm outdated|awk 'NR>1')

npm install "${PACKAGES_TO_UPDATE[@]}"

From bash man:

  Process Substitution
       Process substitution is supported on systems that support named pipes  (FIFOs)  or  the  /dev/fd
       method  of naming open files.  It takes the form of <(list) or >(list).  The process list is run
       with its input or output connected to a FIFO or some file in /dev/fd.  The name of this file  is
       passed  as  an  argument  to the current command as the result of the expansion.  If the >(list)
       form is used, writing to the file will provide input for list.  If the <(list) form is used, the
       file passed as an argument should be read to obtain the output of list.

Explanation

  • npm outdated|awk 'NR>1': here we pipe output of npm outdated to awk which in turn cuts off unwanted headers ('NR>1' means start reading from the second line), so we can avoid the extra read. Let's alias this as the input_process.

  • <(input_process): The process gets executed and its output gets passed to the while read loop, using the same mechanics as if you were reading a file.

Pa Ye
  • 1,779
  • 12
  • 22
Juan Diego Godoy Robles
  • 14,447
  • 2
  • 38
  • 52
  • Thanks for the info! That quote from bash manual doesn't make much sense to me ... Could you please explain in plain English what's happening here => `< <(npm outdated|awk 'NR>1')`? – Pa Ye Jul 08 '19 at 13:55