The implementation of `npm run watch-sass` was trying really hard to recompile precisely only the Sass that needed to be recompiled, but in order to do so, it had to spin up several `watchmedo` processes per theme. These processes would trigger one another sometimes, leading to infinite recompilation loops. Rather than figure out all the dependency directions and messing with `watchmedo`, I've opted to simplify the script to invoke a single `watchmedo` process per theme. A single theme recompiles within seconds, so I think this is a good compromise, one which makes the script easier to reason about will help me move pass this legacy assets work.
130 lines
4.8 KiB
Bash
Executable File
130 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Wait for changes to Sass, and recompile.
|
|
# Invoke from repo root as `npm run watch-sass`.
|
|
|
|
# By default, only watches default Sass.
|
|
# To watch themes too, provide colon-separated paths in the EDX_PLATFORM_THEME_DIRS environment variable.
|
|
# Each path will be treated as a "theme dir", which means that every immediate child directory is watchable as a theme.
|
|
# For example:
|
|
#
|
|
# EDX_PLATFORM_THEME_DIRS=/openedx/themes:./themes npm run watch-sass
|
|
#
|
|
# would watch default Sass as well as /openedx/themes/indigo, /openedx/themes/mytheme, ./themes/red-theme, etc.
|
|
|
|
set -euo pipefail
|
|
|
|
COL_SECTION="\e[1;36m" # Section header color (bold cyan)
|
|
COL_LOG="\e[36m" # Log color (cyan)
|
|
COL_WARNING="\e[1;33m" # Warning (bold yellow)
|
|
COL_ERROR="\e[1;31m" # Error (bold red)
|
|
COL_CMD="\e[35m" # Command echoing (magenta)
|
|
COL_OFF="\e[0m" # Normal color
|
|
|
|
section() {
|
|
# Print a header in bold cyan to indicate sections of output.
|
|
echo -e "${COL_SECTION}$*${COL_OFF}"
|
|
}
|
|
log() {
|
|
# Info line. Indented by one space so that it appears as nested under section headers.
|
|
echo -e " ${COL_LOG}$*${COL_OFF}"
|
|
}
|
|
warning() {
|
|
# Bright yellow warning message.
|
|
echo -e "${COL_WARNING}WARNING: $*${COL_OFF}"
|
|
}
|
|
error() {
|
|
# Bright red error message.
|
|
echo -e "${COL_ERROR}ERROR: $*${COL_OFF}"
|
|
}
|
|
echo_quoted_cmd() {
|
|
# Echo args, each single-quoted, so that the user could copy-paste and run them as a command.
|
|
# Indented by two spaces so it appears as nested under log lines.
|
|
echo -e " ${COL_CMD}$(printf "'%s' " "$@")${COL_OFF}"
|
|
}
|
|
|
|
start_sass_watch() {
|
|
# Start a watch for .scss files in a particular dir. Run in the background.
|
|
# Usage: start_sass_watch NAME_FOR_LOGGING HANDLER_COMMAND WATCH_ROOT_PATHS...
|
|
local name="$1"
|
|
local handler="$2"
|
|
shift 2
|
|
local paths=("$@")
|
|
section "Starting Sass watchers for $name:"
|
|
# Note: --drop means that we should ignore any change events that happen during recompilation.
|
|
# This is good because it means that if you edit 3 files, we won't fire off three simultaneous compiles.
|
|
# It's not perfect, though, because if you change 3 files, only the first one will trigger a recompile,
|
|
# so depending on the timing, your latest changes may or may not be picked up. We accept this as a reasonable
|
|
# tradeoff. Some watcher libraries are smarter than watchdog, in that they wait until the filesystem "settles"
|
|
# before firing off a the recompilation. This sounds nice but did not seem worth the effort for legacy assets.
|
|
local watch_cmd=(watchmedo shell-command -v --patterns '*.scss' --recursive --drop --command "$handler" "${paths[@]}")
|
|
echo_quoted_cmd "${watch_cmd[@]}"
|
|
"${watch_cmd[@]}" &
|
|
}
|
|
|
|
# Verify execution environment.
|
|
if [[ ! -d common/static/sass ]] ; then
|
|
error 'This command must be run from the root of edx-platform!'
|
|
exit 1
|
|
fi
|
|
if ! type watchmedo 1>/dev/null 2>&1 ; then
|
|
error "command not found: watchmedo"
|
|
log "The \`watchdog\` Python package is probably not installed. You can install it with:"
|
|
log " pip install -r requirements/edx/development.txt"
|
|
exit 1
|
|
fi
|
|
|
|
# Reliably kill all child processes when script is interrupted (Ctrl+C) or otherwise terminated.
|
|
trap "exit" INT TERM
|
|
trap "kill 0" EXIT
|
|
|
|
# Watch default Sass.
|
|
# If it changes, then recompile the default theme *and* all custom themes,
|
|
# as custom themes' Sass is based on the default Sass.
|
|
start_sass_watch "default theme" \
|
|
'npm run compile-sass-dev' \
|
|
lms/static/sass \
|
|
lms/static/certificates/sass \
|
|
cms/static/sass \
|
|
common/static \
|
|
node_modules \
|
|
xmodule/assets
|
|
|
|
# Watch each theme's Sass.
|
|
# If it changes, only recompile that theme.
|
|
export IFS=":"
|
|
for theme_dir in ${EDX_PLATFORM_THEME_DIRS:-} ; do
|
|
for theme_path in "$theme_dir"/* ; do
|
|
theme_name="${theme_path#"$theme_dir/"}"
|
|
lms_sass="$theme_path/lms/static/sass"
|
|
cert_sass="$theme_path/lms/static/certificates/sass"
|
|
cms_sass="$theme_path/cms/static/sass"
|
|
theme_watch_dirs=()
|
|
if [[ -d "$lms_sass" ]] ; then
|
|
theme_watch_dirs+=("$lms_sass")
|
|
fi
|
|
if [[ -d "$cert_sass" ]] ; then
|
|
theme_watch_dirs+=("$cert_sass")
|
|
fi
|
|
if [[ -d "$cms_sass" ]] ; then
|
|
theme_watch_dirs+=("$cms_sass")
|
|
fi
|
|
# A directory is a theme if it as LMS Sass *and/or* CMS Sass *and/or* certificate Sass.
|
|
if [[ -n "${theme_watch_dirs[*]}" ]] ; then
|
|
start_sass_watch "theme '$theme_name'" \
|
|
"npm run compile-sass-dev -- -T $theme_dir -t $theme_name --skip-default" \
|
|
"${theme_watch_dirs[@]}"
|
|
fi
|
|
done
|
|
done
|
|
|
|
# Wait until interrupted/terminated.
|
|
sleep infinity &
|
|
echo
|
|
echo "Watching Open edX LMS & CMS Sass for changes."
|
|
echo "Use Ctrl+c to exit."
|
|
echo
|
|
echo
|
|
wait $!
|
|
|