npm run dev now runs scripts/dev-env.ts first, which creates .dev/site.env with a known dev password and a random session secret, so a fresh clone serves a working admin without anyone hand-building a hash. Adds .vscode launch configurations and tasks for the dev server, one test file, and check-site, and brings scripts/ under typecheck, which it had been missing.
5.3 KiB
menu-admin
Uploads, orders and publishes the customer-facing menus of a restaurant Site as
photographed Pages, replacing the scp-a-JPG workflow. One instance per Site;
docs/plan.md is the agreed design and CONTEXT.md is the glossary.
Status
Built: Site config, the Menu Set store, the image pipeline, the customer-facing
renderer (landing page, Section pages, archived sets, media route) with the
approved design, the admin (sign in, create a set, upload and reorder Pages,
preview, publish, restore, delete), the verify/repair CLI, the container and
the Ansible playbook. Not yet done: the parallel-run deployment on monoco,
which needs a human present.
Deploying
./deploy.sh 2026-09-22 # tests, build, push to registry.tanshu.com
then the playbook in ansible-playbooks/menu-admin/ — its readme covers the
parallel run, the site.env step that gives the admin a password, and cutover.
After deploying, check the running site from the outside (read-only, exits non-zero on failure, so it can gate a cutover):
npm run check-site -- https://menu-new.hopsngrains.com
Layout
src/config.ts Site config: brand, theme, Sections, limits
src/storage/manifest.ts Manifest type, validation, atomic JSON writes
src/storage/sets.ts The Menu Set store: create, add Pages, publish, restore, delete
src/storage/validate.ts Boot validation: fatal problems and warnings
src/images/pipeline.ts sharp variants: Original, Viewing image, Thumbnail
src/auth.ts scrypt password hash, session cookie, login throttle, CSRF
src/render/ HTML builder, document shell, the customer pages, the admin
src/routes/public.ts Landing, Section, archive and media routes
src/routes/admin.ts Sign in, dashboard, editor, upload, publish, restore, delete
src/server.ts buildApp (what tests drive) and main (what the container runs)
public/ Hand-written CSS, ~2KB of progressive-enhancement JS, Archivo
config/ Per-Site config, one file per Site
test/ node:test suites
docs/plan.md The agreed plan
docs/operating.md Publishing, fixing and rolling back, for whoever operates it
docs/adr/ Decisions worth remembering
deploy.sh Tests, build, push
The Ansible playbook lives in ansible-playbooks/menu-admin/, beside the other
playbooks, because that is where it is run from.
Running it locally
Node 24 or newer runs the TypeScript sources directly; there is no build step.
npm install
npm run seed # placeholder Pages in .dev (wipes .dev/data; for looking at the design)
npm run dev # http://localhost:3000 against .dev
npm run dev first runs scripts/dev-env.ts, which creates .dev/site.env on
first use with the password menu-dev-password (override with
DEV_ADMIN_PASSWORD=...) and a random session secret, then starts the server
with --watch. Sign in at http://localhost:3000/admin.
The file is gitignored and development only: the dev server runs with
SECURE_COOKIES=false over http, and that password is a known constant. On the
server the same file is written by scripts/provision-site-env.sh with a hash of
the real password and mode 600.
Tests and checks:
npm test # node:test
npm run typecheck # tsc --noEmit, scripts/ included
npm run verify # integrity of .dev/data
npm run check-site -- http://127.0.0.1:3000 # checks a running site from outside
In VS Code
.vscode/ holds launch configurations and tasks.
| Configuration | What it does |
|---|---|
| Dev server (debug) | runs src/server.ts with the dev environment, breakpoints live |
| Dev server (watch, no debugger) | npm run dev in a terminal |
| Debug the open test file | runs the file you have open under node --test, with child processes attached so breakpoints inside a test bind |
| Check the running site | prompts for a URL and runs scripts/check-site.ts |
Breakpoints land in the .ts files themselves: Node strips types in place, so
line numbers are the ones you see. The debug configuration creates .dev/site.env
first (preLaunchTask), so a fresh clone serves a working admin immediately.
Tasks (Command Palette → Run Task) cover the same ground: seed, the dev server,
tests, typecheck, verify and repair. .dev, .tmp and .npm-cache are hidden
from the explorer, search and the file watcher, so a watched server does not
restart on its own data writes.
Who can do what
/ and /<section>/ are public. /admin needs the password; a Draft's Pages
are served only to a signed-in session, and Originals have no route at all.
Storage model
data/
├── originals/<section>/<set-id>/<page-id>.<hash>.<ext> # kept, never served
├── drafts/<section>/<set-id>/ # manifest.json + variants
└── published/<section>/<slug>/ # manifest.json + variants
The directory decides a Menu Set's state; the manifest carries metadata only.
<slug> is the valid-until date, else the publication date. The Current Menu
Set for a Section is the published one with the newest publishedAt. Published
sets are immutable: changing a menu means publishing a new set.