chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
764
worker-toolkit-stocks-in-the-future/explore/run-app.sh
Normal file
764
worker-toolkit-stocks-in-the-future/explore/run-app.sh
Normal file
@@ -0,0 +1,764 @@
|
||||
#!/bin/bash
|
||||
# run-app — start the source app inside the Explore container with one command.
|
||||
#
|
||||
# Before this existed you had to open two shells into the container and start
|
||||
# the server and client by hand. This wraps that up: it makes sure postgres is
|
||||
# running, starts each process in the background, waits until they're actually
|
||||
# listening, and prints the URL to open plus a login. Logs are written to a
|
||||
# file so the foreground stays clean.
|
||||
#
|
||||
# Usage:
|
||||
# run-app start the app (no-op if it's already running)
|
||||
# run-app --restart stop, then start again
|
||||
# run-app --stop stop the app
|
||||
# run-app --logs follow the server + client logs (Ctrl-C to stop following)
|
||||
# run-app --status show whether the app is running
|
||||
# run-app --help this message
|
||||
set -uo pipefail
|
||||
|
||||
RUN_DIR="/tmp/raccoon-app"
|
||||
mkdir -p "$RUN_DIR"
|
||||
|
||||
CYAN='\033[1;36m'; YELLOW='\033[1;33m'; GRAY='\033[0;90m'; RED='\033[1;31m'; RESET='\033[0m'
|
||||
|
||||
REPO_NAME=$(node -e "try{process.stdout.write(require('/workspace/toolkit.json').repo)}catch{}" 2>/dev/null || true)
|
||||
# Host port the browser uses. The app always binds the container ports (3000 /
|
||||
# 3001); the Explore container publishes them on a host port that defaults per
|
||||
# repo but can be overridden (so more than one container — even of the same
|
||||
# repo — can run at once). That live value is exported into the container as
|
||||
# $EXPLORE_CLIENT_PORT; prefer it, falling back to toolkit.json then 3000 for
|
||||
# older containers built before this var existed.
|
||||
CLIENT_HOST_PORT="${EXPLORE_CLIENT_PORT:-$(node -e "try{process.stdout.write(String(require('/workspace/toolkit.json').explorePorts.clientHost))}catch{process.stdout.write('3000')}" 2>/dev/null || echo 3000)}"
|
||||
|
||||
# --- process helpers ---------------------------------------------------------
|
||||
|
||||
# Is the process recorded in $1 (a pidfile) still alive?
|
||||
_alive() { local pf="$1"; [ -f "$pf" ] && kill -0 "$(cat "$pf" 2>/dev/null)" 2>/dev/null; }
|
||||
|
||||
# Start a backgrounded process group leader so we can later kill the whole
|
||||
# group (vite/tsx spawn children). setsid makes the started process its own
|
||||
# session+group leader; we record its pid (== the group id).
|
||||
_spawn() {
|
||||
local name="$1" workdir="$2" cmd="$3"
|
||||
local log="$RUN_DIR/$name.log" pf="$RUN_DIR/$name.pid"
|
||||
: > "$log"
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
setsid bash -c "cd '$workdir' && exec $cmd" >"$log" 2>&1 &
|
||||
else
|
||||
# Fallback: no setsid (children may outlive a stop; best-effort).
|
||||
( cd "$workdir" && exec $cmd ) >"$log" 2>&1 &
|
||||
fi
|
||||
echo $! > "$pf"
|
||||
}
|
||||
|
||||
# Stop the process recorded in pidfile $1 (and its group, when we have one).
|
||||
_kill_pidfile() {
|
||||
local pf="$1"; [ -f "$pf" ] || return 0
|
||||
local pid; pid=$(cat "$pf" 2>/dev/null || true)
|
||||
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
kill -TERM "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do kill -0 "$pid" 2>/dev/null || break; sleep 0.3; done
|
||||
kill -KILL "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$pf"
|
||||
}
|
||||
|
||||
# Wait (bounded) until something is listening on TCP port $1.
|
||||
_wait_tcp() {
|
||||
local port="$1" tries="${2:-180}" i
|
||||
for ((i = 0; i < tries; i++)); do
|
||||
if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then exec 3>&- 3<&-; return 0; fi
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- actions -----------------------------------------------------------------
|
||||
|
||||
stop_app() {
|
||||
local stopped=0
|
||||
for pf in "$RUN_DIR"/*.pid; do
|
||||
[ -e "$pf" ] || continue
|
||||
_kill_pidfile "$pf"
|
||||
stopped=1
|
||||
done
|
||||
if [ "$stopped" = 1 ]; then printf "${GRAY}Stopped the app.${RESET}\n"; else printf "${GRAY}Nothing to stop.${RESET}\n"; fi
|
||||
}
|
||||
|
||||
status_app() {
|
||||
local any=0
|
||||
for pf in "$RUN_DIR"/*.pid; do
|
||||
[ -e "$pf" ] || continue
|
||||
local name; name=$(basename "$pf" .pid)
|
||||
if _alive "$pf"; then printf " ${GRAY}%-8s${RESET} running (pid %s)\n" "$name" "$(cat "$pf")"; else printf " ${GRAY}%-8s${RESET} not running\n" "$name"; fi
|
||||
any=1
|
||||
done
|
||||
[ "$any" = 1 ] || printf "${GRAY}App is not running.${RESET}\n"
|
||||
}
|
||||
|
||||
logs_app() {
|
||||
local logs=()
|
||||
for lf in "$RUN_DIR"/*.log; do [ -e "$lf" ] && logs+=("$lf"); done
|
||||
if [ "${#logs[@]}" -eq 0 ]; then printf "${GRAY}No logs yet — start the app first with ${RESET}run-app\n"; return 0; fi
|
||||
printf "${GRAY}Following %s (Ctrl-C to stop following; the app keeps running):${RESET}\n" "${logs[*]}"
|
||||
tail -n +1 -f "${logs[@]}"
|
||||
}
|
||||
|
||||
# Start helpers per repo. Each starts the process(es) on their container ports.
|
||||
start_palolo() {
|
||||
_spawn server /workspace/repo/packages/server "node --import=tsx src/server.ts"
|
||||
_spawn client /workspace/repo/packages/client "npx vite --host 0.0.0.0 --port 3000"
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting server (packages/server)\xe2\x80\xa6\n"
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting client (packages/client)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
|
||||
local ok_server=1 ok_client=1
|
||||
_wait_tcp 3001 || ok_server=0
|
||||
_wait_tcp 3000 || ok_client=0
|
||||
if [ "$ok_server" = 1 ] && [ "$ok_client" = 1 ]; then
|
||||
printf " ${CYAN}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${CYAN}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " login ${GRAY}zaniyah@exhalefi.com${RESET} / ${GRAY}test${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET} (server=%s client=%s)\n" "$ok_server" "$ok_client"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/{server,client}.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_zenbill() {
|
||||
_spawn app /workspace/repo "bundle exec rails server -b 0.0.0.0 -p 3000"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails (puma)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
|
||||
if _wait_tcp 3000; then
|
||||
printf " ${YELLOW}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${YELLOW}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " ${GRAY}note: this app routes by subdomain. Plain localhost shows only the${RESET}\n"
|
||||
printf " ${GRAY}Rails welcome page; the real UI needs /etc/hosts entries for${RESET}\n"
|
||||
printf " ${GRAY}app.dev.zenbill.com etc. (see README \xe2\x86\x92 Running the app).${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET}\n"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/app.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_zeta_heimdall() {
|
||||
# API-only Rails app — boots a JSON API on container port 3000 (no separate client).
|
||||
_spawn app /workspace/repo "bundle exec rails server -b 0.0.0.0 -p 3000"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails API (puma)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
|
||||
if _wait_tcp 3000; then
|
||||
printf " ${YELLOW}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " base ${YELLOW}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " ${GRAY}note: this is a JSON API, not a UI \xe2\x80\x94 hit an endpoint (e.g. an auth route)${RESET}\n"
|
||||
printf " ${GRAY}rather than expecting a page in the browser.${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET}\n"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/app.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_zeta_platform() {
|
||||
# Boots BOTH the Rails API and the React client so the full UI comes up.
|
||||
# The client (Create React App, react-scripts 2.1.1) serves the UI on container
|
||||
# :3000 (the published port) and proxies /graphql to the Rails API, which its
|
||||
# package.json "proxy" hardcodes at localhost:5000. So Rails binds :5000 (reached
|
||||
# only from inside the container — the browser talks solely to the client) and the
|
||||
# client binds :3000. rspec doesn't need any of this; it's just the interactive app.
|
||||
#
|
||||
# react-scripts 2.1.1 is webpack-4 era: on Node 17+ its build hashing crashes
|
||||
# without --openssl-legacy-provider. HOST=0.0.0.0 + DANGEROUSLY_DISABLE_HOST_CHECK
|
||||
# let the dev server answer requests arriving via the published host port.
|
||||
# node_modules is the container-local symlink post-create.sh set up; yarn is v1.
|
||||
_spawn server /workspace/repo "env PORT=5000 bundle exec rails server -b 0.0.0.0 -p 5000"
|
||||
_spawn client /workspace/repo "env NODE_OPTIONS=--openssl-legacy-provider BROWSER=none CI=false PORT=3000 HOST=0.0.0.0 DANGEROUSLY_DISABLE_HOST_CHECK=true NODE_PATH=src:src/components/ ./node_modules/.bin/react-app-rewired start"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails API (puma) on :5000\xe2\x80\xa6\n"
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting React client (react-scripts)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up (first client compile takes a minute)\xe2\x80\xa6${RESET}\n"
|
||||
local ok_server=1 ok_client=1
|
||||
_wait_tcp 5000 || ok_server=0
|
||||
_wait_tcp 3000 || ok_client=0
|
||||
if [ "$ok_server" = 1 ] && [ "$ok_client" = 1 ]; then
|
||||
printf " ${CYAN}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${CYAN}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " ${GRAY}note: that URL is the React UI. It proxies GraphQL to the Rails API on${RESET}\n"
|
||||
printf " ${GRAY}:5000 inside the container (reach it directly from a container shell at${RESET}\n"
|
||||
printf " ${GRAY}http://localhost:5000). The DB is schema-loaded but unseeded \xe2\x80\x94 you may need${RESET}\n"
|
||||
printf " ${GRAY}to create an account/records to see much in the UI.${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET} (server=%s client=%s)\n" "$ok_server" "$ok_client"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/{server,client}.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_flaredown() {
|
||||
# Polyglot single-container app: the Rails API (backend/) + the Ember client (frontend/).
|
||||
# The Ember dev server serves the UI on container :3000 (the published port) and proxies
|
||||
# API calls to the Rails backend, which docker-compose runs on :3000 too — here the client
|
||||
# takes :3000, so the API binds :5000 (reached only from inside the container) and the
|
||||
# client proxies to it. rspec needs neither the client nor the running server. Node 14
|
||||
# (from nvm) drives ember-cli; Ruby 3.2.3 is the image default. OPENSSL_CONF=/dev/null
|
||||
# lets the old webpack md4 hashing run on bookworm's OpenSSL 3.
|
||||
local NODE14_BIN
|
||||
NODE14_BIN=$(ls -d /usr/local/nvm/versions/node/v14.* 2>/dev/null | sort -V | tail -1)/bin
|
||||
_spawn server /workspace/repo/backend "env PORT=5000 bundle exec rails server -b 0.0.0.0 -p 5000"
|
||||
_spawn client /workspace/repo/frontend "env PATH=$NODE14_BIN:\$PATH OPENSSL_CONF=/dev/null ./node_modules/.bin/ember serve --port 3000 --proxy http://localhost:5000"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails API (puma) on :5000\xe2\x80\xa6\n"
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting Ember client (ember-cli)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up (first Ember build takes a minute)\xe2\x80\xa6${RESET}\n"
|
||||
local ok_server=1 ok_client=1
|
||||
_wait_tcp 5000 || ok_server=0
|
||||
_wait_tcp 3000 || ok_client=0
|
||||
if [ "$ok_server" = 1 ] && [ "$ok_client" = 1 ]; then
|
||||
printf " ${CYAN}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${CYAN}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " ${GRAY}note: that URL is the Ember UI; it proxies API calls to the Rails backend on${RESET}\n"
|
||||
printf " ${GRAY}:5000 inside the container. The DBs (Postgres + MongoDB) are migrated but${RESET}\n"
|
||||
printf " ${GRAY}unseeded \xe2\x80\x94 register a user in the UI to see much.${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET} (server=%s client=%s)\n" "$ok_server" "$ok_client"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/{server,client}.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_breezy_complete() {
|
||||
# Monorepo: Rails API (backend/, container :3001) + Next.js frontend (frontend/,
|
||||
# container :3000). The offline Clerk-bypass env (DISABLE_CLERK etc.) is injected
|
||||
# HERE, not baked into the image, so a worker's bare `bundle exec rspec` keeps
|
||||
# upstream CI's env (ambient DISABLE_CLERK 403s several controller specs).
|
||||
# NEXT_PUBLIC_BACKEND_URL must be the HOST-visible backend URL — the browser
|
||||
# calls it — so derive it from the live published server port. Sidekiq is not
|
||||
# started (only needed for background-job behavior; LLM-dependent jobs degrade
|
||||
# keyless anyway).
|
||||
local server_host_port
|
||||
server_host_port="${EXPLORE_SERVER_PORT:-$(node -e "try{process.stdout.write(String(require('/workspace/toolkit.json').explorePorts.serverHost))}catch{process.stdout.write('4001')}" 2>/dev/null || echo 4001)}"
|
||||
_spawn server /workspace/repo/backend "env DISABLE_CLERK=true CLERK_SKIP_RAILTIE=true bundle exec rails server -b 0.0.0.0 -p 3001"
|
||||
_spawn client /workspace/repo/frontend "env NEXT_PUBLIC_BACKEND_URL=http://localhost:${server_host_port} npm run dev -- -H 0.0.0.0 -p 3000"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails API (backend/) on :3001\xe2\x80\xa6\n"
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting Next.js frontend (frontend/)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
|
||||
local ok_server=1 ok_client=1
|
||||
_wait_tcp 3001 || ok_server=0
|
||||
_wait_tcp 3000 || ok_client=0
|
||||
if [ "$ok_server" = 1 ] && [ "$ok_client" = 1 ]; then
|
||||
printf " ${CYAN}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${CYAN}http://localhost:%s/pro_signin${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
printf " ${GRAY}auth is bypassed offline \xe2\x80\x94 /pro_signin auto-redirects to the seeded${RESET}\n"
|
||||
printf " ${GRAY}professional's dashboard (no login needed). Enter via /pro_signin, not a${RESET}\n"
|
||||
printf " ${GRAY}bookmarked dashboard URL \xe2\x80\x94 those embed a token that changes on re-seed.${RESET}\n"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET} (server=%s client=%s)\n" "$ok_server" "$ok_client"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/{server,client}.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
# ---- polyglot mode -----------------------------------------------------------
|
||||
# A polyglot toolkit (toolkit.json .polyglot=true) hosts many member repos under
|
||||
# repos/<slug>/. The worker picks one with `run-app <repo>`; its deps + DB install on
|
||||
# first use (deferred), then its app boots on container port 3000. Dispatch is
|
||||
# RUNTIME-DRIVEN: each member carries a `runtime` ("ruby:3.2.1" | "node:16" |
|
||||
# "python:3.10" | "none") and an optional `startCmd` in toolkit.json, so there is no
|
||||
# per-repo hardcoding (scales to all repos). rbenv/pyenv shims must be on PATH inside
|
||||
# the backgrounded process (a non-login shell), hence the explicit env prefixes.
|
||||
RBENV_PATH='/usr/local/rbenv/shims:/usr/local/rbenv/bin'
|
||||
PYENV_PATH='/usr/local/pyenv/shims:/usr/local/pyenv/bin'
|
||||
_is_polyglot() { node -e "try{process.exit(require('/workspace/toolkit.json').polyglot?0:1)}catch{process.exit(1)}" 2>/dev/null; }
|
||||
_poly_repos() { node -e "require('/workspace/toolkit.json').repos.forEach(r=>console.log(r.repo))" 2>/dev/null; }
|
||||
_poly_default() { node -e "process.stdout.write(require('/workspace/toolkit.json').defaultRepo||'')" 2>/dev/null; }
|
||||
# _poly_field <repo> <field> → the member's field value ('' if absent). Args passed via
|
||||
# argv (not interpolated) so a repo name can't break the JS.
|
||||
_poly_field() { node -e "const r=require('/workspace/toolkit.json').repos.find(x=>x.repo===process.argv[1]);process.stdout.write(r&&r[process.argv[2]]!=null?String(r[process.argv[2]]):'')" "$1" "$2" 2>/dev/null; }
|
||||
# Is rbenv/pyenv version <ver> installed in this image? (EOL runtimes won't be.)
|
||||
_rb_have() { [ -d "/usr/local/rbenv/versions/$1" ]; }
|
||||
_py_have() { [ -d "/usr/local/pyenv/versions/$1" ]; }
|
||||
# Newer estate images ship Python via uv (a system python3 + `uv`) instead of pyenv.
|
||||
# True when there's no pyenv build for <ver> but uv can provide it — the python arms
|
||||
# then fall back to a container-local uv venv per member.
|
||||
_py_uv_ok() { ! _py_have "$1" && command -v uv >/dev/null 2>&1; }
|
||||
_uv_venv_dir() { printf '/opt/raccoon-venvs/%s' "$1"; }
|
||||
# Node is multi-version via nvm. Resolve a member's node spec (e.g. "16" or
|
||||
# "16.20.2") to that major's installed node bin dir, or '' if that major isn't in
|
||||
# the image (so the selector can fall back to explore-only). Picks the highest
|
||||
# installed patch of the requested major.
|
||||
_node_bin() {
|
||||
local major="${1%%.*}" nvm_dir="${NVM_DIR:-/usr/local/nvm}" d
|
||||
d=$(ls -d "$nvm_dir"/versions/node/v"$major".* 2>/dev/null | sort -V | tail -1)
|
||||
[ -n "$d" ] && printf '%s/bin' "$d"
|
||||
}
|
||||
# Symlink ./node_modules (cwd = the dir being installed) to a container-local tree keyed by
|
||||
# <key> — see the ENFILE rationale at the call site. The target must itself be named
|
||||
# `node_modules` (Node resolves the symlink, then walks ancestors for that literal name),
|
||||
# and its parent needs a stub manifest: postinstall scripts that locate the project by
|
||||
# truncating their realpath at `node_modules` require() `<parent>/package.json`.
|
||||
_nm_link() {
|
||||
local root="/opt/raccoon-node-modules/$1"
|
||||
[ -L node_modules ] || rm -rf node_modules
|
||||
mkdir -p "$root/node_modules"
|
||||
[ -f "$root/package.json" ] \
|
||||
|| printf '{"name":"raccoon-node-modules-root","version":"0.0.0","private":true}\n' > "$root/package.json"
|
||||
ln -sfn "$root/node_modules" node_modules
|
||||
}
|
||||
# Rewrite poetry deps of the form `<pkg> = { git = "ssh://git@github.com/AskZeta/<name>.git", rev=… }`
|
||||
# in <pyproject.toml> to a local path dep at /workspace/repos/zeta-<name>. The sibling repo is a
|
||||
# member of this toolkit, so the path resolves offline (no SSH key / network needed).
|
||||
# NB: uses `|` as the s/// delimiter, NOT `{}` — the pattern has `[^}]` and the replacement has
|
||||
# `{ … }`, which break perl's brace-balanced delimiter parsing.
|
||||
_rewrite_askzeta_git_deps() {
|
||||
perl -i -pe 's|=\s*\{\s*git\s*=\s*"ssh://git\@github\.com/AskZeta/([^"]+?)(?:\.git)?"\s*,[^}]*\}|= { path = "/workspace/repos/zeta-\L$1\E", develop = false }|g' "$1" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Create + schema-load EVERY database of a multi-DB Rails app for one RAILS_ENV ($1).
|
||||
#
|
||||
# Rails only defines the namespaced `db:schema:load:<name>` tasks when more than one config
|
||||
# is VISIBLE to rake, and a config marked `database_tasks: false` is hidden from
|
||||
# `configs_for`. zeta-plastic marks `source` hidden in development and BOTH connections
|
||||
# hidden in test, so it has no namespaced tasks at all: the commands below fail with
|
||||
# `UnrecognizedCommandError`, and plain `db:schema:load` can't reach the extra DB anyway.
|
||||
# So: try the namespaced path (px-api has it), else walk the configs ourselves.
|
||||
#
|
||||
# NEVER db:migrate — its implicit schema:dump regenerates db/source_schema.rb from the
|
||||
# near-empty source DB, truncating the real file (3487 -> ~77 lines).
|
||||
_multidb_setup_env() {
|
||||
local e="$1"
|
||||
RAILS_ENV="$e" DISABLE_SPRING=1 bundle exec rails db:create 2>/dev/null
|
||||
if RAILS_ENV="$e" DISABLE_SPRING=1 bundle exec rails db:schema:load:primary >/dev/null 2>&1; then
|
||||
RAILS_ENV="$e" DISABLE_SPRING=1 bundle exec rails db:schema:load:source >/dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
# Fallback: create and load each config, hidden ones included. Two traps, both hit in
|
||||
# practice on zeta-plastic: (1) `create` raises DatabaseAlreadyExists once the db:create
|
||||
# above has made the primary DB, and that path leaves ActiveRecord connected to the
|
||||
# `postgres` MAINTENANCE database; (2) load_schema does not connect on its own (Rails
|
||||
# 7.2) — it loads into whatever connection is current. Without the explicit
|
||||
# establish_connection below, the app's tables get created inside `postgres` and the
|
||||
# real DB is left empty, with every command still reporting success.
|
||||
# Ruby goes to a real temp file, not /dev/stdin — `rails runner` Kernel.loads the path,
|
||||
# which needs a seekable file, and a heredoc is a pipe on some shells.
|
||||
local rb; rb=$(mktemp /tmp/raccoon-load-schemas.XXXXXX.rb)
|
||||
cat > "$rb" <<'RUBY'
|
||||
ActiveRecord::Base.configurations
|
||||
.configs_for(env_name: Rails.env, include_hidden: true)
|
||||
.reject(&:replica?).each do |c|
|
||||
begin
|
||||
ActiveRecord::Tasks::DatabaseTasks.create(c)
|
||||
rescue ActiveRecord::DatabaseAlreadyExists, ActiveRecord::StatementInvalid
|
||||
end
|
||||
dump = c.schema_dump || "schema.rb"
|
||||
file = Rails.root.join("db", dump)
|
||||
next unless File.exist?(file)
|
||||
ActiveRecord::Base.establish_connection(c)
|
||||
ActiveRecord::Tasks::DatabaseTasks.load_schema(c, :ruby, file.to_s)
|
||||
puts "loaded db/#{dump} -> #{c.database}"
|
||||
end
|
||||
RUBY
|
||||
# "already exists" is expected for the DB db:create just made — not worth showing.
|
||||
RAILS_ENV="$e" DISABLE_SPRING=1 bundle exec rails runner "$rb" 2>&1 \
|
||||
| grep -v "already exists" | sed 's/^/ /'
|
||||
rm -f "$rb"
|
||||
return 0
|
||||
}
|
||||
|
||||
# First-use setup for a member repo: checkout its commit, install deps, prepare DB.
|
||||
# Idempotent via a marker file. Runtime-driven; the marker is written only on success.
|
||||
setup_repo() {
|
||||
local repo="$1" dir="/workspace/repos/$1" marker="/workspace/repos/$1/.raccoon-setup-done"
|
||||
[ -f "$marker" ] && return 0
|
||||
local commit runtime kind ver bootenv setupcmd
|
||||
commit=$(_poly_field "$repo" defaultCommit)
|
||||
runtime=$(_poly_field "$repo" runtime); kind=${runtime%%:*}; ver=${runtime#*:}
|
||||
# A member's optional bootEnv ("KEY=val KEY2=val2") supplies dummy values for vars an
|
||||
# app reads at class-load that its .env.example omits (e.g. wasabi-platform's IVR_UN/
|
||||
# IVR_PW). dotenv does NOT reliably load .env into the rspec process for some apps, so
|
||||
# the load-bearing channel is real shell exports in ~/.bashrc (below) — the worker's
|
||||
# `bundle exec rspec` then sees them. The .env append (in each runtime case) is belt-
|
||||
# and-suspenders for dotenv-loading apps. Keeps real secrets out; just unblocks boot.
|
||||
bootenv=$(_poly_field "$repo" bootEnv)
|
||||
# A member's optional setupCmd runs ONCE here, after deps are installed, for one-time
|
||||
# app preparation that isn't boot (schema push, seeding, generating a gitignored asset).
|
||||
# It belongs here rather than in startCmd: startCmd runs on every `run-app`, so seeding
|
||||
# from there re-runs on each boot and its output is mixed into the server log. Failure
|
||||
# is non-fatal (a warning) — a member that can still be explored shouldn't be blocked by
|
||||
# a seed hiccup, mirroring the `|| true` seeds in post-create.sh for single-repo kits.
|
||||
setupcmd=$(_poly_field "$repo" setupCmd)
|
||||
if [ -n "$commit" ] && ! git -C "$dir" -c advice.detachedHead=false checkout "$commit" >/dev/null 2>&1; then
|
||||
printf "${RED}checkout %s failed for %s${RESET}\n" "$commit" "$repo"; return 1
|
||||
fi
|
||||
# Keep the setup marker out of `git status` — and out of snapshot patches, which
|
||||
# capture the worker's repo state (mirrors post-create's .pnpm-store exclude; the
|
||||
# create-snapshot checkpoint hook excludes it as well).
|
||||
mkdir -p "$dir/.git/info"
|
||||
grep -qxF '.raccoon-setup-done' "$dir/.git/info/exclude" 2>/dev/null \
|
||||
|| printf '\n# raccoon-explore: run-app first-use setup marker\n.raccoon-setup-done\n' >> "$dir/.git/info/exclude"
|
||||
# Persist bootEnv as real exports for ALL the worker's container shells (deduped per repo).
|
||||
if [ -n "$bootenv" ] && ! grep -q "raccoon-bootenv:$repo" "$HOME/.bashrc" 2>/dev/null; then
|
||||
{ echo "# raccoon-bootenv:$repo"; for kv in $bootenv; do echo "export $kv"; done; } >> "$HOME/.bashrc"
|
||||
fi
|
||||
printf " ${GRAY}first-time setup for %s (%s) \xe2\x80\x94 runs once\xe2\x80\xa6${RESET}\n" "$repo" "${runtime:-explore-only}"
|
||||
case "$kind" in
|
||||
ruby)
|
||||
_rb_have "$ver" || { printf " ${GRAY}(Ruby %s not in this image; skipping deps \xe2\x80\x94 explore-only)${RESET}\n" "$ver"; touch "$marker"; return 0; }
|
||||
( cd "$dir" \
|
||||
&& export PATH="$RBENV_PATH:$PATH" RBENV_VERSION="$ver" \
|
||||
&& { [ -f config/database.yml.example ] && cp -n config/database.yml.example config/database.yml; true; } \
|
||||
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
|
||||
&& { [ -n "$bootenv" ] && printf '%s\n' $bootenv >> .env; true; } \
|
||||
&& { bundle lock --add-platform x86_64-linux aarch64-linux >/dev/null 2>&1 || true; } \
|
||||
&& { bundle install || bundle install --full-index; } \
|
||||
&& { if [ -f db/source_schema.rb ]; then \
|
||||
# MULTI-DATABASE (px-api, plastic): the `users` etc. live in the `source` DB.
|
||||
# Load EACH db's schema for dev AND test. DISABLE_SPRING so a preloaded
|
||||
# stale connection doesn't make the source load a silent no-op.
|
||||
for e in development test; do \
|
||||
_multidb_setup_env "$e" || true; \
|
||||
done; \
|
||||
else \
|
||||
# Single-DB: prepare the dev DB (rails_helper often needs it present), then
|
||||
# load + migrate the test DB (migrate is a no-op when schema.rb is current,
|
||||
# and applies pending migrations when it's stale).
|
||||
bundle exec rails db:prepare 2>/dev/null || bundle exec rails db:create db:schema:load 2>/dev/null || true; \
|
||||
RAILS_ENV=test bundle exec rails db:create 2>/dev/null; \
|
||||
RAILS_ENV=test bundle exec rails db:schema:load 2>/dev/null; \
|
||||
RAILS_ENV=test bundle exec rails db:migrate 2>/dev/null || true; \
|
||||
fi; } ) || return 1 ;;
|
||||
node)
|
||||
nbin=$(_node_bin "$ver")
|
||||
[ -z "$nbin" ] && { printf " ${GRAY}(Node %s not in this image; skipping deps \xe2\x80\x94 explore-only)${RESET}\n" "$ver"; touch "$marker"; return 0; }
|
||||
# Install node_modules to a CONTAINER-LOCAL path, not the bind-mounted repo dir. On
|
||||
# macOS Docker Desktop the repo is a host bind mount; writing a huge node_modules tree
|
||||
# across the file-sharing layer is slow AND exhausts the HOST's open-file table (ENFILE
|
||||
# "file table overflow"), which can take the whole machine down — not just the install.
|
||||
# Keeping node_modules inside the Linux VM confines that churn to the VM. The repo stays
|
||||
# bind-mounted (the worker sees their edits); node_modules is reached via a symlink.
|
||||
( cd "$dir" \
|
||||
&& export PATH="$nbin:$PATH" \
|
||||
&& _nm_link "$repo" \
|
||||
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
|
||||
&& { [ -n "$bootenv" ] && printf '%s\n' $bootenv >> .env; true; } \
|
||||
&& { if [ -f yarn.lock ]; then yarn install; elif [ -f package-lock.json ]; then npm install; else yarn install; fi; } ) || return 1 ;;
|
||||
python)
|
||||
if _py_uv_ok "$ver"; then
|
||||
# uv-python image (clockwise-polyglot era): container-local venv per member,
|
||||
# deps via uv. `uv pip install -e .` handles poetry-backend pyprojects too.
|
||||
local vdir; vdir=$(_uv_venv_dir "$repo")
|
||||
( cd "$dir" \
|
||||
&& uv venv "$vdir" -p "$ver" -q \
|
||||
&& . "$vdir/bin/activate" \
|
||||
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
|
||||
&& { [ -n "$bootenv" ] && printf '%s\n' $bootenv >> .env; true; } \
|
||||
&& { if [ -f pyproject.toml ]; then uv pip install -q -e . || uv pip install -q -r requirements.txt 2>/dev/null || true; \
|
||||
elif [ -f requirements.txt ]; then uv pip install -q -r requirements.txt; \
|
||||
elif [ -f server/requirements.txt ]; then uv pip install -q -r server/requirements.txt; \
|
||||
elif [ -f setup.py ]; then uv pip install -q -e .; else true; fi; } ) || return 1
|
||||
touch "$marker"; return 0
|
||||
fi
|
||||
_py_have "$ver" || { printf " ${GRAY}(Python %s not in this image; skipping deps \xe2\x80\x94 explore-only)${RESET}\n" "$ver"; touch "$marker"; return 0; }
|
||||
# Some poetry repos depend on sibling repos via `git = "ssh://git@github.com/AskZeta/<name>.git"`,
|
||||
# which can't resolve in the container (no SSH key, no network). The deps are TRANSITIVE
|
||||
# (cx-chatbot → compiler-agent → agent-tools → leaves), so rewrite the target AND every
|
||||
# sibling pyproject to local path deps — else poetry shells out to `ssh` for a transitive
|
||||
# git dep and fails ("No such file or directory: 'ssh'").
|
||||
for pp in /workspace/repos/*/pyproject.toml; do
|
||||
[ -f "$pp" ] && _rewrite_askzeta_git_deps "$pp"
|
||||
done
|
||||
( cd "$dir" && export PATH="$PYENV_PATH:$PATH" PYENV_VERSION="$ver" \
|
||||
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
|
||||
&& { [ -n "$bootenv" ] && printf '%s\n' $bootenv >> .env; true; } \
|
||||
&& { if [ -f pyproject.toml ]; then \
|
||||
# The git→path rewrite invalidates poetry.lock ("changed significantly");
|
||||
# regenerate it before installing. Poetry 2.x `lock` preserves pins by
|
||||
# default (the old `--no-update` flag was removed in 2.0).
|
||||
poetry lock 2>/dev/null || true; \
|
||||
# --no-root: install deps only, not the project package itself. Some members'
|
||||
# pyproject package name doesn't map to a folder poetry can find ("No file/folder
|
||||
# found for package <x>"), which fails the whole install. The worker explores +
|
||||
# runs the code from the repo dir (cwd on path), so the project never needs to be
|
||||
# pip-installed as a package. Mirrors the harbor build.
|
||||
poetry install --no-interaction --no-root; \
|
||||
elif [ -f requirements.txt ]; then pip install -r requirements.txt; \
|
||||
elif [ -f setup.py ]; then pip install -e .; else true; fi; } ) || return 1 ;;
|
||||
rust)
|
||||
command -v cargo >/dev/null 2>&1 || { printf " ${GRAY}(Rust not in this image; skipping build \xe2\x80\x94 explore-only)${RESET}\n"; touch "$marker"; return 0; }
|
||||
# Build to a container-local target dir (same ENFILE/bind-mount rationale as
|
||||
# node_modules): a Cargo workspace target tree is huge and rebuilds often.
|
||||
( cd "$dir" \
|
||||
&& { [ -f .env.example ] && cp -n .env.example .env; true; } \
|
||||
&& { [ -n "$bootenv" ] && printf '%s\n' $bootenv >> .env; true; } \
|
||||
&& CARGO_TARGET_DIR="/opt/raccoon-cargo-target/$repo" cargo build --workspace ) || return 1 ;;
|
||||
none|"") : ;; # no-code / explore-only: nothing to install
|
||||
*) printf "${YELLOW}unknown runtime '%s' for %s \xe2\x80\x94 explore-only${RESET}\n" "$runtime" "$repo" ;;
|
||||
esac
|
||||
# Optional one-time app preparation (see setupCmd above), with the member's runtime on
|
||||
# PATH and its bootEnv exported — same environment the app boots with.
|
||||
if [ -n "$setupcmd" ]; then
|
||||
local spath=""
|
||||
case "$kind" in
|
||||
ruby) spath="$RBENV_PATH" ;;
|
||||
node) spath=$(_node_bin "$ver") ;;
|
||||
python) spath="$PYENV_PATH" ;;
|
||||
esac
|
||||
# Output goes to a log, not the worker's terminal: preparation is chatty (an app's
|
||||
# own seed can log hundreds of lines about services it can't reach offline, all of
|
||||
# them harmless), and a wall of red JSON reads as "something is broken". The log
|
||||
# lands in RUN_DIR so `run-app --logs` picks it up like any other.
|
||||
mkdir -p "$RUN_DIR"
|
||||
local slog="$RUN_DIR/setup-$repo.log"
|
||||
printf " ${GRAY}preparing %s (one-time; details in ${RESET}${GRAY}run-app --logs${RESET}${GRAY})\xe2\x80\xa6${RESET}\n" "$repo"
|
||||
if ( cd "$dir" \
|
||||
&& export PATH="${spath:+$spath:}$PATH" \
|
||||
&& case "$kind" in ruby) export RBENV_VERSION="$ver" ;; python) export PYENV_VERSION="$ver" ;; esac \
|
||||
&& { for kv in $bootenv; do export "$kv"; done; } \
|
||||
&& eval "$setupcmd" ) > "$slog" 2>&1; then
|
||||
printf " ${GRAY}\xe2\x9c\x93 %s prepared${RESET}\n" "$repo"
|
||||
else
|
||||
printf " ${YELLOW}setup for %s did not finish cleanly \xe2\x80\x94 the repo is still explorable.${RESET}\n" "$repo"
|
||||
printf " ${GRAY}what went wrong: %s${RESET}\n" "$slog"
|
||||
fi
|
||||
fi
|
||||
touch "$marker"
|
||||
}
|
||||
|
||||
start_poly() {
|
||||
local repo="${1:-}"; [ -z "$repo" ] && repo="$(_poly_default)"
|
||||
if ! _poly_repos | grep -qx "$repo"; then
|
||||
printf "${RED}unknown repo '%s'.${RESET} available: ${GRAY}%s${RESET}\n" "$repo" "$(_poly_repos | tr '\n' ' ')"
|
||||
return 1
|
||||
fi
|
||||
local dir="/workspace/repos/$repo" runtime kind ver startcmd bootenv
|
||||
runtime=$(_poly_field "$repo" runtime); kind=${runtime%%:*}; ver=${runtime#*:}
|
||||
startcmd=$(_poly_field "$repo" startCmd)
|
||||
bootenv=$(_poly_field "$repo" bootEnv) # dummy class-load vars (e.g. IVR_UN); see setup_repo
|
||||
# Explore-only members (no-code repos, or no runtime): nothing to boot.
|
||||
if [ "$kind" = "none" ] || [ -z "$kind" ]; then
|
||||
printf " ${CYAN}%s${RESET} is explore-only (no app to run). Read it under ${GRAY}/workspace/repos/%s${RESET}.\n" "$repo" "$repo"
|
||||
return 0
|
||||
fi
|
||||
# Runtime not in this image (EOL Ruby 2.6.6 / Python 3.7 / an uninstalled node major):
|
||||
# explorable, not runnable here. Python counts as present when EITHER pyenv has the
|
||||
# version or uv can provide it (uv-python images ship no pyenv at all — without the
|
||||
# _py_uv_ok check this gate refused every python member before the uv setup arm ran).
|
||||
if { [ "$kind" = ruby ] && ! _rb_have "$ver"; } \
|
||||
|| { [ "$kind" = python ] && ! _py_have "$ver" && ! _py_uv_ok "$ver"; } \
|
||||
|| { [ "$kind" = node ] && [ -z "$(_node_bin "$ver")" ]; }; then
|
||||
printf " ${YELLOW}%s needs %s, which isn't in this image.${RESET}\n" "$repo" "$runtime"
|
||||
printf " Explore the code under ${GRAY}/workspace/repos/%s${RESET}; to RUN it use that repo's dedicated toolkit.\n" "$repo"
|
||||
return 0
|
||||
fi
|
||||
local running=0; for pf in "$RUN_DIR"/*.pid; do [ -e "$pf" ] && _alive "$pf" && running=1; done
|
||||
if [ "$running" = 1 ]; then
|
||||
printf "${GRAY}An app is already running.${RESET} Stop it first: ${GRAY}run-app --stop${RESET} (then ${GRAY}run-app %s${RESET}).\n" "$repo"
|
||||
return 0
|
||||
fi
|
||||
bash /workspace/.devcontainer/post-start.sh >/dev/null 2>&1 || true
|
||||
setup_repo "$repo" || { printf "${RED}setup failed for %s${RESET} \xe2\x80\x94 ${GRAY}run-app --logs${RESET}\n" "$repo"; return 1; }
|
||||
local cmd=""
|
||||
case "$kind" in
|
||||
ruby)
|
||||
if [ -n "$startcmd" ]; then
|
||||
cmd="env $bootenv PATH=$RBENV_PATH:\$PATH RBENV_VERSION=$ver $startcmd"
|
||||
elif [ -f "$dir/bin/rails" ]; then
|
||||
cmd="env $bootenv PATH=$RBENV_PATH:\$PATH RBENV_VERSION=$ver bundle exec rails server -b 0.0.0.0 -p 3000"
|
||||
elif [ -f "$dir/config.ru" ]; then
|
||||
# Rack app that isn't Rails (no bin/rails) — boot via rackup.
|
||||
cmd="env $bootenv PATH=$RBENV_PATH:\$PATH RBENV_VERSION=$ver bundle exec rackup -o 0.0.0.0 -p 3000"
|
||||
else
|
||||
printf " ${GRAY}%s isn't a web app (no bin/rails/config.ru) \xe2\x80\x94 run its tests directly (${RESET}${GRAY}bundle exec rspec${RESET}${GRAY}).${RESET}\n" "$repo"
|
||||
return 0
|
||||
fi ;;
|
||||
node)
|
||||
local nbin sc=""
|
||||
nbin=$(_node_bin "$ver")
|
||||
if [ -n "$startcmd" ]; then
|
||||
cmd="env $bootenv PATH=$nbin:\$PATH PORT=3000 BROWSER=none HOST=0.0.0.0 $startcmd"
|
||||
elif [ -f "$dir/metro.config.js" ] || [ -d "$dir/ios" ] || [ -d "$dir/android" ]; then
|
||||
# React Native app: no web server in a Linux container; tests still run.
|
||||
printf " ${GRAY}%s is a React Native app (no web server here) \xe2\x80\x94 run its Jest tests directly (${RESET}${GRAY}yarn test${RESET}${GRAY}).${RESET}\n" "$repo"
|
||||
return 0
|
||||
else
|
||||
# CRA / generic: first dev-server script the repo defines, bound to :3000.
|
||||
local s
|
||||
for s in start dev develop serve; do
|
||||
if node -e "process.exit((((require('$dir/package.json')||{}).scripts)||{})['$s']?0:1)" 2>/dev/null; then sc="$s"; break; fi
|
||||
done
|
||||
if [ -z "$sc" ]; then
|
||||
printf " ${GRAY}%s: deps installed, no dev-server script \xe2\x80\x94 run its tests directly (${RESET}${GRAY}yarn test${RESET}${GRAY}).${RESET}\n" "$repo"
|
||||
return 0
|
||||
fi
|
||||
# A Create-React-App dev server (react-scripts / react-app-rewired) needs extra env
|
||||
# to survive in this non-interactive container. We spawn it with stdout redirected
|
||||
# to a log, so react-scripts sees a non-TTY and (start.js) registers a stdin-"end"
|
||||
# handler that closes the dev server the moment stdin ends — which it does at once
|
||||
# when there's no interactive terminal, so the app appears to "crash on boot". The
|
||||
# guard is `if (isInteractive || process.env.CI !== 'true')`, so CI=true is what
|
||||
# skips it and keeps the server up. CI=true does NOT make `start` treat warnings as
|
||||
# errors — that is `build` only (verified against react-scripts 3.4.1). The others:
|
||||
# DANGEROUSLY_DISABLE_HOST_CHECK=true let the dev server answer requests arriving
|
||||
# via the published host port (belt-and-braces;
|
||||
# wds3 already allows IP/localhost hosts).
|
||||
# NODE_OPTIONS=--openssl-legacy-provider webpack-4-era CRA crashes on Node 17+
|
||||
# without it; the flag only EXISTS on Node 17+,
|
||||
# so gate it on the major — older nodes (e.g.
|
||||
# Node 16) abort on "bad option".
|
||||
# Non-CRA dev servers (Next.js, vite, …) don't match the test, so they boot unchanged.
|
||||
local craenv=""
|
||||
if node -e "const s=(((require('$dir/package.json')||{}).scripts)||{})['$sc']||'';process.exit(/react-scripts|react-app-rewired/.test(s)?0:1)" 2>/dev/null; then
|
||||
craenv="CI=true DANGEROUSLY_DISABLE_HOST_CHECK=true"
|
||||
case "${ver%%.*}" in 1[7-9]|[2-9][0-9]) craenv="NODE_OPTIONS=--openssl-legacy-provider $craenv" ;; esac
|
||||
fi
|
||||
cmd="env $bootenv $craenv PATH=$nbin:\$PATH PORT=3000 BROWSER=none HOST=0.0.0.0 yarn $sc"
|
||||
fi ;;
|
||||
python)
|
||||
if [ -z "$startcmd" ]; then
|
||||
printf " ${GRAY}%s: Python deps installed. No web server is wired \xe2\x80\x94 run its tests/scripts directly (e.g. pytest).${RESET}\n" "$repo"
|
||||
return 0
|
||||
fi
|
||||
if _py_uv_ok "$ver"; then
|
||||
local vdir; vdir=$(_uv_venv_dir "$repo")
|
||||
cmd="env $bootenv VIRTUAL_ENV=$vdir PATH=$vdir/bin:\$PATH $startcmd"
|
||||
else
|
||||
cmd="env $bootenv PATH=$PYENV_PATH:\$PATH PYENV_VERSION=$ver $startcmd"
|
||||
fi ;;
|
||||
rust)
|
||||
if [ -z "$startcmd" ]; then
|
||||
printf " ${GRAY}%s: workspace built. No web server is wired \xe2\x80\x94 run its tests directly (${RESET}${GRAY}cargo test${RESET}${GRAY}).${RESET}\n" "$repo"
|
||||
return 0
|
||||
fi
|
||||
cmd="env $bootenv CARGO_TARGET_DIR=/opt/raccoon-cargo-target/$repo $startcmd" ;;
|
||||
*) printf "${YELLOW}runtime '%s' for %s isn't runnable here \xe2\x80\x94 explore-only.${RESET}\n" "$runtime" "$repo"; return 0 ;;
|
||||
esac
|
||||
printf " ${CYAN}\xe2\x96\xb6${RESET} starting %s (%s)\xe2\x80\xa6\n" "$repo" "$runtime"
|
||||
_spawn app "$dir" "$cmd"
|
||||
if _wait_tcp 3000; then
|
||||
printf " ${CYAN}\xe2\x9c\x85 %s is up${RESET} open ${CYAN}http://localhost:%s${RESET}\n" "$repo" "$CLIENT_HOST_PORT"
|
||||
# Per-member "how do I actually get in" notes. Only members whose landing page needs
|
||||
# more than the URL need an entry here (e.g. an app whose real sign-in is a hosted
|
||||
# third-party login that can't be reached offline).
|
||||
case "$repo" in
|
||||
strongsuit-app)
|
||||
printf " ${GRAY}Sign-in normally goes through a hosted Auth0 page, which isn't reachable\n"
|
||||
printf " offline, so this app ships a local-only dev-login route. Open\n"
|
||||
printf " ${RESET}${CYAN}http://localhost:%s/dev-login${RESET}${GRAY} to sign in as a seeded admin\n" "$CLIENT_HOST_PORT"
|
||||
printf " (${RESET}${GRAY}?role=MSS${RESET}${GRAY} or ${RESET}${GRAY}?role=MEMBER${RESET}${GRAY} for the other roles). The DB was seeded during setup.${RESET}\n"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 %s didn't come up in time${RESET} \xe2\x80\x94 ${GRAY}run-app --logs${RESET}\n" "$repo"
|
||||
fi
|
||||
printf " stop ${GRAY}run-app --stop${RESET} switch ${GRAY}run-app --stop && run-app <repo>${RESET}\n"
|
||||
printf " focus ${GRAY}cd /workspace/repos/%s && claude${RESET} (so Claude works in this repo without being told the path)\n" "$repo"
|
||||
}
|
||||
|
||||
# Generic Rails boot for the standard-shape apps (the rubyforgood repos): a single
|
||||
# `bin/rails server` on container :3000, no separate client. The DB is seeded during
|
||||
# post-create (none of these expose a working self-service signup), so the caller
|
||||
# passes the demo login to print. Optional $2 is a one-line note printed above the
|
||||
# login (e.g. a subdomain caveat).
|
||||
# start_rails <login-hint> [url-note]
|
||||
start_rails() {
|
||||
local login_hint="${1:-}" url_note="${2:-}"
|
||||
_spawn app /workspace/repo "bin/rails server -b 0.0.0.0 -p 3000"
|
||||
printf " ${YELLOW}\xe2\x96\xb6${RESET} starting Rails (puma)\xe2\x80\xa6\n"
|
||||
printf " ${GRAY}\xe2\x8f\xb3 waiting for the app to come up\xe2\x80\xa6${RESET}\n"
|
||||
if _wait_tcp 3000; then
|
||||
printf " ${YELLOW}\xe2\x9c\x85 app is up${RESET}\n"
|
||||
printf " open ${YELLOW}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
[ -n "$url_note" ] && printf " ${GRAY}%s${RESET}\n" "$url_note"
|
||||
[ -n "$login_hint" ] && printf " login ${GRAY}%s${RESET}\n" "$login_hint"
|
||||
else
|
||||
printf " ${RED}\xe2\x9a\xa0 the app didn't come up in time${RESET}\n"
|
||||
printf " check the logs: ${GRAY}run-app --logs${RESET}\n"
|
||||
fi
|
||||
printf " logs ${GRAY}%s/app.log${RESET}\n" "$RUN_DIR"
|
||||
printf " stop ${GRAY}run-app --stop${RESET}\n"
|
||||
}
|
||||
|
||||
start_app() {
|
||||
# Already running? Don't double-start.
|
||||
local running=0
|
||||
for pf in "$RUN_DIR"/*.pid; do [ -e "$pf" ] && _alive "$pf" && running=1; done
|
||||
if [ "$running" = 1 ]; then
|
||||
printf "${GRAY}The app is already running.${RESET} Use ${GRAY}run-app --restart${RESET} to restart, ${GRAY}run-app --status${RESET} to check.\n"
|
||||
printf " open ${CYAN}http://localhost:%s${RESET}\n" "$CLIENT_HOST_PORT"
|
||||
return 0
|
||||
fi
|
||||
# Make sure the database is up before the server tries to connect.
|
||||
bash /workspace/.devcontainer/post-start.sh >/dev/null 2>&1 || true
|
||||
case "$REPO_NAME" in
|
||||
Palolo-031) start_palolo ;;
|
||||
ZenBill-006) start_zenbill ;;
|
||||
zeta-heimdall) start_zeta_heimdall ;;
|
||||
zeta-platform) start_zeta_platform ;;
|
||||
human-essentials) start_rails "test@example.com / password! (sign in at /users/sign_in)" ;;
|
||||
endsideout) start_rails "admin@example.com / password (sign in at /session/new)" ;;
|
||||
community-foundation)
|
||||
# Multi-tenant: the org is a subdomain, so plain localhost only shows the
|
||||
# apex landing page. The seed creates the 'arlington' tenant.
|
||||
start_rails "owner@example.com / password" \
|
||||
"this app routes by subdomain — open http://arlington.lvh.me:${CLIENT_HOST_PORT}/ (plain localhost shows only the landing page)" ;;
|
||||
stocks-in-the-future) start_rails "username admin / password (sign in at /users/sign_in — login is by USERNAME, not email)" ;;
|
||||
casa) start_rails "casa_admin1@example.com / 12345678 (sign in at /users/sign_in)" ;;
|
||||
awbw) start_rails "umberto.user@example.com / password (sign in at /users/sign_in)" ;;
|
||||
flaredown) start_flaredown ;;
|
||||
alongwithyou)
|
||||
# Fresh scaffold: no routes/auth yet, so plain localhost shows the default Rails
|
||||
# welcome page. No login to print. The app grows over time.
|
||||
start_rails "" "young app — no routes defined yet, so this shows the default Rails welcome page" ;;
|
||||
breezy-complete) start_breezy_complete ;;
|
||||
*)
|
||||
printf "${YELLOW}run-app isn't configured for repo '%s'.${RESET}\n" "${REPO_NAME:-unknown}"
|
||||
printf "Start the app with the project's own dev command from ${GRAY}/workspace/repo${RESET}.\n"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
usage() {
|
||||
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
if _is_polyglot; then
|
||||
# `run-app [<repo>] [--restart|--stop|--logs|--status]` — order-independent: the repo
|
||||
# name and the action can appear in either order (e.g. `run-app --restart zeta-hook`),
|
||||
# and the bare verbs (start/restart/stop/...) are recognized as actions, not repos.
|
||||
poly_repo=""; poly_action="start"
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
start) poly_action="start" ;;
|
||||
--restart|restart) poly_action="restart" ;;
|
||||
--stop|stop) poly_action="stop" ;;
|
||||
--logs|logs) poly_action="logs" ;;
|
||||
--status|status) poly_action="status" ;;
|
||||
-h|--help|help) poly_action="help" ;;
|
||||
-*) printf "${RED}Unknown option:${RESET} %s\n\n" "$a"; usage; exit 2 ;;
|
||||
*) poly_repo="$a" ;;
|
||||
esac
|
||||
done
|
||||
case "$poly_action" in
|
||||
start) start_poly "$poly_repo" ;;
|
||||
restart) stop_app; start_poly "$poly_repo" ;;
|
||||
stop) stop_app ;;
|
||||
logs) logs_app ;;
|
||||
status) status_app ;;
|
||||
help) usage ;;
|
||||
esac
|
||||
exit $?
|
||||
fi
|
||||
|
||||
case "${1:-}" in
|
||||
""|start) start_app ;;
|
||||
--restart|restart) stop_app; start_app ;;
|
||||
--stop|stop) stop_app ;;
|
||||
--logs|logs) logs_app ;;
|
||||
--status|status) status_app ;;
|
||||
-h|--help|help) usage ;;
|
||||
*) printf "${RED}Unknown option:${RESET} %s\n\n" "$1"; usage; exit 2 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user