Skip to content

Fix cross-OS installs (pin deps, drop lockfiles) and boot playground out of the box#3

Merged
AlemTuzlak merged 2 commits into
mainfrom
fix/pin-deps-cross-os-and-env-boot
Jul 8, 2026
Merged

Fix cross-OS installs (pin deps, drop lockfiles) and boot playground out of the box#3
AlemTuzlak merged 2 commits into
mainfrom
fix/pin-deps-cross-os-and-env-boot

Conversation

@AlemTuzlak

Copy link
Copy Markdown
Collaborator

Why

Students kept hitting OS-specific setup failures. Two root causes:

  1. Cross-OS install breakage. The committed package-lock.json was generated on Windows and only pinned win32-x64 native binaries (@rollup/rollup, @esbuild, @tailwindcss/oxide, lightningcss, @unrs/resolver). macOS/Linux students hit npm's optional-deps bug (npm/cli#4828) → Cannot find module @rollup/rollup-darwin-arm64. (There's no rolldown in this repo — the native-binary chain is rollup/esbuild/oxide/lightningcss/unrs.)
  2. App didn't boot out of the box. A fresh playground had no .env, so DATABASE_URL was unset and db.server.ts crashed at prisma.$connect(). And from exercise 1's solution onward the OpenRouter adapter is constructed at module scope, which throws on an empty OPENROUTER_API_KEY → every page 500s (not just chat).

What changed

Deps / installs

  • Pinned every dependency to an exact version — root + all 20 exercise apps + epicshop. Per-workspace resolution, so divergent versions are preserved (exercises keep @types/node@20.19.41 / zod@4.4.3; root keeps @types/node@24.12.4).
  • Gitignored + untracked package-lock.json. Each machine now resolves its own OS-native binaries on a fresh install; exact pins keep top-level versions deterministic without a committed lockfile.

Boot out of the box

  • epicshop/post-set-playground.js seeds the playground .env from the exercise's .env.example when none exists — placed after the existing stash/restore so a student's edited key is never clobbered.
  • All 20 .env.example now ship DATABASE_URL + a non-empty placeholder OPENROUTER_API_KEY. App boots on every exercise; chat returns 401 until the student pastes a real key. No app/teaching code touched.

Verification

  • Clean-room root npm install: exit 0, no ERESOLVE.
  • Standalone install of every exercise (2 full + 18 full-resolve) passes.
  • From-scratch --strict-peer-deps resolve passes → peers are genuinely compatible, not just force-installed (legacy-peer-deps=true kept as a safety net).
  • Regenerated lockfile carries all platforms; this machine's binaries load.
  • All 20 exercise dev servers serve the homepage (HTTP 200) from .env alone.

Tradeoff

Direct deps are frozen; transitive patches still float on fresh installs (e.g. rollup 4.60 → 4.62). If byte-for-byte reproducibility is ever needed, the alternative is committing an all-platform lockfile via optionalDependencies (~100 entries, higher maintenance) — deliberately not taken here.

Workshop installs were breaking on macOS/Linux. The committed
package-lock.json was generated on Windows and pinned only win32-x64
native binaries (@rollup/rollup, @esbuild, @tailwindcss/oxide,
lightningcss, @unrs/resolver), so other platforms hit npm's optional-deps
bug (npm/cli#4828): "Cannot find module @rollup/rollup-darwin-arm64".

- Pin every dependency to an exact version across the root, all 20
  exercise apps, and epicshop (per-workspace resolution, so divergent
  versions like exercises' @types/node@20 / zod@4 are preserved).
- Gitignore + untrack package-lock.json so each machine resolves its own
  OS-native binaries on a fresh install. Exact pins keep top-level
  versions deterministic without a committed lockfile.

Verified: clean-room root install (no ERESOLVE), standalone install of
every exercise, and a from-scratch --strict-peer-deps resolve all pass.
Fresh playgrounds had no .env, so DATABASE_URL was unset and the app
crashed on boot (db.server.ts calls prisma.$connect() at import). And
from the first exercise's solution onward, the OpenRouter adapter is
constructed at module scope, which throws when OPENROUTER_API_KEY is
empty -> every page 500s, not just chat.

- post-set-playground.js now creates the playground .env from the
  exercise's .env.example when none exists (after the stash/restore, so a
  student's edited key is never clobbered).
- .env.example ships DATABASE_URL plus a non-empty placeholder
  OPENROUTER_API_KEY, so openRouterText() constructs and every exercise
  boots. Chat returns 401 until the student pastes a real key.

Verified: all 20 exercise dev servers serve the homepage (HTTP 200) from
.env alone, with no key set beyond the placeholder.
@AlemTuzlak AlemTuzlak merged commit 5eedb4e into main Jul 8, 2026
1 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant