forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathforeach.sh
executable file
·171 lines (150 loc) · 4.99 KB
/
foreach.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/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:
# foreach.sh [-r | --reset]
#
# to force a reset and run a session with the current, run:
# foreach.sh [-r | --reset] [-u | --up || -d | --down] COMMAND
#
# to run the command only against the current module and its dependencies:
# foreach.sh [-u | --up] COMMAND
#
# to run the command only against the current module and its consumers:
# foreach.sh [-d | --down] COMMAND
#
# --------------------------------------------------------------------------------------------------
set -euo pipefail
scriptdir=$(cd $(dirname $0) && pwd)
statedir="${scriptdir}"
statefile="${statedir}/.foreach.state"
commandfile="${statedir}/.foreach.command"
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"
}
function reset {
rm -f "${statedir}/.foreach."*
success "state cleared. you are free to start a new command."
}
DIRECTION=""
RESET=0
SKIP=0
command_arg=""
for arg in "$@"
do
case "$arg" in
-r | --reset) RESET=1 ;;
-s | --skip) SKIP=1 ;;
-u | --up) DIRECTION="UP" ;;
-d | --down) DIRECTION="DOWN" ;;
*) command_arg="$command_arg$arg " ;;
esac
shift
done
if [[ "$RESET" -eq 1 ]]; then
reset
fi
if [[ "$RESET" -eq 1 && "$DIRECTION" == "" ]]; then
exit 0
fi
if [[ "$SKIP" -eq 1 ]]; then
if [ ! -f ${statefile} ]; then
error "skip failed. no active sessions found."
exit 1
fi
next=$(head -1 ${statefile})
if [ -z "${next}" ]; then
error "skip failed. queue is empty. to reset:"
error " $0 --reset"
exit 1
fi
tail -n +2 "${statefile}" > "${statefile}.tmp"
cp "${statefile}.tmp" "${statefile}"
success "directory '$next' skipped. re-run the original foreach command (without --reset) to resume."
exit 0
fi
direction=""
direction_desc=""
if [[ "$DIRECTION" == "UP" || "$DIRECTION" == "DOWN" ]]; then
if [ ! -f package.json ]; then
error "--up or --down can only be executed from within a module directory (looking for package.json)"
exit 1
fi
scope=$(node -p "require('./package.json').name")
if [[ "$DIRECTION" == "UP" ]]; then
direction=" --scope ${scope} --include-dependencies"
direction_desc="('${scope}' and its dependencies)"
else # --down
direction=" --scope ${scope} --include-dependents"
direction_desc="('${scope}' and its consumers)"
fi
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 ${direction_desc}"
${scriptdir}/../node_modules/.bin/lerna ls --all ${direction} --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). reseting queue:"
reset
exit 0
fi
remaining=$(cat ${statefile} | wc -l | xargs -n1)
heading "---------------------------------------------------------------------------------"
heading "${next}: ${command} (${remaining} remaining)"
(
cd ${next}
# special-case "npm run" or "yarn run" - skip any modules that simply don't have
# that script (similar to how "lerna run" behaves)
if [[ "${command}" == "npm run "* ]] || [[ "${command}" == "yarn run "* ]]; then
script="$(echo ${command} | cut -d" " -f3)"
exists=$(node -pe "(require('./package.json').scripts && 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