Leveraging the power of ChatGPT (or any other GenAI chatbot) to build a sophisticated bash automation script for npm-update procedures of your Node.js Git repositories within minutes.
Table of Contents
Why this automation makes sense
You may know the situation: you receive an alert for a severe vulnerability in one or more of your Node.js package dependencies, e.g. via GitHub Dependabot. In many cases, a compatible fix is already available and a simple npm update would do the trick. As long as only one repository is affected, this could be easily done by hand. But if the alert affects dozens or hundreds of repos, it could easily become a very time-consuming task…
For such and other reasons it absolutely makes sense to automate the straight forward npm update procedure including running tests, commiting and pushing to the remote repo in case of success. To do so, we’ll use ChatGPT to create a proper bash script within minutes. Any other GenAI chatbot/LLM would of course also be sufficient.
The goal is to demonstrate that GenAI can…
- Help you indirectly: We use it to built the script, not to do the task itself. This way GenAI is “only” a powerful tool we need exactly once to create the script. No further dependencies to any AI, no passing of sensible data to a LLM or whatsoever.
- Enable you to automate tasks with common techniques in minutes although you don’t need any special knowledge of that technologies. E.g. as a Node.js developer you may not be a Bash guru also you know it’s a perfect tool to solve the task.
Iterative prompting of the script
First, we let ChatGPT create a basic script. In the following steps, this is refined by stating more functionalities ChatGPT should add to the script. This iterative process is a good approach when working with GenAI as it is more likely to achieve better results instead of searching for the perfect one-time prompt.
Prompt #1: Foundational script
Create a Bash script that does the following:
- Take a list of directories that are relative to the path of the script. The paths are Github repositories
- The following should be done for each repository:
- Run “npm update”
- If the package-lock.json file has been modified, continue, otherwise stop
- Run “npm run test”
- If the test ran without errors, stage all changes, commit them with the comment “Dependencies updated” in Git
- Synchronize the local repo with the remote repo so that the commit arrives there
With this prompt defining the main task of the script, ChatGPT should already output a very suitable script for you. Anyways, let’s add two more features to make it even better.
Prompt #2: Logging and status output
Add logging to the script and extend the output by a list of all processed repositories at the end with status: dependencies updated, no updates, errors.
The logging feature enables us to easily write proper defined results for futher processing if needed. Also a final overview with statustracking makes sense to have a brief summary of the scripts execution.
Prompt #3: Configurable repo list
Move the list of repositories to a simple config file.
Finally, we add a bit of configuration with this prompt. Usually you may have different locations on your disk with Git repos that need seperate update cycles or want a simple way to add/remove repos to the update procedure.
The final script
Here’s the final script that was generated with the simple three prompts above. Note that ChatGPT (free version) was also smart enough to integrate a Git rebase in case of concurrent modifications. Also, it added nice add-ons like the allowing for comments in the configuraton. These features were not prompted explicitly but integrated by GenAI itself. So it gives you some extra benefits for free.
Note: As the output of GenAI isn’t deterministic, your script will for sure not exactly be the same.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ---------------------------------------------------------
# Option: --log <file>
# ---------------------------------------------------------
LOGFILE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--log)
LOGFILE="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# ---------------------------------------------------------
# Logging-Function
# ---------------------------------------------------------
log() {
echo "$@"
if [[ -n "$LOGFILE" ]]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') $@" >> "$LOGFILE"
fi
}
# ---------------------------------------------------------
# Load list of relative repository paths
# ---------------------------------------------------------
REPO_CONFIG="$SCRIPT_DIR/repos.conf"
if [[ ! -f "$REPO_CONFIG" ]]; then
echo "❌ Repo config not found: $REPO_CONFIG"
exit 1
fi
mapfile -t REPOS < <(
grep -v '^\s*#' "$REPO_CONFIG" | grep -v '^\s*$'
)
# ---------------------------------------------------------
# Status-Tracking
# ---------------------------------------------------------
declare -A STATUS
# ---------------------------------------------------------
# Main loop
# ---------------------------------------------------------
for REL_PATH in "${REPOS[@]}"; do
REPO_PATH="$SCRIPT_DIR/$REL_PATH"
STATUS["$REL_PATH"]="Error" # Default until success
log ""
log "========================================"
log "→ Processing repository: $REPO_PATH"
log "========================================"
if [[ ! -d "$REPO_PATH" ]]; then
log "⚠️ VPath not existing üskipping"
STATUS["$REL_PATH"]="Error"
continue
fi
cd "$REPO_PATH"
if [[ ! -d ".git" ]]; then
log "⚠️ KNo Git repository – üskipping"
STATUS["$REL_PATH"]="Error"
continue
fi
log "→ npm update…"
npm update
log "→ Checking changes in package-lock.json…"
if git diff --quiet -- package-lock.json; then
log "→ No changes – nothing to do."
STATUS["$REL_PATH"]="No updates"
continue
fi
log "→ Changes found. Running npm run test…"
if ! npm run test; then
log "❌ Tests failed – update stopped."
STATUS["$REL_PATH"]="Error"
continue
fi
log "→ Tests OK. Commit & Push…"
git add .
git commit -m "Dependencies updated"
# Rebase + Push
git pull --rebase
git push
log "✔️ Repo $REL_PATH esuccessfully updated"
STATUS["$REL_PATH"]="Dependencies updated"
done
log ""
log "========================================"
log " Summary for all Repos"
log "========================================"
printf "\n%-40s | %s\n" "Repository" "Status"
printf "%-40s-+-%s\n" "----------------------------------------" "-------------------------"
for REL_PATH in "${REPOS[@]}"; do
printf "%-40s | %s\n" "$REL_PATH" "${STATUS[$REL_PATH]}"
done
printf "\n✨ Done.\n"
Given this, you can ask yourself how long it would have take if you’d created this manually… even if you are a bash pro…
See it in action
Now create a simple repos.conf file in the folder of the script containing the relative paths of your repos and give it a go…
➜ tsmx cat repos.conf
# Repos to do 'npm update' for
json-traverse
json-tools
secure-config
secure-config-tool
human-readable
object-hmac
string-crypto
express-jwt-validator
ws-server
gae-env-secrets
json-tools
weather-tools
kafkajs-file-consumer
kafkajs-file-producer
gcp-get-env
➜ tsmx ./update-deps.sh
...script is working...
========================================
Summary for all Repos
========================================
Repository | Status
-----------------------------------------+--------------------------
json-traverse | Dependencies updated
json-tools | No updates
secure-config | No updates
secure-config-tool | No updates
human-readable | Dependencies updated
object-hmac | Dependencies updated
string-crypto | Dependencies updated
express-jwt-validator | Dependencies updated
ws-server | No updates
gae-env-secrets | No updates
json-tools | No updates
weather-tools | No updates
kafkajs-file-consumer | No updates
kafkajs-file-producer | No updates
gcp-get-env | No updates
✨ Done.
➜ tsmx
Awesome – with just one command we have checked and updated the dependencies of a whole bunch of repos. Imagine you would have done this by hand…
What if my repo doesn’t contain tests?
You may have some repositories that for whatever reason don’t have any tests. Therefore npm run test would fail and so the complete npm update routine in the script for that repos.
To get around this, simply add a no-op as a test script in package.json for Linux/Mac like so…
"scripts": {
"test": "true"
}
…or if you are on Windows (for whatever reason :-P) or need multi-OS compatibility like so…
"scripts": {
"test": "node -e \"process.exit(0)\""
}
Happy updating 🙂