#!/usr/bin/env bash # Switch how this app consumes boboko-core, then restart the dev stack so the # entrypoints re-resolve it. # # bin/core-mode local ../boboko-core checkout (Composer path repo + npm file:) # bin/core-mode repo tagged releases from the forge (Composer VCS repo + npm git+semver) # bin/core-mode print the current mode # # Running it for the mode you're already in skips the file edits but still # restarts the stack — that's how repo mode picks up a newly pushed tag. # # The toggle lives in composer.json ("repositories" <-> "_repositories") and # package.json (@boboko/core in "dependencies" <-> "_dependencies"). The dev # entrypoints run `composer update boboko/*` and `npm update @boboko/core` on # every boot, which rewrites composer.lock / package-lock.json for the mode. set -euo pipefail cd "$(dirname "$0")/.." fail() { echo "!!! $*" >&2; exit 1; } # Escape a string for use as a sed regex / replacement with "|" as delimiter. sed_pattern() { printf '%s' "$1" | sed 's/[][\.*^$|]/\\&/g'; } sed_replacement() { printf '%s' "$1" | sed 's/[\&|]/\\&/g'; } current_mode() { local composer_type npm_dep composer_mode npm_mode # The live block is "repositories" — the leading quote keeps "_repositories" out. composer_type=$(sed -n '/"repositories": \[/,/"type"/p' composer.json | grep -o '"type": "[a-z]*"' | head -1) npm_dep=$(sed -n '/"dependencies": {/,/}/p' package.json | grep -o '"@boboko/core": "[^"]*"' | head -1) case "$composer_type" in *'"path"') composer_mode=local ;; *'"vcs"') composer_mode=repo ;; *) fail "Can't tell the mode from composer.json's \"repositories\" block." ;; esac case "$npm_dep" in *'"file:'*) npm_mode=local ;; *'"git+'*) npm_mode=repo ;; *) fail "Can't tell the mode from package.json's @boboko/core dependency." ;; esac [ "$composer_mode" = "$npm_mode" ] || fail "composer.json ($composer_mode) and package.json ($npm_mode) disagree — fix by hand first." echo "$composer_mode" } swap_files() { # composer.json: swap the key names, values stay where they are. sed -i \ -e 's/"_repositories":/"__core_mode_swap__":/' \ -e 's/"repositories":/"_repositories":/' \ -e 's/"__core_mode_swap__":/"repositories":/' \ composer.json # package.json: swap the two @boboko/core values (live and parked). local a b a=$(grep -o '"@boboko/core": "[^"]*"' package.json | sed -n 1p) b=$(grep -o '"@boboko/core": "[^"]*"' package.json | sed -n 2p) [ -n "$a" ] && [ -n "$b" ] || fail "Expected two @boboko/core entries in package.json." sed -i \ -e "s|$(sed_pattern "$a")|__core_mode_swap__|" \ -e "s|$(sed_pattern "$b")|$(sed_replacement "$a")|" \ -e "s|__core_mode_swap__|$(sed_replacement "$b")|" \ package.json } wrapper_for() { if [ "$1" = local ]; then echo bin/dc-core.sh; else echo bin/dc; fi } # The app healthcheck (php-fpm -t) passes before the entrypoint finishes, so # wait on the entrypoints' own log lines instead: the app's composer block is # done once it clears caches, the vite container's npm block once Vite is up. wait_for_log() { local dc=$1 service=$2 pattern=$3 label=$4 waited=0 printf '>>> Waiting for %s' "$label" until $dc logs --no-color "$service" 2>&1 | grep -q "$pattern"; do if [ -z "$($dc ps -q --status running "$service")" ]; then echo $dc logs --no-color --tail 40 "$service" fail "$service stopped before finishing (logs above)." fi if [ "$waited" -ge 600 ]; then echo $dc logs --no-color --tail 40 "$service" fail "Timed out after 600s waiting for $service (logs above)." fi printf '.' sleep 3 waited=$((waited + 3)) done echo " done" } mode=$(current_mode) if [ $# -eq 0 ]; then echo "$mode" exit 0 fi target=$1 [ "$target" = local ] || [ "$target" = repo ] || fail "Usage: bin/core-mode [local|repo]" if [ "$target" = local ] && [ ! -d ../boboko-core ]; then fail "../boboko-core not found — local mode needs the core checkout next to this repo." fi old_dc=$(wrapper_for "$mode") new_dc=$(wrapper_for "$target") echo ">>> Stopping stack ($mode mode)..." $old_dc down if [ "$mode" != "$target" ]; then echo ">>> Switching composer.json / package.json: $mode -> $target" swap_files [ "$(current_mode)" = "$target" ] || fail "Switch didn't take — check composer.json / package.json." else echo ">>> Already in $target mode, restarting to re-resolve boboko-core" fi # The entrypoints run `composer install` / `npm install` from the lockfiles # BEFORE re-resolving core. Local-mode lockfiles point at ../boboko-core, # which isn't mounted in repo mode, so the install fails. The committed # lockfiles are always repo mode — restore those and let the entrypoints bump # them to the newest tag. if [ "$target" = repo ]; then if grep -A14 '"name": "boboko/core"' composer.lock | grep -q '"type": "path"' || grep -A6 '"node_modules/@boboko/core": {' package-lock.json | grep -q '"link": true'; then echo ">>> Lockfiles still point at ../boboko-core — restoring the committed ones" git checkout HEAD -- composer.lock package-lock.json fi fi echo ">>> Starting stack ($target mode)..." if ! $new_dc up -d; then $new_dc logs --no-color --tail 40 app fail "Stack failed to start (app logs above)." fi wait_for_log "$new_dc" app '\[entrypoint\] Caches cleared' "composer to re-resolve boboko/core" wait_for_log "$new_dc" vite 'ready in' "npm to re-resolve @boboko/core" version=$(grep -A1 '"name": "boboko/core"' composer.lock | grep -o '"version": "[^"]*"' | cut -d'"' -f4) echo ">>> Now in $target mode — boboko/core $version"