diff --git a/docs/explanations/2026-07-21-explanation-kcd2-dice-solver-run-sh.html b/docs/explanations/2026-07-21-explanation-kcd2-dice-solver-run-sh.html new file mode 100644 index 00000000..55e6ebbb --- /dev/null +++ b/docs/explanations/2026-07-21-explanation-kcd2-dice-solver-run-sh.html @@ -0,0 +1,644 @@ +run.sh for the KCD2 Dice Solver + + +
+

run.sh for the KCD2 Dice Solver

+

How one script turns "clone and pray" into "clone and run" for a Vite/React app.

+ + + +
+

Background

+

+ kcd2_dice_solver is a small Vite + React + TypeScript app: you tell it + which dice and badges you own in Kingdom Come: Deliverance II, and it + searches every loadout your inventory allows to recommend the best one. It's a + normal Node project — a package.json with scripts for dev, + build, test, and lint, run through pnpm. +

+

+ Until now, running it meant reading the README and typing two commands by hand: +

+
pnpm install
+pnpm dev
+

+ That's fine if you already have the right Node version and pnpm installed and you + remember the exact commands. It's not fine as a "just run it" entry point — which + is what this change adds: a run.sh that does the checking and installing + for you, then starts the dev server. +

+
+ +
+

Intuition

+

+ The interesting part of run.sh isn't the two commands above — it's + everything that has to happen before them so those two commands are safe to + run unconditionally. The script has to answer two questions about the machine it's + running on: +

+
+
Is Node installed, and is it ≥ v22?
+
no load nvm, install/switch to the LTS release
+
yes leave it alone
+
Is pnpm on the PATH?
+
no enable Corepack, activate the latest pnpm
+
yes leave it alone
+
+

+ Only once both are true does it do the boring part: pnpm install, then + pnpm dev. The project's own package.json already states the + requirement ("engines": {"node": ">=22", "pnpm": ">=10"}) — the + script's job is to make that requirement true rather than just documented. +

+
+ Play with the simulator below: pick a starting machine state and step through + exactly what run.sh would print and do. +
+ +
+
+
+ Node on this machine + +
+
+ pnpm on this machine + +
+
+   + +
+
+
+
+
+ +
+

Code

+ +

Guarding Node's version

+

+ ensure_node only reaches for nvm when it has to. If + node resolves and its major version already clears the bar, it returns + immediately — no network call, no reinstall, no surprise version switch on a + machine that's already fine. +

+
ensure_node() {
+    if command -v node >/dev/null 2>&1; then
+        local major
+        major="$(node --version | sed -E 's/^v([0-9]+).*/\1/')"
+        if (( major >= NODE_MIN_MAJOR )); then
+            return
+        fi
+        echo "Node $(node --version) is older than required v${NODE_MIN_MAJOR}; installing a newer version via nvm..."
+    else
+        echo "Node not found; installing via nvm..."
+    fi
+
+    if [[ ! -s "${NVM_DIR}/nvm.sh" ]]; then
+        echo "Error: nvm not found at ${NVM_DIR}/nvm.sh; install nvm first." >&2
+        exit 1
+    fi
+    source "${NVM_DIR}/nvm.sh"
+    nvm install --lts
+    nvm use --lts
+}
+

+ Two things worth noticing: it fails with a clear error rather than trying to + bootstrap nvm itself (installing a version manager is a bigger, riskier + step than the script should take silently), and it treats "too old" and "missing" + as the same recovery path — both end up at nvm install --lts. +

+ +

Guarding pnpm

+

+ ensure_pnpm is the same shape, one level simpler: pnpm ships inside Node + via Corepack, so there's no version manager to bootstrap — just corepack + enable followed by pulling the latest pnpm release. +

+
ensure_pnpm() {
+    if command -v pnpm >/dev/null 2>&1; then
+        return
+    fi
+    echo "pnpm not found; enabling via corepack..."
+    corepack enable
+    corepack prepare pnpm@latest --activate
+}
+ +

Loading nvm before checking, not just before installing

+

+ One subtlety lives in main, not in ensure_node: nvm.sh + is sourced before ensure_node is even called. +

+
main() {
+    if [[ -s "${NVM_DIR}/nvm.sh" ]]; then
+        source "${NVM_DIR}/nvm.sh"
+    fi
+
+    ensure_node
+    ensure_pnpm
+
+    cd "$SCRIPT_DIR"
+    echo "Installing dependencies..."
+    pnpm install
+
+    echo "Starting dev server (http://localhost:5174)..."
+    exec pnpm dev
+}
+

+ A fresh, non-interactive shell (like the one a script runs in) doesn't source + ~/.bashrc, so nvm's shims for switching Node versions + wouldn't otherwise be in scope at all. Sourcing it up front means that if a later + run needs nvm use --lts inside ensure_node, the function + to call already exists. +

+

+ The last line, exec pnpm dev, replaces the shell process with pnpm + instead of running it as a child. That means Ctrl-C in the terminal goes straight + to Vite's dev server rather than being caught by an intermediate run.sh + process — one less layer between you and the thing you're trying to stop. +

+ + + + + +
FunctionChecksRecovery
ensure_nodenode present and ≥ v22nvm install --lts + nvm use --lts
ensure_pnpmpnpm presentcorepack enable + corepack prepare pnpm@latest --activate
+
+ +
+

Quiz

+
+

+
+ + +
+ + diff --git a/docs/explanations/index.html b/docs/explanations/index.html index aec4b526..a13f4af7 100644 --- a/docs/explanations/index.html +++ b/docs/explanations/index.html @@ -30,8 +30,12 @@
testsAndMisc

Change explanations

-

14 explanations · interactive walkthroughs of notable changes, newest first.

+

15 explanations · interactive walkthroughs of notable changes, newest first.

+ + 2026-07-21 + run.sh for the KCD2 Dice Solver + 2026-07-19 A shared launcher-icon generator for the Flutter apps