diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae6eb0d5..9b038057 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,8 +67,13 @@ jobs: run: npm ci working-directory: ./react-ystemandchess - - name: Build react-ystemandchess - run: CI=false npm run build --if-present + # The verify script runs in this step so it sees the exact env the build + # used. Jest runs in Node, where `process` exists, so env bugs that blank + # the page in the browser would otherwise pass CI. + - name: Build react-ystemandchess and verify env was inlined + run: | + npm run build --if-present + node scripts/verify-build-env.mjs working-directory: ./react-ystemandchess env: # These are read by src/environments/environment.prod.js, which is diff --git a/documentation/frontend-cra-to-vite-migration.md b/documentation/frontend-cra-to-vite-migration.md new file mode 100644 index 00000000..7140f16e --- /dev/null +++ b/documentation/frontend-cra-to-vite-migration.md @@ -0,0 +1,298 @@ +# Frontend Build Migration: Create React App → Vite + +**Scope:** `react-ystemandchess` +**Context:** Issue 4 (Vulnerable and Near-Deprecated Dependencies), Phase 2 follow-up +**Branch:** `chore/frontend-cra-to-vite` (`8c6a523c`) — pushed, **not merged** +**Status:** Ready for review. One verification step outstanding (see [Verification](#verification)). + +--- + +## Summary + +After `npm audit fix` cleared the safely-fixable findings, ~33 remained in the frontend. All of them +traced to a single root cause: `react-scripts@5.0.1` (Create React App), which is unmaintained +upstream and pins its own outdated build toolchain. Five options were scoped (A–E). This document +records why **Option D (migrate to Vite)** was the only one that could reach zero, why it was +substantially cheaper in this repository than the generic estimate assumed, and what the migration +uncovered. + +**Result: 47 → 5 audit findings. Zero critical, zero high, zero low.** + +| Metric | Before | After | +|---|---|---| +| Audit findings | 47 | **5** | +| Critical / High / Low | 0 / 26 / 10 | **0 / 0 / 0** | +| Moderate | 11 | 5 | +| Test suites / tests | 27 / 149 | 27 / 149 (parity) | +| Production build | webpack (CRA) | Vite 8.2.2, ~6.4s | +| Node (Docker) | `18.20.8-alpine` (EOL Apr 2025) | `20.19.0-alpine` | + +--- + +## The ledger + +Counts from `npm audit --package-lock-only`. + +| Stage | Total | High | Moderate | Low | Provenance | +|---|---:|---:|---:|---:|---| +| `main`, pre-fix | 47 | 26 | 11 | 10 | Measured | +| After `npm audit fix` | 33 | — | — | — | As reported by Thrishma | +| After migration | **5** | 0 | 5 | 0 | Measured | + +--- + +## Decision: why Option D + +### Option B (npm overrides) provably cannot reach zero + +`react-scripts` pins `webpack-dev-server: ^4.6.0`, while the advisory fix requires `>= 5.2.1`. +CRA's own dev-server config calls three APIs that webpack-dev-server 5 removed outright: + +| File | Line | API | +|---|---|---| +| `node_modules/react-scripts/config/webpackDevServer.config.js` | 112 | `onBeforeSetupMiddleware` | +| " | 123 | `onAfterSetupMiddleware` | +| " | 102 | `https:` | + +Forcing that override breaks `npm start`. + +`react-scripts` itself cannot be patched at all — `npm audit` reports its `fixAvailable` as +`version: "0.0.0"`, npm's way of signalling that no fix exists. Sixteen findings sat behind that +wall: `@svgr/*`, `css-select`, `nth-check`, `postcss`, `resolve-url-loader`, `rollup-plugin-terser`, +`serialize-javascript`, `sockjs`, `svgo`, `uuid`, `webpack-dev-server`, `workbox-*`, and +`react-scripts`. + +### The other options + +- **Option A** (accept and document) — a valid *reporting posture* while a fix lands, but not a + destination; the 16 CRA-locked findings never close. +- **Option C** (`npm audit fix --force` to measure) — superseded; the mechanism above already + explains the breakage without an exploratory branch. +- **Option E** (craco / react-app-rewired) — a config layer over the same aging toolchain. Does not + change the pinned versions. + +### Why Option D was cheaper here than estimated + +Option D was scoped as "medium–high, needs its own task." Three of its four cost drivers had already +been paid in this repository: + +| Estimated cost | Actual state | +|---|---| +| Jest / test-runner config changes | Already standalone. `jest.config.js`, `babel.config.js` and `__mocks__/` existed; only the `test` script still pointed at `react-scripts test`. | +| `REACT_APP_*` → `VITE_*` sweep | **2** usages across **303** source files. Real config is `src/environments/environment.js`, a plain ES module. Handled with a Vite `define` and **zero source edits**. | +| Dev server / proxy setup | None needed — no CRA `proxy` field; the app calls absolute URLs. | +| Build script changes | One line. `build.outDir: 'build'` keeps the Dockerfile's `serve -s build` untouched. | + +Other common migration blockers came back clear: no JSX inside `.js` files, no absolute-from-`src` +imports or `@/` aliases, and Sass, Tailwind, PostCSS and Autoprefixer already present as direct +devDependencies. + +--- + +## What CRA was doing implicitly + +The most useful outcome was not the audit number. It was discovering four things the project depended +on that were **never written down anywhere** — each invisible until the tool supplying it was removed. + +### 1. Tailwind was compiling only because CRA auto-detected it + +CRA auto-detects `tailwind.config.js` and injects the `tailwindcss` PostCSS plugin itself +(`react-scripts/config/webpack.config.js:72`). **No `postcss.config.js` has ever existed in this +repository's history** — confirmed against all branches, and there is no craco or react-app-rewired +either. + +Under Vite, Tailwind silently stopped compiling and the site rendered unstyled. This had **no test +coverage** — it was caught only by loading the app in a browser. Fixed by adding an explicit +`postcss.config.js`. + +### 2. `uuid` was a phantom dependency + +`uuid` is imported directly by `src/features/puzzles/Puzzles.tsx:8` and +`src/features/student/student-page/Student.tsx:7`, but was **absent from `package.json`**. It +resolved only through hoisting: + +``` +react-scripts → webpack-dev-server → sockjs → uuid@8.3.2 +``` + +Removing CRA would have broken the production build. Note also that the vulnerable `uuid` npm +flagged under the CRA cluster was the copy the application actually ships. It is now an explicit +direct dependency. + +### 3. `whatwg-fetch` and `src/setupTests.ts` were wired up by CRA's Jest config + +CRA set `setupFiles` to `react-app-polyfill/jsdom` (which is just `whatwg-fetch`) and +`setupFilesAfterEnv` to `src/setupTests.ts`. Standalone Jest has to be told about both explicitly. + +### 4. ESLint ran on every build — and now does not + +Linting was part of `react-scripts build`, which is why CI carried `CI=false` to stop warnings +failing the build. ESLint is not a direct dependency, so **build-time linting is now gone**. See +[Follow-ups](#follow-ups). + +--- + +## Defects surfaced by the migration + +Standing up the standalone Jest config exposed two pre-existing defects. Neither was caused by Vite. + +### The automatic JSX runtime was never configured + +`babel.config.js` used `@babel/preset-react` without `runtime: 'automatic'`. Because components +render JSX without importing React, `npx jest` failed **14 of 27 suites** with +`ReferenceError: React is not defined`. `react-scripts test` masked this completely by using its own +Babel preset, so CI stayed green and anyone running Jest directly hit a wall. + +### Two specs passed only because of a jsdom gap + +Two `ParentSignUp` specs clicked submit on a form whose inputs carry the native `required` attribute. +Under jsdom 16 (Jest 27, via react-scripts) this submitted fine, because that version did not +implement interactive constraint validation. Under jsdom 20 — and in **every real browser** — +submission is blocked and the handler never fires. + +The specs were asserting behaviour that cannot occur in production. They now dispatch `submit` on the +form directly, which is what they were always trying to test. + +> **Worth a separate look:** because those fields are natively `required`, the component's own +> friendlier validation messages (`"Invalid Email"`, `"Please accept the terms and conditions."`) are +> unreachable for empty fields in a real browser. Users see browser-default tooltips instead. + +--- + +## What changed + +No application logic was modified. Changes are confined to build configuration, plus the mechanical +SVG import change and the two test fixes above. + +### Dependencies + +| Action | Package | +|---|---| +| Removed | `react-scripts`, `@svgr/webpack` (dead — webpack-only, carried a HIGH finding) | +| Added (dev) | `vite`, `@vitejs/plugin-react`, `vite-plugin-svgr`, `whatwg-fetch` | +| Added (prod) | `uuid` (promoted from phantom dependency) | + +### Configuration + +| File | Change | +|---|---| +| `vite.config.mts` | New. React plugin, svgr, `build.outDir: 'build'`, `server.port: 3000`, `define` for `process.env`. `.mts` avoids `"type": "module"`, which would break the CommonJS jest/babel configs. | +| `postcss.config.js` | New. Declares `tailwindcss` + `autoprefixer` explicitly. | +| `index.html` | Moved from `public/`. `%PUBLIC_URL%` → `/`; module script tag added. | +| `package.json` | Scripts → `vite` / `vite build` / `jest`. Removed dead `eslintConfig` block referencing the now-absent `eslint-config-react-app`. | +| `jest.config.js` | Added `setupFiles`, `setupFilesAfterEnv`, `testPathIgnorePatterns` (Playwright specs), `transformIgnorePatterns` (ESM-only `uuid`), and a mapper for `?react` SVG imports. | +| `babel.config.js` | Added `runtime: 'automatic'`; targets Node for Jest. | +| `tsconfig.json` | `target` ES5 → ES2020 (esbuild cannot downlevel to ES5); added `vite/client` types. | +| `Dockerfile` | `node:18.20.8-alpine` → `node:20.19.0-alpine`, both stages. Required by Vite, not optional. | +| `.github/workflows/ci.yml` | Dropped the CRA-specific `CI=false` prefix. | +| `src/index.css` | Moved the Google Fonts `@import` above the `@tailwind` directives — CSS requires `@import` to precede other rules, so the font was being dropped. | + +### SVG imports + +Both import styles coexisted in the codebase (28 component imports, 34 URL imports). They moved to +Vite's current convention rather than pinning an older plugin major — staying on legacy tooling is +what created this ticket in the first place. + +```diff +- import { ReactComponent as RedoIcon } from "./icon_redo.svg"; ++ import RedoIcon from "./icon_redo.svg?react"; +``` + +Bare `.svg` imports remain asset URLs and were untouched. Type declarations and the Jest SVG mocks +were updated to match. + +--- + +## Verification + +| Check | Result | +|---|---| +| Test parity | **Pass.** Baseline captured on `main` first: 27 suites / 149 tests under `react-scripts test`. Standalone Jest now matches exactly — 27 / 149. | +| Production build | **Pass.** ~6.4s. CJS interop for `chessboardjsx`, `chess.js` and `react-element-to-jsx-string` — the flagged highest risk — cleared with no extra config. CSS grew 130 → 177 kB once Tailwind compiled again. | +| Bundle integrity | **Pass.** Zero server-side modules in output (0 of 469 sources), matching CRA. `process.env` references fully replaced, `localhost:8000` fallback preserved. | +| Browser pass | **Pass.** Dev server ready in ~300 ms. Home, `/lessons`, `/login` checked: `?react` component icons and bare-URL SVG imports both render, chessboard mounts, no module or CSS errors. Only console errors were `Failed to fetch` (no local backend). | +| Node engines | **Pass.** Vite is tightest at `^20.19.0 \|\| >=22.12.0`; all other dependencies looser. The pin is valid — but sits exactly on the floor, with no headroom for a future Vite minor. | +| **Docker build** | **NOT VERIFIED.** Docker unavailable in the working environment. **Must be run before merge** to confirm the `node:20.19.0-alpine` pin. | + +--- + +## What remains open + +Five moderate findings, both groups deliberate. + +| Findings | Packages | Why left | +|---|---|---| +| 3 × moderate | `express`, `body-parser`, `qs` | Present only for six unreachable server-side files under `src/`. Verified against build sourcemaps that **0 of 469** bundle sources are these. Real fix is Express 5, a semver-major `npm audit fix` will not apply; correct resolution is deleting the dead files. Flagged rather than actioned, per agreed scope. | +| 2 × moderate | `react-router`, `react-router-dom` | Fix requires v7, a semver-major with genuine routing-behaviour risk. Deferred to its own task with a dedicated manual navigation pass (parent plan, Phase 4). Bundling a routing major into a build-tool migration would make any regression untraceable. | + +--- + +## Where it stands + +- Branch `chore/frontend-cra-to-vite` (`8c6a523c`) pushed to origin. 22 files, +4,924 / −15,471. +- `main` untouched. **No pull request opened, no merge performed.** +- One file deliberately left uncommitted: `src/core/environments/`, which CI regenerates on every + run. Committing it would create a tracked file with a credentials shape that competes with CI's own + generation. It was untracked before this work began. + +> **Note on the Dependabot figure.** GitHub reports 210 vulnerabilities on the default branch +> (1 critical, 111 high, 83 moderate, 15 low). That spans all four services *plus* the stale +> directories — `angular-ystemandchess-old`, `chess-client-react-refactor`, +> `suggested-chess-front-end`, and the dead root `package.json`. The 47 → 5 figure here is +> `react-ystemandchess` alone. Worth clearing the abandoned folders (parent plan, Phase 1) before +> enabling Dependabot properly, or it will keep reporting against directories nobody ships. + +--- + +## Follow-ups + +Found during this work, deliberately out of scope. + +1. **Re-wire linting.** ESLint ran on every CRA build and no longer runs at all. Add `eslint` with + `eslint-plugin-react-hooks`, a `lint` script, and a CI step. This restores a check that `CI=false` + was suppressing anyway. + +2. **Delete the server-side code under `src/`.** Six unreachable files import `express`, + `nodemailer`, `config` and `jsonwebtoken`. Removing them closes the last three moderate findings + and drops four frontend dependencies. + Prioritise for a second reason: `resetPasswordService.ts:14` reads Gmail credentials from a + client-side config module. Inert today — nothing reachable imports it — but a live secret leak the + moment any component does. + +3. **CI injects secrets into a file nothing reads.** `ci.yml` generates + `src/core/environments/environment.ts` with `APP_ID`, `EMAIL_USER`, `EMAIL_PASS` and the service + URLs. No source file imports `core/environments` — every consumer reads + `src/environments/environment` instead. The injection is a no-op, and CI has been building against + committed `localhost` values. + +4. **Correction to the Phase 2 write-up.** "All 33 are dev/build-time — none ship in the production + bundle" holds for the 16 CRA-locked findings (verified: no service worker registered). It does + **not** hold for `uuid`, which the app imports directly, nor for `axios`, `react-router`, or + `engine.io-client` / `socket.io-parser` / `ws` via `socket.io-client`. Worth re-checking before + that sentence enters an audit or compliance record. + +5. **Bundle size.** The main chunk is 1.66 MB (581 kB gzipped) in a single file, well over the 150 kB + landing-page budget. Vite flagged three ineffective dynamic imports where a module is both lazily + and statically imported. Pre-existing app structure, now simply visible — route-level code + splitting is straightforward under Vite. + +6. **Native validation vs. custom messages.** See the `ParentSignUp` note above; the component's own + validation messages are unreachable for empty `required` fields in a real browser. + +--- + +## Appendix: running the app + +```bash +cd react-ystemandchess +npm install + +npm start # Vite dev server on :3000 +npm run build # production build → build/ +npm run preview # serve the production build locally +npm test # Jest (27 suites / 149 tests) +npm run test:watch # Jest in watch mode +npm run test:e2e # Playwright (requires a running backend) +``` + +`npm run eject` no longer exists — it was a CRA-only escape hatch. diff --git a/documentation/frontend-vite-merge-plan.md b/documentation/frontend-vite-merge-plan.md new file mode 100644 index 00000000..1289102c --- /dev/null +++ b/documentation/frontend-vite-merge-plan.md @@ -0,0 +1,200 @@ +# **Development Plan: Finish the CRA to Vite Migration (Issue 4\)** + +**Owner:** Deepesh Katudia +**Pipeline contact:** Sahana Balaji +**Branch:** chore/frontend-cra-to-vite (last commit 75388650, Sept 7\) +**Date:** September 24, 2026 +**Severity:** P1. The branch is close to done, but merging it as-is would take the production site down. + +## **Summary** + +Deepesh's migration work is solid. It takes the frontend from 47 audit findings to 5, with zero high or critical, and keeps full test parity. His own write-up (documentation/frontend-cra-to-vite-migration.md) is thorough and stays the reference for the migration itself. +The branch was cut on August 23\. main has moved since then. PR \#208 (environment separation) merged, and it changed how the frontend reads its production URLs. Vite cannot handle the new pattern. The fix is small and has already been tested against a trial merge of the branch into current main. Four other pieces of work also need to land around it: Thrishma's frontend audit branch, the Node 24 pin, Sahana's CD pipeline, and one smoke test in that pipeline. + +## **Note on method** + +Every finding below comes from a trial merge of chore/frontend-cra-to-vite into main as of September 24 (cb499441). I resolved the conflicts, ran npm ci, built with all five REACT\_APP\_\* variables set, and inspected the compiled bundle. I then applied the fix, rebuilt, and ran the full Jest suite. Docker was not available, so the Docker build itself is still unverified. Deepesh's write-up flagged the same gap. + +## **Confirmed findings** + +### **1\. The production bundle crashes on load (critical)** + +\#208's environment.prod.js reads required URLs through a dynamic lookup, process.env\[name\]. In a production build, Vite replaces a bare process.env with an empty object. The compiled bundle contains: +``var P=e=>{let t={}[e];if(!t)throw Error(`Missing required production environment variable: ${e}`)...}`` +The lookup always returns nothing. The app throws as soon as it loads, even when Docker passed every variable correctly. Visitors get a blank page. The error message is also misleading, because it says a variable is missing when it isn't. +None of the current checks catch it: + +> * vite build succeeds. +> * All Jest tests pass, because Jest runs in Node, where process.env is real. +> * The Dockerfile healthcheck passes, because serve still returns HTTP 200 for index.html. +> * Sahana's curl \--fail smoke test passes for the same reason. + +**Correction to earlier advice:** I previously suggested a check that fails if the bundle contains the string process.env. That check would not catch this bug. Vite already removed the string, and the broken bundle contains none. The checks in Phases 3 and 6 look for the injected values instead. + +### **2\. Two merge conflicts, and the obvious resolution is wrong** + +ActivitiesModal.tsx and LeaderboardModal.tsx conflict. Each side changed one line of the same import block: + +> * The Vite branch converted the SVG imports to import X from "...svg?react". +> * main (\#208) changed the environment import from environments/environment to environments. + +Taking the Vite side wholesale brings back the hardcoded-dev import that \#208 fixed. Keep both changes: Vite's ?react SVG imports, plus main's environments path. After resolving, no ReactComponent as SVG imports and no environments/environment imports remain in src/. Both were checked by grep. + +### **3\. Docker: the Node version sits on Vite's floor** + +Vite 8 requires Node ^20.19.0 or \>=22.12.0. The Node 18 Dockerfile on main cannot build it. The branch pins node:20.19.0-alpine, which reached end-of-life in April 2026 and sits exactly on Vite's minimum. The team is standardizing on Node 24 (Sweksha's pin). Other checks: + +> * The ARG/ENV REACT\_APP\_\* lines from \#208 merge cleanly into the branch's Dockerfile. +> * The lockfile already includes the Alpine (musl) native binaries Vite needs (@rolldown/binding-linux-x64-musl, lightningcss-linux-x64-musl). +> * Output stays in build/, so the runtime stage (serve \-s build, port 3000\) is unchanged. +> * The Dockerfile uses npm install instead of npm ci, so image builds can drift from the lockfile. + +### **4\. Pre-existing: password reset calls localhost in production** + +reset-password.tsx (line 18\) and set-password.tsx (line 40\) read process.env.REACT\_APP\_API\_URL. Nothing sets that variable: not CI, the Dockerfile, deploy.yml, or tag\_build\_containers.sh. The production bundle therefore falls back to http://localhost:8000 for both calls. The same was true under CRA, so Vite didn't cause it. Confirm on the live site. Both files call {base}/user/..., the same shape as environment.urls.middlewareURL everywhere else, so the fix is a one-line swap in each file. + +### **5\. Other branches touch the same files** + +> * Thrishma's chore/npm-audit-fix-frontend merges cleanly onto main alone. Once it merges, the Vite branch will conflict with it on package.json and package-lock.json. Her change worth keeping is socket.io-client ^4.8.3. +> * Sahana's sahana/208-azure-cicd doesn't touch any frontend file. Her deploy.yml builds ./react-ystemandchess with the same build-arg names, so it needs no changes for Vite apart from the smoke test in Phase 6\. + +## **Fix phases** + +### **Phase 0: Prerequisites (not Deepesh's work, but they gate the merge)** + +> 1. Thrishma's chore/npm-audit-fix-frontend merges to main. +> 2. Sweksha's Node 24 pin merges to main. +> 3. Sahana's CD pipeline merges, so the Vite change ships through it as a normal PR. + +Deepesh can do Phases 1 through 5 in parallel with these. Only the final merge waits. + +### **Phase 1: Rebase onto main** + +> 1. Rebase (or merge main into) chore/frontend-cra-to-vite. +> 2. Resolve the two .tsx conflicts as described in Finding 2\. Keep ?react SVG imports and the environments path. +> 3. If Thrishma's branch has merged, take main's package.json, re-apply the Vite dependency changes, and run npm install to regenerate the lockfile. Keep socket.io-client ^4.8.3. +> 4. Grep src/ for ReactComponent as and for environments/environment". Both should return nothing. + +### **Phase 2: Fix how the environment variables reach the bundle** + +Tested: after this change, all five values appear in the compiled bundle, a missing required URL still fails fast (the behavior \#208 intended), and Jest passes 30 of 30 suites and 155 of 155 tests. +**src/environments/environment.prod.js**: pass each value by its literal name, so Vite can replace it. +`// Each variable is referenced by its full literal name (process.env.REACT_APP_X)` +``// because Vite's `define` can only replace static references, never a dynamic`` +`// process.env[name] lookup. vite.config.mts lists every one of them.` +`const requiredProductionEnv = (name, value) => {` + `if (process.env.NODE_ENV === 'production' && !value) {` + ``throw new Error(`Missing required production environment variable: ${name}`);`` + `}` + `return value || '';` +`};` + +`middlewareURL: requiredProductionEnv('REACT_APP_MIDDLEWARE_URL', process.env.REACT_APP_MIDDLEWARE_URL),` +`stockfishServerURL: requiredProductionEnv('REACT_APP_STOCKFISH_SERVER_URL', process.env.REACT_APP_STOCKFISH_SERVER_URL),` +`chessServerURL: requiredProductionEnv('REACT_APP_CHESS_SERVER_URL', process.env.REACT_APP_CHESS_SERVER_URL),` +**vite.config.mts**: add all five variables to define, next to the existing two. +`define: {` + `...Object.fromEntries(` + `[` + `'REACT_APP_MIDDLEWARE_URL',` + `'REACT_APP_STOCKFISH_SERVER_URL',` + `'REACT_APP_CHESS_SERVER_URL',` + `'REACT_APP_CHESS_CLIENT_URL',` + `'REACT_APP_AGORA_APP_ID',` + ``].map((name) => [`process.env.${name}`, JSON.stringify(process.env[name] ?? '')])`` + `),` + `'process.env.REACT_APP_API_URL': JSON.stringify(process.env.REACT_APP_API_URL ?? ''),` + `'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV ?? 'development'),` +`},` +Also update the comment at the top of environment.prod.js. It still says Create React App injects the variables. + +### **Phase 3: Add a build guard in CI** + +Add this to the "Build react-ystemandchess" step in ci.yml, so it runs with the same env block as the build: +`run: |` + `npm run build --if-present` + `# Fails if the production URLs didn't reach the bundle (the Vite blank-page bug).` + `grep -q "$REACT_APP_MIDDLEWARE_URL" build/assets/index-*.js` +Dependabot PRs can't read repo secrets, so they build with the fallback URLs in ci.yml. The guard still works, because it checks whatever value the build actually used. + +### **Phase 4: Update the Dockerfile and verify it** + +> 1. Change both FROM lines to node:24-alpine. If Sweksha's pin has already merged, keep her lines when rebasing. +> 2. Change RUN npm install to RUN npm ci in the build stage. +> 3. Build locally with every build arg set, the way deploy.yml will: +> `docker build \` +> `--build-arg REACT_APP_MIDDLEWARE_URL=https://ystemandchess.com/middleware \` +> `--build-arg REACT_APP_STOCKFISH_SERVER_URL=https://ystemandchess.com/stockfishserver \` +> `--build-arg REACT_APP_CHESS_SERVER_URL=https://ystemandchess.com/chessserver \` +> `--build-arg REACT_APP_CHESS_CLIENT_URL=https://ystemandchess.com/chessclient \` +> `--build-arg REACT_APP_AGORA_APP_ID=test \` +> `-t ystemandchess:vite-test react-ystemandchess` +> `docker run --rm -p 3000:3000 ystemandchess:vite-test` +> 4. Open http://localhost:3000 in a browser. Confirm the page renders and the console has no "Missing required production environment variable" error. An HTTP 200 alone doesn't prove anything here. + +### **Phase 5 (recommended, separate commit): Fix the password-reset base URL** + +In reset-password.tsx and set-password.tsx, replace process.env.REACT\_APP\_API\_URL || 'http://localhost:8000' with environment.urls.middlewareURL, imported from environments. Then remove REACT\_APP\_API\_URL from define. Neither test file asserts on the URL, so the tests should keep passing. Test a real reset email end to end after deploy. Keep this change in its own commit, so it can be reverted separately if the endpoint behaves differently than expected. + +### **Phase 6: Pipeline smoke test (Sahana, coordinated in the next section)** + +Add a step to deploy.yml after "Smoke test frontend." It checks that the deployed bundle contains the real middleware URL: +`- name: Smoke test frontend bundle` + `env:` + `FRONTEND_URL: https://ystem-frontend.proudglacier-1911e147.westus2.azurecontainerapps.io` + `EXPECTED_MIDDLEWARE_URL: ${{ secrets.MIDDLEWARE_URL }}` + `run: |` + `js=$(curl --fail -s "$FRONTEND_URL/" | grep -o 'src="/assets/index-[^"]*\.js"' | head -1 | cut -d'"' -f2)` + `test -n "$js"` + `curl --fail -s "$FRONTEND_URL$js" | grep -q "$EXPECTED_MIDDLEWARE_URL"` +The script-tag pattern matches the Vite build output (\ + + + + + + + + +