#!/bin/bash # -------------------------------------------------------------------------------------------------- # # this wonderful tool allows you to execute a command for all modules in this repo # in topological order, but has the incredible property of being stateful. # this means that if a command fails, you can fix the issue and resume from where # you left off. # # to start a session, run: # foreach.sh COMMAND # # this will execute "COMMAND" for each module in the repo (cwd will be the directory of the module). # if a task fails, it will stop, and then to resume, simply run `foreach.sh` again (with or without the same command). # # to reset the session (either when all tasks finished or if you wish to run a different session), run: # rm -f ~/.foreach.* # # this will effectively delete the state files. # # -------------------------------------------------------------------------------------------------- set -euo pipefail scriptdir=$(cd $(dirname $0) && pwd) statefile="${HOME}/.foreach.state" commandfile="${HOME}/.foreach.command" command_arg="${@:-}" base=$PWD function heading { printf "\e[38;5;81m$@\e[0m\n" } function error { printf "\e[91;5;81m$@\e[0m\n" } function success { printf "\e[32;5;81m$@\e[0m\n" } if [[ "${1:-}" == "--reset" ]]; then rm -f ~/.foreach.* success "state cleared. you are free to start a new command." exit 0 fi if [ -f "${statefile}" ] && [ -f "${commandfile}" ]; then command="$(cat ${commandfile})" if [ ! -z "${command_arg}" ] && [ "${command}" != "${command_arg}" ]; then error "error: there is still an active session for: \"${command}\". to reset:" error " $0 --reset" exit 1 fi fi if [ ! -f "${statefile}" ] && [ ! -f "${commandfile}" ]; then if [ ! -z "${command_arg}" ]; then command="${command_arg}" success "starting new session" node_modules/.bin/lerna ls --all --toposort -p > ${statefile} echo "${command}" > ${commandfile} else error "no active session, use \"$(basename $0) COMMAND\" to start a new session" exit 0 fi fi next="$(head -n1 ${statefile})" if [ -z "${next}" ]; then success "done (queue is empty). to reset:" success " $0 --reset" exit 0 fi remaining=$(cat ${statefile} | wc -l | xargs -n1) heading "---------------------------------------------------------------------------------" heading "${next}: ${command} (${remaining} remaining)" ( cd ${next} # special-case "npm run" - skip any modules that simply don't have # that script (similar to how "lerna run" behaves) if [[ "${command}" == "npm run"* ]]; then script="$(echo ${command} | cut -d" " -f3)" exists=$(node -pe "(require('./package.json').scripts || {}).${script} || ''") if [ -z "${exists}" ]; then echo "skipping (no "${script}" script in package.json)" exit 0 fi fi sh -c "${command}" &> /tmp/foreach.stdio || { cd ${base} cat /tmp/foreach.stdio | ${scriptdir}/path-prefix ${next} error "error: last command failed. fix problem and resume by executing: $0" error "directory: ${next}" exit 1 } ) tail -n +2 "${statefile}" > "${statefile}.tmp" cp "${statefile}.tmp" "${statefile}" exec $0