`createRecipeInternal` écrivait, pour chaque technique détectée, ses ingrédients / ustensiles / températures de clause ; `updateRecipe` et le script `backfill-tech-steps.ts` n'écrivaient que le span de la technique. Toute recette ré-enregistrée via l'UI (ou touchée par le backfill) perdait donc silencieusement ces métadonnées — et `cooking-optimizer.ts` ne pouvait plus détecter de conflit de four entre deux préchauffages dès qu'une recette avait été éditée une fois (ni sur les imports antérieurs à la détection des températures, jamais ré-enregistrés). - `toStepTechStepCreateInputs` (nouveau, `lib/recipe-matching/`) : seule source de vérité du payload `techSteps.create`, utilisée par les trois appelants pour qu'ils ne divergent plus. - `backfill-tech-steps.ts` : `createMany` -> `create` par ligne (createMany ne fait pas les écritures imbriquées) + doc à jour. - Rattrapage automatique au déploiement : `model BackfillState` + migration `backfill_state`, script `backfill-runtime.ts` ajouté au CMD Docker après `seed-runtime`. Gardé par un marqueur (tourne une seule fois), n'échoue jamais le boot (une panne du backfill est loggée, le serveur démarre quand même, retente au prochain start). - Test `PATCH /recipes/:id` : une température de clause survit à l'édition. - `reset-db.ts` : `backfill_state` ajouté au TRUNCATE. Vérifié : `tsc --noEmit` + `pnpm --filter api build` + `pnpm lint` OK ; migration = SQL généré par Prisma pour le modèle, à l'identique. Mocha `recipe.test.ts` non lancé (Postgres + intent-service requis, indisponibles ici) — à jouer en CI / sur la machine de dev. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
4 KiB
Docker
70 lines
4 KiB
Docker
# Debian-based (not alpine) on purpose: avoids musl-vs-glibc native binding
|
|
# surprises for argon2/Prisma's engine binaries. Same base image family for
|
|
# build and runtime stages, so "native" binaries built in `build` are
|
|
# guaranteed compatible with `runtime`.
|
|
FROM node:22-slim AS base
|
|
# Prisma's query engine needs OpenSSL to be present to detect the right
|
|
# binary target; without it, it silently defaults to a guess (openssl-1.1.x)
|
|
# that may not match what's actually on the image and fail at runtime.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends openssl && rm -rf /var/lib/apt/lists/*
|
|
RUN corepack enable
|
|
WORKDIR /repo
|
|
|
|
# Single image serving both the API and the built frontend (apps/web) — one
|
|
# process, one container, no separate nginx/static host. Builds both so the
|
|
# runtime stage below can copy each app's build output independently.
|
|
FROM base AS build
|
|
# `pnpm install` (below) installs every workspace's dependencies, including
|
|
# apps/web's `cypress` devDependency — this image never runs it, so skip its
|
|
# (large, Electron-bundled) binary download: saves build time/bandwidth and
|
|
# removes a network dependency on Cypress's CDN from every production build.
|
|
# The `cypress` npm *package* itself still ends up in `node_modules` (it's a
|
|
# real lockfile entry) and gets copied into `runtime` below — a deliberate
|
|
# tradeoff. Tried to prune it out too (`pnpm prune --prod`, then a full
|
|
# wipe-and-reinstall with `--prod`), but in this pnpm workspace both proved
|
|
# actively unsafe rather than just ineffective: `prune` doesn't cascade into
|
|
# sibling workspace projects' `node_modules` at all, and even a *scoped*
|
|
# `pnpm --filter web prune --prod` emptied out apps/api's entire
|
|
# `node_modules` as a side effect; the wipe-and-reinstall variant silently
|
|
# dropped argon2's compiled native binding (no postinstall re-run to rebuild
|
|
# it), which only surfaced as a crash *after* deploy
|
|
# (`Cannot find module '.../argon2.node'`) — not something to risk on the
|
|
# real production image for what's ~10MB of inert JS once the binary
|
|
# download above is already skipped.
|
|
ENV CYPRESS_INSTALL_BINARY=0
|
|
COPY . .
|
|
RUN pnpm install --frozen-lockfile
|
|
RUN pnpm --filter api build
|
|
RUN pnpm --filter web build
|
|
|
|
# Copies the monorepo structure as-is (not a flattened single package) so
|
|
# pnpm's symlinked node_modules (root node_modules/.pnpm <- apps/api/node_modules)
|
|
# stay valid — paths must match exactly between build and runtime stages.
|
|
FROM base AS runtime
|
|
ENV NODE_ENV=production
|
|
# Tells the API where to find the built frontend — see FRONTEND_DIST_DIR's
|
|
# doc comment in apps/api/src/config/env.ts.
|
|
ENV FRONTEND_DIST_DIR=/repo/apps/web/dist
|
|
COPY --from=build /repo/node_modules ./node_modules
|
|
COPY --from=build /repo/package.json ./package.json
|
|
COPY --from=build /repo/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
COPY --from=build /repo/packages/shared ./packages/shared
|
|
COPY --from=build /repo/packages/error-tools ./packages/error-tools
|
|
COPY --from=build /repo/packages/express-tools ./packages/express-tools
|
|
COPY --from=build /repo/packages/date-tools ./packages/date-tools
|
|
COPY --from=build /repo/apps/api/node_modules ./apps/api/node_modules
|
|
COPY --from=build /repo/apps/api/dist ./apps/api/dist
|
|
COPY --from=build /repo/apps/api/prisma ./apps/api/prisma
|
|
COPY --from=build /repo/apps/api/package.json ./apps/api/package.json
|
|
COPY --from=build /repo/apps/web/dist ./apps/web/dist
|
|
|
|
WORKDIR /repo/apps/api
|
|
EXPOSE 3000
|
|
# Applies pending migrations, seeds the reference data (Diet/Category/
|
|
# Allergy — see src/scripts/seed-runtime.ts), then runs any pending one-time
|
|
# data backfill (src/scripts/backfill-runtime.ts) before starting. migrate
|
|
# deploy only applies pending migrations and the seed upserts by unique
|
|
# name, so both are safe to repeat every start; the backfill step gates
|
|
# itself on a `backfill_state` row (no-op once done) and is written never to
|
|
# fail the boot — see its doc comment.
|
|
CMD ["sh", "-c", "node_modules/.bin/prisma migrate deploy && node dist/scripts/seed-runtime.js && node dist/scripts/backfill-runtime.js && node dist/server.js"]
|