-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
244 lines (226 loc) · 17.1 KB
/
Copy pathDockerfile
File metadata and controls
244 lines (226 loc) · 17.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
ARG PNPM_VERSION=9.15.9
FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat dumb-init chromium
RUN if [ -x /usr/bin/chromium-browser ] && [ ! -e /usr/bin/chromium ]; then ln -s /usr/bin/chromium-browser /usr/bin/chromium; fi
RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate
ENV CHROMIUM_PATH=/usr/bin/chromium
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
# ─── Collect all package.json for lockfile resolution ────────────────────────
# pnpm install --frozen-lockfile needs every workspace package.json present.
# We copy the full repo into a temp stage, then extract only package.json files.
FROM base AS manifests
WORKDIR /app
COPY . .
RUN mkdir -p /manifests && \
find packages services benchmarks -type f -name "package.json" 2>/dev/null | grep -v "node_modules" | while read -r f; do \
mkdir -p "/manifests/$(dirname "$f")" && cp "$f" "/manifests/$f"; \
done
# ─── Install all deps ────────────────────────────────────────────────────────
FROM base AS deps
WORKDIR /app
# .npmrc carries `shamefully-hoist=true` — REQUIRED so @types/* (e.g. @types/react) hoist flat
# into node_modules and are resolvable by packages that declare react as a PEER dep
# (studio-plugin-sdk, ui). Omitting it made their tsc dts emit fail TS2503/TS7016 on react,
# silently masked until 2568f997e removed the || true suppressions. (2026-06-07 studio fix.)
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.base.json tsconfig.json .npmrc ./
COPY --from=manifests /manifests/ ./
COPY scripts/docker/build-engine-no-dts.sh scripts/docker/build-engine-no-dts.sh
RUN pnpm install --frozen-lockfile --ignore-scripts
# ─── Build ───────────────────────────────────────────────────────────────────
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/ ./
# Copy ALL workspace packages in one shot. Was 27 individual COPY lines that
# silently missed transitively-imported packages (snn-webgpu, marketplace-agentkit,
# uaal, holomap, etc.) causing a whack-a-mole of "Cannot find module
# '@holoscript/<name>'" deploy failures. Studio's tsconfig has
# `"@holoscript/engine": ["../engine/src"]` so tsc walks engine SOURCE, which
# transitively imports ~15 workspace packages — every one needs to be on disk.
# .dockerignore excludes node_modules, dist, .git, demos, samples, etc., so
# this pulls only source + package.json files (~30MB).
COPY packages/ packages/
# Build workspace dependencies in topological order with DTS emitted.
# Why dts everywhere: studio's tsconfig has `"@holoscript/engine": ["../engine/src"]`
# so studio's tsc step walks engine SOURCE, which transitively imports types from
# ~15 workspace packages. Without dts on disk, tsc reports "Could not find a
# declaration file for module '@holoscript/<name>'" — the whack-a-mole pattern
# we hit 7 times this session (BrowserQuiltRenderer, core-types/ans, jsonwebtoken,
# marketplace-agentkit, transformers, three, crdt-spatial, snn-webgpu).
# Engine itself stays --no-dts (build-engine-no-dts.sh) — its tsup OOMs on the
# full graph; engine src is walked directly via tsconfig path alias instead.
# core stays --no-dts because it has hand-crafted index.d.ts via generate-types.mjs.
# DTS patterns (both fail loudly):
# A) pnpm exec tsup — packages with dts:true in tsup.config.ts; JS+dts in one pass.
# B) pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# — packages where tsup's rollup-plugin-dts fails on complex type graphs;
# JS via tsup (fail loud on JS errors), .d.ts via tsc (fail loud on missing types).
RUN cd packages/crdt && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/crdt-spatial && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# snn-webgpu: engine/src/simulation/SNNCognitionEngine.ts imports from
# '@holoscript/snn-webgpu'; without dist/index.d.ts Studio's type-check fails TS7016.
# Uses tsup --dts-only (not pattern B tsc) because WGSL shader types need tsup's
# resolver. Emit is heavy (~50s, large type graph + WGSL) — bump the heap.
RUN cd packages/snn-webgpu && pnpm exec tsup --no-dts && NODE_OPTIONS=--max-old-space-size=4096 pnpm exec tsup --dts-only
RUN cd packages/uaal && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/agent-protocol && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/core-types && pnpm exec tsup
RUN cd packages/core && pnpm exec tsup --no-dts && node scripts/generate-types.mjs
RUN chmod +x scripts/docker/build-engine-no-dts.sh && ./scripts/docker/build-engine-no-dts.sh
# llm-provider MUST build before framework: framework/src/ai/adapters.ts and
# framework/src/llm/llm-adapter.ts import '@holoscript/llm-provider' (workspace
# dep), so framework's `tsc -p tsconfig.build.json` step below needs
# llm-provider's dist/*.d.ts on disk or it fails with TS2307 "Cannot find module
# '@holoscript/llm-provider'" (broke 3 consecutive Studio deploys 2026-05-26).
# llm-provider has no workspace deps, so it can build right after engine.
RUN cd packages/llm-provider && pnpm exec tsup
# Framework needs BOTH the tsup JS build AND a tsc declaration emit because
# tsup.config.ts has dts: false (subpath dts via tsup is unreliable for this
# package). Without the tsc step, dist/agents/index.d.ts etc. don't exist, and
# Studio's `next build` type-check fails on `@holoscript/framework/agents`
# with TS7016. The custom scripts/build-framework.mjs would do this in 4
# memory-tuned batches, but requests up to 6GB heap which OOMs in Railway's
# build container. The single-pass tsup proved fine in the prior Dockerfile,
# so we keep it + add the missing tsc declaration step.
#
# Both steps fail loudly. Historical note: core once re-exported domain plugins
# from `@holoscript/core/traits`, pulling ~189 plugin source files (incl. `.tsx`
# importing react/three) into framework's program → TS2307. That re-export was
# removed (core/src/traits/index.ts + generate-types.mjs); tsc type-checks clean.
#
# Framework MUST build before engine's dts-only step: engine/src/choreography/
# StepExecutor.ts imports '@holoscript/framework/agents', so tsup --dts-only
# needs framework's dist/agents/index.d.ts on disk or it fails TS2307
# "Cannot find module '@holoscript/framework/agents'" (broke studio deploy 2026-06-07).
RUN cd packages/framework && pnpm exec tsup --no-dts && pnpm exec tsc -p tsconfig.build.json
# NOTE: engine is intentionally built --no-dts (line above). No package needs engine's .d.ts:
# mesh tolerates it via --noImplicitAny false (below) and Studio's next build resolves engine via
# its ../engine/src tsconfig alias. An engine `tsup --dts-only` step was tried (2026-06-07) but is
# redundant AND triggers a holoembed/holo-vm dep-ordering cascade — removed. Validated by local
# `docker build --target builder` from a clean origin/main worktree before push.
# std: tsup's rollup-plugin-dts fails (chokidar/glob in src/fs causes it to exit
# non-zero). Use tsc --emitDeclarationOnly directly (pattern B).
# tsconfig.json has declaration:true + outDir:dist so this produces dist/index.d.ts.
RUN cd packages/std && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly
RUN cd packages/config && pnpm exec tsup
RUN cd packages/absorb-service && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/platform && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# mesh declares @holoscript/engine as an OPTIONAL peer dep, and engine is built --no-dts here
# (by design — the wgsl no-dts engine layer), so mesh's dts emit hits TS7016 on engine's subpaths
# (resolves engine's .js from dist, no .d.ts). --noImplicitAny false lets those resolve as `any`
# in mesh's emitted .d.ts (fine for an optional peer) while keeping mesh's other type checks.
# Studio's next build resolves engine via its own src alias (../engine/src + holoembed/holo-vm
# built below), so it does NOT consume mesh's engine types. This restores the pre-2568f997e green
# build cleanly — without the blanket `|| true` mask 2568f997e removed. (2026-06-07 studio fix.)
RUN cd packages/mesh && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck --noImplicitAny false
# (llm-provider moved earlier — see note above, must build before framework)
RUN cd packages/marketplace-agentkit && pnpm exec tsup
# holo-vm MUST build before security-sandbox (which imports @holoscript/holo-vm) and before
# studio's next build (engine/src walk imports it). holo-vm imports the no-dts engine, so its
# dts emit needs --noImplicitAny false (engine resolves as `any`, like mesh). Only dep is core.
RUN cd packages/holo-vm && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck --noImplicitAny false
RUN cd packages/security-sandbox && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# secrets-broker: studio/src/lib/capability-session.ts imports
# '@holoscript/secrets-broker' (used by api/connectors/disconnect/route.ts), a
# workspace:* dep. Without its dist on disk, Studio's `next build` (step 32)
# fails with "Module not found: Can't resolve '@holoscript/secrets-broker'".
# Zero workspace deps, so it builds standalone here among the leaf packages.
RUN cd packages/secrets-broker && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# holoembed + holo-vm: Studio's `next build` type-check walks engine SOURCE
# (tsconfig alias "@holoscript/engine": ["../engine/src"]). engine/src imports
# both — ConjectureEngine.ts -> @holoscript/holoembed, and engine/src/index.ts +
# vm/index.ts -> @holoscript/holo-vm. Without their dist on disk the type-check
# fails "Cannot find module '@holoscript/holoembed'". Deps already built above
# (holoembed: config + snn-webgpu; holo-vm: core), so they slot in here.
# holoembed: tsup's rollup-plugin-dts fails (snn-webgpu external + GPU types cause
# it to error). Use tsc --emitDeclarationOnly directly (pattern B).
# tsconfig.json has declaration:true + outDir:dist so this produces dist/index.d.ts.
RUN cd packages/holoembed && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly
# r3f-renderer's declarations are hand-crafted via generate-types.mjs (like core),
# NOT emitted by `tsup --dts-only`. Using the generic --dts-only pattern silently
# dropped dist/hooks/useWebcamGaze.d.ts, so studio's typecheck of the subpath import
# `@holoscript/r3f-renderer/hooks/useWebcamGaze` failed (implicit any). Run the real
# type generator so all subpath .d.ts files (incl. ./hooks/useWebcamGaze) are present.
RUN cd packages/r3f-renderer && pnpm exec tsup --no-dts && node scripts/generate-types.mjs
RUN cd packages/studio-plugin-sdk && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/ui && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/connector-core && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
RUN cd packages/connector-appstore && pnpm exec tsup
RUN cd packages/connector-github && pnpm exec tsup
RUN cd packages/connector-railway && pnpm exec tsup
RUN cd packages/connector-upstash && pnpm exec tsup
RUN cd packages/connector-vscode && pnpm exec tsup --no-dts && pnpm exec tsc --emitDeclarationOnly --outDir dist --skipLibCheck
# xr-embodiment (D.094 agent-generic embodiment): studio's /playground + immersive
# viewers import '@holoscript/xr-embodiment' and '@holoscript/xr-embodiment/react'.
# Its exports point at dist/*, dist is gitignored, and it has NO @holoscript/* deps
# (only externalized three/react/r3f peers) — so it builds standalone here. Missing
# this step is what bricked EVERY studio deploy from fa9878879 onward (2026-06-14):
# "Module not found: Can't resolve '@holoscript/xr-embodiment'". `tsup` emits the
# JS + dts for index/three/react in one pass (verified clean ~8s).
RUN cd packages/xr-embodiment && pnpm exec tsup
# Ensure webpack stubs exist (Docker cache can miss them)
RUN test -f packages/studio/src/lib/empty-module.js || \
(mkdir -p packages/studio/src/lib && echo "module.exports = function(){};" > packages/studio/src/lib/empty-module.js)
# Build Studio — standalone output is enabled on Linux
ENV NEXT_TELEMETRY_DISABLED=1
ENV SHARP_IGNORE_GLOBAL_LIBVIPS=1
# Use `pnpm run build` (not `pnpm next build`) so the `--webpack` flag from
# package.json's "build" script is applied. Without it, Next.js 16 defaults
# to Turbopack which can't read the webpack-config in next.config.js, and
# the build dies with: "ERROR: This build is using Turbopack, with a
# `webpack` config and no `turbopack` config. ... Build error occurred ...
# Error: Call retries were exceeded ... type: 'WorkerError'". Verified
# 2026-04-25 against Railway deployment b69f2efe (5 consecutive failures).
# Heap bump: `next build` compiles fine but OOMs in the post-compile phase (page-data
# collection / static gen) on the default ~2GB V8 heap — "FATAL ERROR: ... heap out of
# memory", build worker SIGABRT. Not a container-RAM issue (builders have >8GB); it's the
# V8 cap. NODE_OPTIONS is inherited by next's build workers. (2026-06-07 studio fix.)
RUN --mount=type=cache,id=s/55a18466-6702-4497-ad22-5856f4f196f3-/app/packages/studio/.next/cache-v2,target=/app/packages/studio/.next/cache cd packages/studio && NODE_OPTIONS=--max-old-space-size=8192 pnpm run build
# ─── Production runner ───────────────────────────────────────────────────────
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
# Ensure NextJS standalone server binds to 0.0.0.0
ENV HOSTNAME="0.0.0.0"
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
# Copy standalone output
COPY --from=builder --chown=nextjs:nodejs /app/packages/studio/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/packages/studio/.next/static ./packages/studio/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/packages/studio/public ./packages/studio/public
# Native scene library: pages like /playground/pipeline read .holo/.hs/.hsplus
# scenes from examples/ at runtime via loadHoloExample. Next standalone tracing
# only bundles deps IMPORTED by app code (the same reason drizzle/ and
# db-migrate.cjs are copied explicitly below), so copy the library from the
# full-repo `manifests` stage — the builder stage only carries packages/. Runtime
# cwd is /app (CMD runs node packages/studio/server.js), so the loader's
# process.cwd()=/app resolves /app/examples.
COPY --from=manifests --chown=nextjs:nodejs /app/examples ./examples
# Migrations + runner — copied alongside the standalone output so the runtime
# can self-heal schema on cold start. The pre-CMD step runs
# scripts/db-migrate.cjs, which is idempotent against drizzle's
# __drizzle_migrations ledger so reruns are no-ops. Without this step the
# Studio container boots against whatever schema state Postgres happens to
# be in — which already bit us once on 2026-04-29 (the 35-table push had to
# be applied by hand from a workstation against the prod proxy).
COPY --from=builder --chown=nextjs:nodejs /app/packages/studio/drizzle ./packages/studio/drizzle
COPY --from=builder --chown=nextjs:nodejs /app/packages/studio/scripts/db-migrate.cjs ./packages/studio/scripts/db-migrate.cjs
# Next.js standalone output traces only deps imported by the app code.
# db-migrate.cjs needs pg + drizzle-orm/node-postgres which are NOT imported
# by any page/API route, so they're missing from the standalone bundle.
# Install them explicitly in the runner image so migrations run on cold start.
# Hide package.json during install because the standalone output's package.json
# contains pnpm workspace: protocol URLs that npm does not understand.
# Install in an ISOLATED clean dir and copy in — NOT `npm install` against the
# standalone's node_modules. Since 2026-06-14 the standalone bundle traces workspace
# packages (e.g. @holoscript/xr-embodiment) into node_modules, and npm's arborist
# chokes reconciling them: "Cannot destructure property 'package' of 'node.target' as
# it is null" (exit 1, every runner build). A clean-dir install has no workspace
# graph to choke on; pg + drizzle-orm are pure-JS (no native bindings) so a copy is safe.
RUN mkdir -p /tmp/rt && cd /tmp/rt && npm init -y >/dev/null 2>&1 && \
npm install pg drizzle-orm --omit=dev --no-optional --no-audit --no-fund && \
cp -R node_modules/. /app/node_modules/ && chown -R nextjs:nodejs /app/node_modules
USER nextjs
EXPOSE 3000
CMD ["dumb-init", "sh", "-c", "node packages/studio/scripts/db-migrate.cjs && node packages/studio/server.js"]