Showing posts with label scripting. Show all posts
Showing posts with label scripting. Show all posts

Sunday, 29 November 2020

Micro-optimisation #1874: NPM script targets

These days I spend most of my working day writing TypeScript/Node/React apps. I still love (and work with) Scala but in the main, it's the faster-moving Javascript world where most of the changes are taking place. One of the best things about the NPM/Yarn workflow that these apps all share, is the ability to declare "scripts" to shortcut common development tasks. It's not new (make has entered the chat) but it's very flexible and powerful. The only downside is, there's no definitive convention for naming the tasks.

One project might use start (i.e. yarn start) to launch the application in development mode (e.g. with hot-reload and suchlike) while another might use run:local (i.e. yarn run:local) for a similar thing. The upshot being, a developer ends up opening package.json in some way, scrolling down to the scripts stanza and looking for their desired task, before carefully typing it in at the command prompt. Can we do better?


Phase 1: The 's' alias

Utilising the wonderful jq, we can very easily get a very nice first pass at streamlining the flow:
alias s='cat package.json | jq .scripts'
This eliminates scrolliing past all the unwanted noise of the package.json (dependencies, jest configuration, etc etc) and just gives a nice list of the scripts:
john$ s
{
  "build": "rm -rf dist && yarn compile && node scripts/build.js ",
  "compile": "tsc -p .",
  "compile:watch": "tsc --watch",
  "lint": "yarn eslint . --ext .ts",
  "start:dev": "source ./scripts/init_dev.sh && concurrently \"yarn compile:watch\" \"nodemon\"",
  "start": "source ./scripts/init_dev.sh && yarn compile && node dist/index",
  "test": "NODE_ENV=test jest --runInBand",
  "test:watch": "yarn test --watch",
  "test:coverage": "yarn test --coverage"
}
john$ 
A nice start. But now while you can see the list of targets, you've still got to (ugh) type one in.
What if ...

Phase 2: Menu-driven

TIL about the select BASH built-in command which will make an interactive menu out of a list of options. So let's do it!

~/bin/menufy_package_json.sh
#!/bin/bash
  
# Show the scripts in alphabetical order, so as to match the
# numbered options shown later
cat package.json | jq '.scripts | to_entries | sort_by(.key) | from_entries'

SCRIPTS=$(cat package.json | jq '.scripts | keys | .[]' --raw-output)

select script in $SCRIPTS
do
  yarn $script
  break
done
I've got that aliased to sm (for "script menu") so here's what the flow looks like now:
john$ sm
{
  "build": "rm -rf dist && yarn compile && node scripts/build.js ",
  "compile": "tsc -p .",
  "compile:watch": "tsc --watch",
  "lint": "yarn eslint . --ext .ts",
  "start": "source ./scripts/init_dev.sh && yarn compile && node dist/index",
  "start:dev": "source ./scripts/init_dev.sh && concurrently \"yarn compile:watch\" \"nodemon\"",
  "test": "NODE_ENV=test jest --runInBand",
  "test:coverage": "yarn test --coverage",
  "test:watch": "yarn test --watch"
}
1) build	  4) lint	    7) test
2) compile	  5) start	    8) test:coverage
3) compile:watch  6) start:dev	    9) test:watch
#? 9
yarn run v1.21.1
$ yarn test --watch
$ NODE_ENV=test jest --runInBand --watch
... and away it goes. For a typical command like yarn test:watch I've gone from 15 keystrokes plus [Enter] to sm[Enter]9[Enter] => five keystrokes, and that's not even including the time/keystroke saving of showing the potential targets in the first place instead of opening package.json in some way and scrolling. For something I might do tens of times a day, I call that a win!

Sunday, 13 September 2020

Micro-optimisation #9725: Checkout the mainline

Very soon (October 1, 2020) Github will be making main the default branch of all new repositories instead of master. While you make the transition over to the new naming convention, it's handy to have an abstraction over the top for frequently-issued commands. For me, git checkout master is one of my faves, so much so that I've already aliased it to gcm. Which actually makes this easier - main and master start with the same letter...

Now when I issue the gcm command, it'll check if main exists, and if not, try master and remind me that this repo needs to be migrated. Here's the script

~/bin/checkout-main-or-master.sh:
#!/bin/bash

# Try main, else master and warn about outdated branch name

MAIN_BRANCH=`git branch -l | grep main`

if [[ ! -z ${MAIN_BRANCH} ]]; then
  git checkout main
else 
  echo "No main branch found, using master... please fix this repo!"
  git checkout master
fi



I run it using this alias:

alias gcm='~/bin/checkout-main-or-master.sh'

So a typical execution looks like this:

mymac:foo john$ gcm
No main branch found, using master... please fix this repo!
Switched to branch 'master'
Your branch is up to date with 'origin/master'.       
mymac:foo john$ 

Monday, 24 August 2020

Micro-optimisation #6587: Git push to Github

I've said it before; sometimes the best automations are the tiny ones that save a few knob-twirls, keystrokes or (as in this case) a drag-copy-paste, each and every day.

It's just a tiny thing, but I like it when a workflow gets streamlined. If you work on a modern Github-hosted codebase with a Pull-Request-based flow, you'll spend more than a few seconds a week looking at this kind of output, which happens the first time you try to git push to a remote that doesn't have your branch:

mymac:foo john$ git push
fatal: The current branch red-text has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin red-text

mymac:foo john$ git push --set-upstream origin red-text
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (16/16), done.
Writing objects: 100% (24/24), 2.79 KiB | 953.00 KiB/s, done.
Total 24 (delta 9), reused 0 (delta 0)
remote: Resolving deltas: 100% (9/9), completed with 9 local objects.
remote: 
remote: Create a pull request for 'red-text' on GitHub by visiting:
remote:      https://github.com/my-org/foo/pull/new/red-text
remote: 
To https://github.com/my-org/foo.git
 * [new branch]        red-text -> red-text
Branch 'red-text' set up to track remote branch 'red-text' from 'origin'.

The desired workflow is clear and simple:

  • Push and set the upstream as suggested
  • Grab the suggested PR URL
  • Use the Mac open command to launch a new browser window for that URL
... so lets automate it!

~/bin/push-to-github.sh:
#!/bin/bash

# Try and push, catch the suggested action if there is one:

SUGG=`git push --porcelain 2>&1 | grep "git push --set-upstream origin"` 

if [[ ! -z ${SUGG} ]]; then
  echo "Doing suggested: ${SUGG}"

  URL=`${SUGG} --porcelain 2>&1 | grep remote | grep new | grep -o "https.*"`

  if [[ ! -z ${URL} ]]; then
    echo "Opening URL ${URL}"
    open $URL
  else
    echo "No PR URL found, doing nothing"
  fi
fi

I run it using this alias:

alias gpgh='~/bin/push-to-github.sh'

So a typical execution looks like this:

mymac:foo john$ gpgh
Doing suggested:     git push --set-upstream origin mybranch
Opening URL https://github.com/my-org/someproject/pull/new/mybranch        
mymac:foo john$