Feat: Adding core-mode and deploy scripts

This commit is contained in:
2026-09-30 12:17:52 +03:00
parent b96fb2800f
commit 92016256ce
2 changed files with 266 additions and 0 deletions
Executable
+146
View File
@@ -0,0 +1,146 @@
#!/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"
Executable
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# Deploy this app to production with the latest boboko-core tag.
#
# 1. Refuses to run with uncommitted work (the server deploys what's pushed,
# nothing else) — only the core wiring files are allowed to be dirty.
# 2. Pulls translation lines added in the Language Lines UI into core's
# seeders (boboko:translations:pull) — in local mode; if any are pulled,
# stops so you can release core first. In repo mode it only checks.
# 3. Switches to repo mode if you're on the local ../boboko-core checkout,
# and restarts the stack either way so the lockfiles pick up the newest
# tag (bin/core-mode repo).
# 4. Commits the lockfile bump, if any, asks for confirmation, pushes.
# 5. Runs `envoy run deploy` (see Envoy.blade.php) against .env.envoy's host.
#
# Leaves you in repo mode — run `bin/core-mode local` to go back.
set -euo pipefail
cd "$(dirname "$0")/.."
fail() { echo "!!! $*" >&2; exit 1; }
[ -f .env.envoy ] || fail ".env.envoy not found — copy .env.envoy.example and fill it in."
branch=$(grep -m1 '^DEPLOY_BRANCH=' .env.envoy | cut -d= -f2-)
host=$(grep -m1 '^DEPLOY_SSH_HOST=' .env.envoy | cut -d= -f2-)
branch=${branch:-master}
[ "$(git branch --show-current)" = "$branch" ] || fail "You're not on $branch (the branch Envoy deploys)."
# Anything tracked and modified other than the four core wiring files would be
# silently left out of the deploy — make that a hard stop instead.
dirty=$(git status --porcelain --untracked-files=no |
grep -vE '^.. (composer\.json|composer\.lock|package\.json|package-lock\.json)$' || true)
if [ -n "$dirty" ]; then
echo "$dirty"
fail "Commit or stash these first — the server only deploys what's pushed."
fi
echo ">>> Fetching origin..."
git fetch origin "$branch"
[ "$(git rev-list --count "HEAD..origin/$branch")" -eq 0 ] || fail "origin/$branch has commits you don't — pull first."
# Translation lines added through the Language Lines UI only reach other apps
# once they're in core's seeders. In local mode, pull them into
# ../boboko-core; in repo mode the command can only report them.
lines_in() { printf '%s\n' "$1" | sed -n 's/^\([0-9][0-9]*\) line(s).*/\1/p' | tail -1; }
if [ "$(bin/core-mode)" = local ]; then
echo ">>> Pulling UI-added translations into ../boboko-core's seeders..."
out=$(bin/dc-core.sh exec -T app php artisan boboko:translations:pull --no-ansi) ||
fail "boboko:translations:pull failed — is the stack running (bin/dc-core.sh up -d)?"
echo "$out"
pulled=$(lines_in "$out")
if [ "${pulled:-0}" -gt 0 ]; then
fail "Pulled $pulled translation line(s) into ../boboko-core. Review, commit, tag and push core, then rerun bin/deploy."
fi
else
echo ">>> Checking for UI-added translations missing from core's seeders..."
if out=$(bin/dc exec -T app php artisan boboko:translations:pull --dry-run --no-ansi 2>&1); then
missing=$(lines_in "$out")
if [ "${missing:-0}" -gt 0 ]; then
echo "$out"
fail "These lines are only in this database — run bin/core-mode local and rerun bin/deploy to pull them into core."
fi
echo " nothing missing"
else
echo " skipped — the installed boboko/core doesn't have boboko:translations:pull yet"
fi
fi
# Switch to repo mode (or just restart if already there) so both lockfiles
# resolve the newest boboko-core tag.
bin/core-mode repo
git diff --quiet HEAD -- composer.json package.json ||
fail "composer.json / package.json differ from HEAD in repo mode — they should only differ by the local/repo toggle."
if grep -A14 '"name": "boboko/core"' composer.lock | grep -q '"type": "path"'; then
fail "composer.lock still points boboko/core at the local path repo."
fi
if grep -A6 '"node_modules/@boboko/core": {' package-lock.json | grep -q '"link": true'; then
fail "package-lock.json still links @boboko/core to the local checkout."
fi
version=$(grep -A1 '"name": "boboko/core"' composer.lock | grep -o '"version": "[^"]*"' | cut -d'"' -f4)
# A tag you created in ../boboko-core but never pushed won't be resolved.
if [ -d ../boboko-core ]; then
latest=$(git -C ../boboko-core tag --sort=-v:refname | head -1)
if [ -n "$latest" ] && [ "$latest" != "v$version" ]; then
echo "!!! Warning: resolved boboko/core $version, but ../boboko-core's newest tag is $latest."
echo "!!! Did you push it? (git -C ../boboko-core push origin $latest)"
fi
fi
if ! git diff --quiet HEAD -- composer.lock package-lock.json; then
echo ">>> Committing lockfiles for boboko/core $version..."
git add composer.lock package-lock.json
git commit -m "Chore: Bumping boboko/core to $version"
fi
echo
echo ">>> Deploying $branch to $host with boboko/core $version."
pending=$(git log --oneline "origin/$branch..HEAD")
if [ -n "$pending" ]; then
echo ">>> Commits to push:"
echo "$pending"
else
echo ">>> Nothing new to push — this redeploys origin/$branch as-is."
fi
echo
read -r -p "Push and deploy? [y/N] " answer
[ "$answer" = y ] || [ "$answer" = Y ] || fail "Aborted."
if [ -n "$pending" ]; then
git push origin "$branch"
fi
vendor/bin/envoy run deploy
echo
echo ">>> Done. You're in repo mode — run bin/core-mode local to go back to your ../boboko-core checkout."