#!/usr/bin/env bash
# Fail on error
set -e
# Echo commands
set -x
COOLDOWN="3"
ADDITIONAL_EXCLUDES=""
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--cooldown=*)
if [[ -n "${1#*=}" ]]; then
COOLDOWN="${1#*=}"
fi
;;
--exclude=*)
if [[ -n "${1#*=}" ]]; then
ADDITIONAL_EXCLUDES="${1#*=}"
fi
;;
*)
echo "Error: Unknown argument '$1'"
echo "Usage: $0 [--cooldown=\"3\"] [--exclude=\"package1 package2 package3\"]"
exit 1
;;
esac
shift
done
# Use the Github gh tool to make sure the user is logged in
gh auth status
# Function to check if current branch is a dependency upgrade branch
is_dep_upgrade_branch() {
local current_branch=$(git rev-parse --abbrev-ref HEAD)
[[ "$current_branch" =~ ^dep-upgrades-[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
}
# Set branch name based on current branch or create new one
DATE=$(date +%Y-%m-%d)
if is_dep_upgrade_branch; then
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
echo "Already on dependency upgrade branch: $BRANCH_NAME"
else
BRANCH_NAME="dep-upgrades-$DATE"
fi
# Function to get the last successful step
get_last_step() {
local branch=$1
# Get the commit where this branch diverged from main
local base_commit=$(git merge-base origin/main "$branch")
# Try to get the latest commit message matching the pattern, only looking at commits after the base
local last_commit=$(git log --grep="Dependency upgrades - step [0-9]" --format="%s" "$base_commit..$branch" 2>/dev/null | head -n 1)
if [[ $last_commit =~ step\ ([0-9]) ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "0"
fi
}
# Handle branch checkout/creation
if ! is_dep_upgrade_branch; then
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
echo "Branch $BRANCH_NAME exists remotely"
git fetch origin
git checkout "$BRANCH_NAME"
else
echo "Creating new branch $BRANCH_NAME"
git checkout -b "$BRANCH_NAME"
fi
fi
# Get the last completed step
LAST_STEP=$(get_last_step "$BRANCH_NAME")
echo "Last completed step: $LAST_STEP"
# Exclude known problem packages
EXCLUDE=""
# Append any additional excludes from the command line
if [ -n "$ADDITIONAL_EXCLUDES" ]; then
echo "Adding additional excludes: $ADDITIONAL_EXCLUDES"
EXCLUDE="$EXCLUDE $ADDITIONAL_EXCLUDES"
fi
# @types/node - We specifically don't want to increment major version for Node types since we need to make sure we satisfy backwards compat with the minimum version of Node that we support
# commander - v13 has backwards-incompatible changes which require a decent amount of refactoring to get our current code to work. We are considering migrating off of commander but for now we should just freeze it
# hibp - version 15 is ESM-only and we can't use it until we configure Jest/Babel to work with ESM packages
# jose - version 6+ requires ESM (depending on the precise NodeJS version), holding back until server supports ESM
# node-fetch - version 3+ requires ESM, holding back until server supports ESM
# zod - version 4+ is incompatible with MCP SDK
# uuid - version 12+ requires ESM, holding back until server supports ESM
# next eslint-config-next @next/bundle-analyzer - 16 of course is a non-trivial upgrade
MAJOR_EXCLUDE="@types/node commander hibp jose node-fetch npm zod uuid next eslint-config-next @next/bundle-analyzer"
if [ "$LAST_STEP" -lt 1 ]; then
# First, only upgrade patch and minor versions
# --workspaces - Run on all workspaces
# --root - Runs updates on the root project in addition to specified workspaces
# --upgrade - Overwrite package file with upgraded versions
# --cooldown - Minimum age (in days) for packages to be considered for upgrade
# --reject - Exclude packages matching the given string
# --target - Determines the version to upgrade to
# "minor" - Upgrade to the highest minor version without bumping the major version
# `enginesNode` makes sure that packages can be run against the node requirement specified in the monorepo "engines.node"
npx npm-check-updates --workspaces --root --upgrade --no-deprecated --cooldown "$COOLDOWN" --reject "$EXCLUDE" --target minor --enginesNode
# Commit and push before running NPM install
git add -u .
git commit -s -m "Dependency upgrades - step 1"
git push origin "$BRANCH_NAME"
fi
# Temporarily unset -e flag so that script won't exit early on next error
set +e
# Check if there is a PR for this branch already
# Skip creation if there is, otherwise create the PR for this branch
gh pr view
PR_VIEW_EXIT_CODE=$?
# Set the -e flag back
set -e
if [ "$PR_VIEW_EXIT_CODE" -ne 0 ]; then
echo "No existing PR found, creating PR..."
gh pr create --title "Dependency upgrades $DATE" --body "Dependency upgrades" --draft
else
echo "Existing PR found, skipping create"
fi
if [ "$LAST_STEP" -lt 2 ]; then
# Reinstall all dependencies
./scripts/reinstall.sh --update
# Commit and push after running NPM install
git add -u .
git commit -s -m "Dependency upgrades - step 2"
git push origin "$BRANCH_NAME"
gh pr ready
fi
if [ "$LAST_STEP" -lt 3 ]; then
# Next, optimistically upgrade to the latest versions
# "latest" - Upgrade to whatever the package's "latest" git tag points to.
# `enginesNode` makes sure that packages can be run against the node requirement specified in the monorepo "engines.node"
npx npm-check-updates --workspaces --root --upgrade --no-deprecated --cooldown "$COOLDOWN" --reject "$EXCLUDE $MAJOR_EXCLUDE" --target latest --enginesNode
# Check for changes in the working directory
if git diff --quiet; then
echo "No active changes. Exiting the script."
exit 0
fi
# Commit and push before running NPM install
git add -u .
git commit -s -m "Dependency upgrades - step 3"
git push origin "$BRANCH_NAME"
fi
if [ "$LAST_STEP" -lt 4 ]; then
# Reinstall all dependencies
./scripts/reinstall.sh --update
# Commit and push after running NPM install
git add -u .
git commit -s -m "Dependency upgrades - step 4"
git push origin "$BRANCH_NAME"
fi