From 0493329ba875be26dc9230158a502cdbcc0a1661 Mon Sep 17 00:00:00 2001 From: "codepress-dev[bot]" <202219725+codepress-dev[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 18:43:33 +0000 Subject: [PATCH] Bootstrap CodePress repository readiness Co-authored-by: dev@codepress.dev --- .claude/skills/pr-screenshot/SKILL.md | 190 +++++++++++++++++++++ .claude/skills/start-app-server/SKILL.md | 104 ++++++++++++ .claude/skills/verify-local/SKILL.md | 203 +++++++++++++++++++++++ .codepress/start-app-server/recipe.json | 21 +++ .dockerignore | 15 ++ Dockerfile | 35 ++++ web/e2e/playwright.config.js | 17 ++ web/package.json | 1 + web/yarn.lock | 26 +++ 9 files changed, 612 insertions(+) create mode 100644 .claude/skills/pr-screenshot/SKILL.md create mode 100644 .claude/skills/start-app-server/SKILL.md create mode 100644 .claude/skills/verify-local/SKILL.md create mode 100644 .codepress/start-app-server/recipe.json create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 web/e2e/playwright.config.js diff --git a/.claude/skills/pr-screenshot/SKILL.md b/.claude/skills/pr-screenshot/SKILL.md new file mode 100644 index 0000000..1c65c69 --- /dev/null +++ b/.claude/skills/pr-screenshot/SKILL.md @@ -0,0 +1,190 @@ +--- +name: pr-screenshot +description: "Capture and attach frontend screenshots and videos to pull requests for quantfive/django-react-intro. Auto-triggers when creating a PR with UI changes under web/src or web/public. Uses Playwright to render the affected page and uploads assets to the PR. IMPORTANT: Use this skill whenever a PR changes visible frontend behavior. Trigger on: 'make a pr', 'create a pr', 'open a pull request', or any request that results in a PR with frontend file changes." +user_invocable: true +codepress_generated: true +--- + +## When to Trigger + +Run this skill automatically when a pull request changes visible frontend behavior, including: + +- `web/src/**/*.{js,jsx,css,svg}` +- `web/public/**/*.{html,css,ico,json,png,svg}` +- `web/package.json` or `web/yarn.lock` when the dependency change affects the UI +- `server/server/views.py` or templates when the change affects the page served at `/` + +Do not run it for backend-only changes, database changes, infrastructure or CI changes, documentation-only changes, or test-only changes with no visible UI effect. + +## What to Capture + +This repository has one frontend app and one public route: + +| Changed files | What to screenshot | URL | +| --- | --- | --- | +| `web/src/containers/home/**` | Home page content and branding | `/` | +| `web/src/containers/App/**` | Application shell and route behavior | `/` | +| `web/src/containers/home/stylesheets/**`, `web/src/index.css` | Home page layout and styling | `/` | +| `web/src/index.js`, `web/public/**` | Bootstrapped home page | `/` | +| `server/server/views.py` or the frontend template | Django-served frontend shell | `/` | + +The frontend is a Create React App application using `react-scripts@1.0.10`, React 15, Redux, and React Router. The current route is public and does not require authentication or API mocks. The current home-page readiness checks are the `Welcome to React` heading and the image with alt text `logo`; when a PR changes a different element, replace those checks with a semantic locator for the changed feature. + +Use a screenshot for static layout, color, typography, and component changes. Use a video when the change includes an animation, transition, hover or click interaction, loading state, or other time-based behavior. Capture both when reviewers need a static preview and an interaction demonstration. + +## Dev Server + +### Option A: CodePress container capture + +If the repository has `.claude/skills/start-app-server/SKILL.md` and `.codepress/start-app-server/recipe.json`, follow that skill to build and start the full-stack container, then use `take_app_server_screenshot` against `/` with the recipe port. Prefer a viewport of 1440 by 1000 and wait up to 1000 milliseconds after load so the page is settled. + +The existing `.codepress/dev-server/recipe.json` describes the `web` CRA dev server at port 3000 and points to `.codepress/dev-server/Dockerfile.web`. That Dockerfile is a thin Live Dev Server image which expects the CodePress Live Dev Server source mount; do not pass it directly to `build_and_start_app_server` without that mount. If the Live Dev Server transport is available, use its `web` entry; otherwise use Option B. + +### Option B: Local Playwright capture + +The durable Playwright config is `web/e2e/playwright.config.js`. It starts the CRA server on port 3000 with the repo's Yarn 1 lockfile: + +```bash +cd web +npx --yes yarn@1.22.22 install --frozen-lockfile +npx --yes yarn@1.22.22 playwright install chromium +npx --yes yarn@1.22.22 playwright test \ + --config e2e/playwright.config.js \ + e2e/tests/_pr-capture.spec.js \ + --workers=1 +``` + +The CRA toolchain is from 2017. Use Node 18 for the frontend runtime; the host Node 25 runtime is incompatible with its `websocket-driver` dependency and fails with `No such module: http_parser`. The temporary capture spec must be removed after the run. + +The frontend home route is standalone, so no Django server is needed for the current screenshot contract. If a future change adds a backend request, start the Django app according to the repo-local app-server recipe and add the required route mocks or service URL to the spec rather than hiding a failing request. + +## Capture Spec Template + +Create a temporary file at `web/e2e/tests/_pr-capture.spec.js`: + +```javascript +const { test, expect } = require('@playwright/test'); + +test('capture the changed home-page feature', async ({ page }) => { + await page.goto('/', { waitUntil: 'networkidle' }); + + const feature = page.getByRole('heading', { name: 'Welcome to React' }); + await expect(feature).toBeVisible(); + await feature.scrollIntoViewIfNeeded(); + await page.waitForTimeout(500); + + await page.screenshot({ + path: '/tmp/pr-screenshots/pr-screenshot-home.png', + fullPage: false, + }); +}); +``` + +Keep the assertion focused on the changed feature body, not only the page shell. Use `getByRole`, `getByText`, or another stable semantic locator because the repository currently has no `data-testid` convention. Set a mobile viewport of 390 by 844 as an additional capture when the PR changes responsive behavior. + +For interaction changes, use this video pattern and close the page after the final state so the recording is flushed: + +```javascript +test.use({ + video: { mode: 'on', size: { width: 1280, height: 720 } }, + viewport: { width: 1280, height: 720 }, +}); + +test('record the changed home-page interaction', async ({ page }, testInfo) => { + await page.goto('/', { waitUntil: 'networkidle' }); + await page.getByRole('heading', { name: 'Welcome to React' }).waitFor({ state: 'visible' }); + // Perform the user interaction here and wait for its final visible state. + await page.waitForTimeout(750); + await page.close(); + + const video = testInfo.attachments.find((attachment) => attachment.name === 'video'); + if (video && video.path) { + const fs = require('fs'); + fs.mkdirSync('/tmp/pr-screenshots', { recursive: true }); + fs.copyFileSync(video.path, '/tmp/pr-screenshots/pr-video-home.webm'); + } +}); +``` + +## Running the Spec + +Kill stale local servers before capture, then remove the temporary spec and output: + +```bash +cd web +lsof -ti :3000 2>/dev/null | xargs kill 2>/dev/null || true +rm -rf /tmp/pr-screenshots +mkdir -p /tmp/pr-screenshots +npx --yes yarn@1.22.22 playwright test \ + --config e2e/playwright.config.js \ + e2e/tests/_pr-capture.spec.js \ + --workers=1 +rm -f e2e/tests/_pr-capture.spec.js +rm -rf /tmp/pr-screenshots +``` + +Before reporting success, confirm each screenshot is larger than 10 KB, the changed feature is visible, and the capture is not only a blank shell, loading state, or page header. If the server fails, inspect the full Playwright web-server output; common causes here are the wrong Node runtime, a stale port 3000 process, or an old CRA dependency failure. + +## Before and After Capture + +For visual changes, capture the same spec on the current branch and on the merge base so reviewers can compare the result: + +1. Capture the current branch into a clean output directory and keep the files with their commit SHA. +2. Set `BASE_SHA=$(git merge-base origin/master HEAD)` and create a detached temporary worktree at that SHA. +3. Copy the temporary capture spec and any local environment file needed for the render into the worktree, install the `web` Yarn dependencies, and run the same config. +4. Save the base screenshots with a `-before` suffix, remove the temporary worktree, and keep after-only evidence if the base tree cannot render. + +Never reuse a stale output directory between the before and after passes. A new page or a fixture drift is a valid reason to omit the before image; it is not a reason to discard a valid after capture. + +## Upload and Embed in the PR + +In a CodePress cloud session, use `upload_pr_asset` for each PNG, GIF, and original video. Embed the returned permanent URL in the PR description, wrapping images as a link so reviewers can open the full resolution. + +For the local fallback, upload assets to the `pr-assets` release in `quantfive/django-react-intro`: + +```bash +gh release upload pr-assets /tmp/pr-screenshots/pr-screenshot-*.png \ + --repo quantfive/django-react-intro --clobber +gh release upload pr-assets /tmp/pr-screenshots/pr-video-*.webm \ + --repo quantfive/django-react-intro --clobber +``` + +Add a `## Demo` section to the PR body and identify the capture commit. Use a before/after table when both images exist: + +```markdown +## Demo + + + +| Before | After | +| --- | --- | +| [![before](BEFORE_URL)](BEFORE_URL) | [![after](AFTER_URL)](AFTER_URL) | +``` + +Convert WebM interaction recordings to GIF for inline playback and link the original WebM separately. Do not use HTML video tags in GitHub markdown. + +## GitHub Release Setup + +The release fallback is only needed when `upload_pr_asset` is unavailable. Create it once with: + +```bash +gh release create pr-assets \ + --repo quantfive/django-react-intro \ + --title "PR Assets" \ + --notes "Screenshots and assets referenced in pull requests." \ + --latest=false +``` + +## Tips + +- Prefer a bounded viewport over `fullPage` so the changed feature remains readable. +- Capture every route affected by the diff; this repository currently has only `/`. +- Add a 390 by 844 capture for mobile or responsive changes. +- Keep videos under 10 seconds and trim loading pre-roll when possible. +- Skip screenshots for structural-only changes such as selector renames with no visual effect. + +## Known Issues at Bootstrap Time + +- The existing Live Dev Server Dockerfile is source-mount dependent and cannot be used as a standalone `build_and_start_app_server` image. +- The legacy CRA dependency tree requires Node 18; Node 25 fails before the app starts because its `websocket-driver` expects the removed `http_parser` binding. +- A real screenshot was captured successfully from an isolated Node 18 container at `/`, with a 1440 by 1000 viewport and a 22 KB PNG result. The route returned HTTP 200 and rendered the `Welcome to React` heading and logo. diff --git a/.claude/skills/start-app-server/SKILL.md b/.claude/skills/start-app-server/SKILL.md new file mode 100644 index 0000000..c97bb91 --- /dev/null +++ b/.claude/skills/start-app-server/SKILL.md @@ -0,0 +1,104 @@ +--- +name: start-app-server +description: "Start the quantfive/django-react-intro app server in a Docker container and validate it responds. Uses a pre-validated recipe with no discovery or guessing. Triggers on: 'spin up the server', 'run my app', 'start the server', 'verify app server', 'test my server', 'run app server', 'spin up the app'." +user_invocable: true +codepress_generated: true +--- + +# Start App Server — quantfive/django-react-intro + +Fast-path skill for starting this repository's app server. Discovery and one repair were completed during bootstrap on 2026-08-06T18:37:57Z; execute the recipe below rather than rediscovering the stack. + +The machine-readable recipe lives at `.codepress/start-app-server/recipe.json`. + +## Tools + +Use these tools directly: + +- `build_and_start_app_server` — build the image and start the container +- `forward_app_request` — send an HTTP request into the running container +- `get_app_server_logs` — inspect container stdout and stderr +- `stop_app_server` — stop and remove a container when cleanup is required + +## Static Context + +- **Stack**: Django 1.11.5 with a Create React App 1.0.10 frontend; Node 18 builds the frontend and Python 3.6 runs Django. +- **Dockerfile**: `Dockerfile` +- **Port**: `8000` +- **Validation**: `GET /` with status in `[200, 301, 302, 404]` +- **Services**: none; the app uses repository-local SQLite. +- **Required secrets**: none. + +## Step 1: Drift Check + +Compare the current inputs with the recipe checksums: + +```bash +git hash-object Dockerfile # expected prefix: 55b86d98d1d0e26a +git hash-object server/Pipfile # expected prefix: a4af380b1d34c482 +``` + +If either value differs, proceed with the current files but report the drift. The build is still the source of truth for whether the server works. + +## Step 2: Fetch Secrets + +This app has no vault-backed secrets. Use an empty environment map and continue. + +## Step 3: Build and Start + +Call: + +```text +build_and_start_app_server( + workspaceDir=, + port=8000, + dockerfilePath="Dockerfile", + envVars={} +) +``` + +The Dockerfile installs both dependency trees inside the image. It builds `web/` with Yarn 1.22.22, copies the resulting assets to `server/static/build`, installs the locked Python dependencies from `server/Pipfile.lock`, applies the initial SQLite migrations, and starts Django on `0.0.0.0:8000`. + +If retrying after a fix in the same session, pass `existingContainerId` from the previous attempt so the old container is replaced cleanly. + +## Step 4: Validate + +If the start tool reports that the health check is ready, send: + +```text +forward_app_request( + containerId=, + path="/", + method="GET" +) +``` + +Accept status `200`, `301`, `302`, or `404`. The expected healthy response for this repository is `HTTP 200` with the React HTML shell and `/static/js/` and `/static/css/` asset references. + +If health is timed out, poll `GET /` up to 12 times at 5-second intervals. Inspect `get_app_server_logs` before the final retry. A crash, a non-allowlisted 4xx, a 5xx response, or exhaustion of the poll budget is a failure. + +## Step 5: Report + +Report the container ID, port `8000`, the `forward_app_request` command for the root route, and the `stop_app_server` command. Leave a successfully started container running so the caller can continue verification. Stop only an explicitly requested teardown or a failed container that would otherwise leak. + +## Known Fixes + +- The legacy CRA `postbuild` script copies into `../server/static/build`; the Dockerfile creates `server/static` before `yarn build`. +- Django's SQLite path is inside `/app/server`, while the container runs as UID 65534; the Dockerfile chowns that tree before startup. +- The old frontend uses Yarn 1.22.22 and is built with Node 18. The install command uses `--ignore-engines` because the repository now includes the Playwright package for screenshot capture. +- Django auth/admin routes need the initial SQLite schema, so migrations run during the image build before the app directory is chowned for the runtime user. +- The app binds to `0.0.0.0:8000` in the foreground so the container proxy can reach it. + +## Repair on Failure + +Before repairing, read `repair_count` from `.codepress/start-app-server/recipe.json`. If it is already 3 or higher, stop and report that the recipe needs a fresh bootstrap rather than attempting another repair. + +For a repair attempt: + +1. Read the full build error and `get_app_server_logs` output. +2. Fix all related issues in one edit pass; do not rebuild after only the first symptom. +3. Retry `build_and_start_app_server` with the previous `existingContainerId`. +4. If the recipe itself changes, increment `repair_count`, set `origin` to `repair`, update `bootstrapped_at`, recompute the two input checksums, and append the fix to `known_fixes`. +5. Re-run the HTTP validation before treating the repair as successful. + +Never put credentials in the recipe or Dockerfile. Do not solve a failed HTTP check by weakening the verification status list. diff --git a/.claude/skills/verify-local/SKILL.md b/.claude/skills/verify-local/SKILL.md new file mode 100644 index 0000000..5aa5474 --- /dev/null +++ b/.claude/skills/verify-local/SKILL.md @@ -0,0 +1,203 @@ +--- +name: verify-local +description: "Verify quantfive/django-react-intro locally: start the Django/React app in Docker from its bootstrapped recipe, run HTTP contract assertions, and report the evidence to the open pull request. Triggers on: 'verify locally', 'local verify', 'local validation', 'test locally', 'run checks'." +user_invocable: true +codepress_generated: true +--- + +# Verify Local — quantfive/django-react-intro + +Run the app in its real Docker container, then exercise the HTTP contract below with `forward_app_request`. The server configuration comes from `.codepress/start-app-server/recipe.json` and `.claude/skills/start-app-server/SKILL.md`; do not hand-start a different server or mock the backend. + +This contract is deliberately trigger-based. It covers every observable route found during bootstrap and adds branch-specific rows for changed files. A health check alone is not a verification result. + +## Quick Reference + +| Field | Value | +| --- | --- | +| Server runtime | Docker via the `start-app-server` recipe | +| Mode | Local development defaults from the recipe | +| Health endpoint | `GET /` → status in `[200, 301, 302, 404]` | +| Auth flow | No auth gate protects the React shell. Django REST Auth exposes session/token endpoints; anonymous access to `/api/auth/user/` is denied. | +| Test account | No seed script or test account was found; never invent credentials. | +| Session cookie | Not used for the default contract; use `useCookieJar: true` only after a real login flow is discovered. | + +## Step 0: Confirm the Recipe + +Read `.codepress/start-app-server/recipe.json`. It must be valid JSON with `schema_version` equal to `1`, `dockerfile_path` equal to `Dockerfile`, and `port` equal to `8000`. If it is missing or invalid, stop and ask for the app-server bootstrap to be rerun. + +The current recipe uses no vault secrets, static environment variables, or companion services. Its validation path is `/` and its accepted startup statuses are `200`, `301`, `302`, and `404`. + +## Step 1: Inventory the Diff and Write the Contract + +Identify the live default branch and inspect the branch diff: + +```bash +DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo master) +git diff "$DEFAULT_BRANCH"...HEAD --stat +git diff "$DEFAULT_BRANCH"...HEAD --name-only +git log "$DEFAULT_BRANCH"..HEAD --oneline +``` + +Start with this inventory, then add a live row for every changed behavior that can be exercised through HTTP or a rendered page: + +| Surface | Location | Contract item | +| --- | --- | --- | +| React shell | `GET /` | #1 returns HTTP 200 and contains the root mount plus built static asset references | +| REST login contract | `OPTIONS /api/auth/login/` | #2 returns HTTP 200, advertises `POST, OPTIONS`, and describes the username/email/password fields | +| Anonymous auth boundary | `GET /api/auth/user/` without credentials | #3 returns HTTP 403 with an authentication-required message | +| Changed React UI | Any diff under `web/src/**` or `web/public/**` | #4 captures `/` with a visible changed feature; add only when the diff touches visible UI | + +The app intentionally ends its Django URL list with a React catch-all. Unknown browser paths can therefore return the React shell with HTTP 200; do not use a generic nonexistent URL as a 404 assertion. The admin login route is not in the default contract because the repository has no admin test fixture or seeded `django_site` record. + +For every added row, name the real trigger, expected status, response evidence, and the negative assertion that would prove a failure. Pure refactors, log absence, static inspection, and a container health signal do not replace a live contract item. + +## Step 2: Start the App Server + +Follow the local fast path in `.claude/skills/start-app-server/SKILL.md`, which calls `build_and_start_app_server` with `Dockerfile` on port `8000`. Capture the returned container ID as `CONTAINER_ID`. If the caller supplied a reusable verification environment, reuse it only when its recorded head SHA matches the current `git rev-parse HEAD` and its `GET /` health check still passes. + +Write or update an environment record at `/tmp/codepress-qa-verifier-runs/verify-env---.json` containing: + +```json +{ + "schema_version": 1, + "mode": "local", + "head_sha": "", + "diff_range": "...HEAD", + "environment": { + "container_id": "", + "base_url": "http://localhost:8000" + }, + "owned_by_verifier": true, + "health_check": "GET / -> ", + "reuse_instructions": "Reuse only for the same head SHA while GET / remains healthy.", + "teardown": "stop_app_server(containerId=CONTAINER_ID)" +} +``` + +If startup fails, report the complete tool error and stop. Do not replace the container with a hand-started Django process or a mocked response. + +## Step 3: Authenticate When a Real Flow Exists + +The default contract has no login step because no test account was found and no application route requires authentication. Do not send guessed credentials. + +Still exercise the anonymous boundary in contract item #3. If a future branch adds a documented seed account or login fixture, use the real REST endpoint at `/api/auth/login/`, pass the discovered request body, set `useCookieJar: true`, and then verify an authenticated endpoint with the stored session. Record the cookie name and account source without writing the password into the report. + +## Step 4: Execute the HTTP Contract + +Run each item against the same `CONTAINER_ID`, record the actual status and a response excerpt, and explain what the result proves: + +```text +forward_app_request(containerId=CONTAINER_ID, path="/", method="GET") +``` + +Pass item #1 only when the response is HTTP 200 and the body contains `id="root"`, `/static/js/`, and `/static/css/`. + +```text +forward_app_request(containerId=CONTAINER_ID, path="/api/auth/login/", method="OPTIONS") +``` + +Pass item #2 only when the response is HTTP 200, the headers include `Allow: POST, OPTIONS`, and the JSON body describes the login fields. + +```text +forward_app_request(containerId=CONTAINER_ID, path="/api/auth/user/", method="GET") +``` + +Pass item #3 only when the response is HTTP 403 and its JSON body says that authentication credentials were not provided. A 200 response here is a security failure, not a success. + +For UI changes, also capture the affected route with `take_app_server_screenshot` after the HTTP checks: + +```text +take_app_server_screenshot( + containerId=CONTAINER_ID, + path="/", + viewport={"width": 1280, "height": 800}, + wait_ms=1000 +) +``` + +Screenshot evidence is supplemental. A changed UI behavior is not verified by a screenshot alone; add a real interaction or response assertion for the behavior whenever the app exposes one. + +Do not use mocks or synthetic success responses. If a contract row cannot be exercised from a real app-server route, keep it in the report as `FAIL` for incomplete local coverage. + +## Step 5: Review Logs + +After all requests, inspect the container logs: + +```text +get_app_server_logs(containerId=CONTAINER_ID, tail=500) +``` + +Note errors or stack traces related to the changed behavior. A clean log review supports the HTTP evidence but cannot replace it. + +## Step 6: Write the Verification Report + +Write `/tmp/local-verification-report.md`. The first line must be exactly: + +```text +@codepress /judge-verification can you judge this verification? +``` + +Use this structure: + +```markdown +@codepress /judge-verification can you judge this verification? + +## Local Verification — quantfive/django-react-intro + +**PR Head SHA:** `` + + + +**Environment artifact:** `/tmp/codepress-qa-verifier-runs/verify-env---.json` + +### Diff Trigger Inventory + +| Surface | Location | Contract item | +| --- | --- | --- | +| React shell | `GET /` | #1 | + +### Verification Contract Results + +| # | Assertion | Result | Details | +| --- | --- | --- | --- | +| 1 | `GET /` → 200 | ✅ PASS | Actual status and response markers | +| 2 | `OPTIONS /api/auth/login/` → 200 | ✅ PASS | Actual status, Allow header, and response fields | +| 3 | Anonymous `GET /api/auth/user/` → 403 | ✅ PASS | Actual status and denial body | + +### Log Review + +- No errors related to the contract, or describe the exact finding. + +### Container + +- ID: `` +- Stopped: yes / no + +### Overall: ✅ PASS +``` + +Use `❌ FAIL` for any failed assertion or any changed behavior left outside the live HTTP contract. Overall `✅ PASS` is allowed only when every gap-worthy inventory row has a passing live result. + +## Step 7: Post the Report When a PR Exists + +If the current branch has an open pull request, include the current live head SHA and the `codepress-verify-result` marker, then post the exact report with `post_pr_comment` using the repository root and PR number. If no PR exists, keep the report at `/tmp/local-verification-report.md` and return its path. + +Do not post a report for a different head SHA. A generated artifact PR should use `post_generated_artifact_verification_report`; an ordinary PR should use `post_pr_comment`. + +## Step 8: Cleanup + +If no PR or follow-up judge will reuse the environment, stop the container: + +```text +stop_app_server(containerId=CONTAINER_ID) +``` + +If a PR exists and the orchestrator is retaining the environment for a judge or another verification pass, leave it running and record that ownership in the environment artifact. + +## Known Issues at Bootstrap Time + +- No seed script or test account was found, so authenticated success paths are not part of the default contract. +- The React catch-all intentionally serves the shell for unknown browser paths; generic 404 checks are not meaningful for this repository. +- The admin route can require a seeded `django_site` row; it is excluded from the default contract until the repository provides a fixture. +- The root route and REST auth contract were exercised against the real container during bootstrap: `/` returned 200, `OPTIONS /api/auth/login/` returned 200, and anonymous `GET /api/auth/user/` returned 403. diff --git a/.codepress/start-app-server/recipe.json b/.codepress/start-app-server/recipe.json new file mode 100644 index 0000000..56935c7 --- /dev/null +++ b/.codepress/start-app-server/recipe.json @@ -0,0 +1,21 @@ +{ + "schema_version": 1, + "bootstrapped_at": "2026-08-06T18:37:57Z", + "origin": "repair", + "repair_count": 1, + "dockerfile_path": "Dockerfile", + "port": 8000, + "validation_status_codes": [200, 301, 302, 404], + "input_checksums": { + "dockerfile": "55b86d98d1d0e26a", + "dependency_manifest": "a4af380b1d34c482" + }, + "system_packages": [], + "known_fixes": [ + "The legacy CRA postbuild hook writes into server/static/build, so the Dockerfile creates server/static before running yarn build.", + "Django uses a repository-local SQLite database and the container runs as nobody, so the Dockerfile chowns /app/server to UID 65534.", + "The frontend lockfile requires Yarn 1.22.22 and the legacy dependency tree needs the image build to ignore modern Playwright engine metadata.", + "Django auth/admin routes require the initial SQLite schema, so the Dockerfile applies migrations before startup." + ], + "discovery_notes": "This repository combines a Django 1.11.5 backend with a Create React App 1.0.10 frontend under web/. The Dockerfile builds the React bundle with Node 18, copies it into Django's static/build directory, installs the locked Python 3.6-era dependencies, and serves the root route with Django's development WSGI server on port 8000. The backend uses local SQLite and exposes no configured remote service or vault secret dependency." +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..26a0307 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.github +.claude +.codepress +node_modules +web/node_modules +web/build +server/static/build +server/.venv +**/__pycache__ +**/*.pyc +*.log +.env +.env.* +!.env.example diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..55b86d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# Generated by CodePress bootstrap-app-server. +# Edits are preserved, but running /codepress-bootstrap-app-server again may overwrite them. + +FROM public.ecr.aws/docker/library/node:18-bookworm AS frontend-build + +RUN corepack disable && npm install -g yarn@1.22.22 + +WORKDIR /app +COPY server /app/server +COPY web/package.json web/yarn.lock /app/web/ +WORKDIR /app/web +RUN yarn install --frozen-lockfile --ignore-engines +COPY web/ /app/web/ +RUN mkdir -p /app/server/static +RUN yarn build + +FROM public.ecr.aws/docker/library/python:3.6-slim AS app + +ENV PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 + +WORKDIR /app/server + +RUN pip install --no-cache-dir 'pip<22' 'setuptools<60' 'pipenv==2020.11.15' + +COPY server/Pipfile server/Pipfile.lock /app/server/ +RUN pipenv install --system --deploy + +COPY server/ /app/server/ +COPY --from=frontend-build /app/server/static/build /app/server/static/build +RUN python manage.py migrate --noinput +RUN chown -R 65534:65534 /app/server + +EXPOSE 8000 +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/web/e2e/playwright.config.js b/web/e2e/playwright.config.js new file mode 100644 index 0000000..31c58e6 --- /dev/null +++ b/web/e2e/playwright.config.js @@ -0,0 +1,17 @@ +const { defineConfig } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests', + timeout: 30_000, + use: { + baseURL: 'http://127.0.0.1:3000', + viewport: { width: 1440, height: 1000 }, + trace: 'retain-on-failure', + }, + webServer: { + command: 'HOST=0.0.0.0 BROWSER=none npx --yes yarn@1.22.22 start', + port: 3000, + reuseExistingServer: true, + timeout: 120_000, + }, +}); diff --git a/web/package.json b/web/package.json index e334e8c..82f2e03 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "redux-thunk": "^2.2.0" }, "devDependencies": { + "@playwright/test": "^1.62.1", "react-scripts": "1.0.10" }, "scripts": { diff --git a/web/yarn.lock b/web/yarn.lock index 69ad62f..254088e 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@playwright/test@^1.62.1": + version "1.62.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.62.1.tgz#68e1aaf6e480e1923936dee6c66fae1921d3a65a" + integrity sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ== + dependencies: + playwright "1.62.1" + abab@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" @@ -2607,6 +2614,11 @@ fsevents@1.1.2, fsevents@^1.0.0: nan "^2.3.0" node-pre-gyp "^0.6.36" +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fstream-ignore@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" @@ -4495,6 +4507,20 @@ pkg-up@^1.0.0: dependencies: find-up "^1.0.0" +playwright-core@1.62.1: + version "1.62.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.62.1.tgz#120f67a19181bfd183c60fa903c0d99330b56785" + integrity sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw== + +playwright@1.62.1: + version "1.62.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.62.1.tgz#8447b6755e8aec85a3cb7207c823e3ed2fc66700" + integrity sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg== + dependencies: + playwright-core "1.62.1" + optionalDependencies: + fsevents "2.3.2" + pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"