npm could never run `npm ci` here. The Dockerfile said so in a comment: @hanzo/gui pulls a react-native tree whose platform and optional packages resolve differently between npm versions, so a lockfile written by one npm failed under another, and the build ran `npm install` — resolving the tree fresh every time and using the committed lockfile as a suggestion. A lockfile nobody installs from is decoration, which is how it drifted far enough that `npm ci` was already dead on main before this change (react-native-worklets missing from the tree it claimed to describe). pnpm records every platform in the lockfile, so the build installs exactly what is committed and fails loudly rather than quietly resolving something else. Both Dockerfiles now `corepack enable && pnpm install --frozen-lockfile`, corepack takes the version from `packageManager`, and package-lock.json is gone. .gitignore is reversed accordingly: pnpm-lock.yaml is the tracked one and every other manager's lockfile is ignored, so a stray `npm install` cannot leave a second source of truth. pnpm-workspace.yaml carries the two settings this needs, both documented in place. Install scripts are denied unless named, and esbuild and sharp are named because the app does not build without them. The release-age gate is excluded by SCOPE for @hanzo/* rather than by version, because pnpm rewrites a per-version entry on every bump, and a file that rewrites itself during install makes --frozen-lockfile fail in CI — the exact determinism this change exists to get. Verified on pnpm 11.17.0: frozen-lockfile install clean, tsc 0 errors, 3056 tests passing, `pnpm build` compiles, and `pnpm build:embed` emits the real bundle cloud fail-hards on (367KB index.html, 5.5M _next). node_modules stays self-contained — every symlink is relative into node_modules/.pnpm — so the runner stage's COPY --from=build of node_modules still resolves. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
31 lines
1.8 KiB
Docker
31 lines
1.8 KiB
Docker
# hanzoai/console — the EMBED artifact.
|
|
#
|
|
# Builds the console SPA static export (`pnpm build:embed` → out/) ONCE, as a
|
|
# versioned immutable image whose rootfs is just the bundle at /dist. hanzoai/cloud
|
|
# then does `FROM registry.hanzo.ai/hanzoai/console-embed:<ver> AS console` +
|
|
# `COPY --from=console /dist/ webui/dist/` instead of re-running the install+Next export on
|
|
# EVERY cloud release (the ~15-min cache-busted long pole). Console changes far less
|
|
# often than cloud ships, so this moves the build to console's own cadence and turns
|
|
# a cloud rebuild into a registry pull.
|
|
#
|
|
# The Next.js SERVER image (standalone/admin hosts) stays in build-image.yml — this
|
|
# is a separate, additional artifact, not a replacement.
|
|
FROM public.ecr.aws/docker/library/node:24-alpine AS build
|
|
RUN apk add --no-cache git
|
|
WORKDIR /console
|
|
# Heap headroom so the full @hanzo/gui static export never OOMs into a stub; telemetry off.
|
|
ENV NEXT_TELEMETRY_DISABLED=1 NODE_OPTIONS=--max-old-space-size=8192
|
|
# Bake the console.hanzo.ai analytics property (public per-site id) — the SAME default
|
|
# cloud baked at build:embed time, so the embedded console keeps tracking identically.
|
|
# GA4/Pixel stay unset. Public id, not a KMS secret.
|
|
ARG NEXT_PUBLIC_ANALYTICS_WEBSITE_ID=7dce54ee-41f6-4751-96bf-fe005067c7c7
|
|
ENV NEXT_PUBLIC_ANALYTICS_WEBSITE_ID=$NEXT_PUBLIC_ANALYTICS_WEBSITE_ID
|
|
COPY . .
|
|
RUN corepack enable && pnpm install --frozen-lockfile
|
|
# FAIL-HARD: the export MUST emit a real bundle (non-empty out/index.html + out/_next/),
|
|
# never a placeholder shell — same invariant cloud's console stage enforced.
|
|
RUN pnpm build:embed && [ -s out/index.html ] && [ -d out/_next ] \
|
|
&& echo ">> embedded REAL console bundle: $(wc -c < out/index.html)-byte index.html, $(du -sh out/_next | cut -f1) _next/"
|
|
FROM scratch
|
|
COPY --from=build /console/out/ /dist/
|