diff --git a/.containerignore b/.containerignore index 1812091..70b617e 100644 --- a/.containerignore +++ b/.containerignore @@ -5,18 +5,8 @@ coverage data dist node_modules -out playwright-report storybook-static test-results .env .env.* -apps/*/.turbo -apps/*/build -apps/*/data -apps/*/dist -apps/*/playwright-report -apps/*/test-results -packages/*/.turbo -packages/*/dist -packages/*/storybook-static diff --git a/.gitignore b/.gitignore index eed0de1..b6326bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules/ -out/ .svelte-kit/ build/ dist/ diff --git a/README.md b/README.md index fee9443..572f9bc 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,10 @@ the checked-in `apps/web/drizzle/` directory so startup migrations can run. ### Internal Container -The checked-in `apps/web/Containerfile` runs -`turbo prune @dimensionlab/web --docker`, installs the pruned manifest set, and -builds the React client plus Bun server from the pruned workspace source. For the -Dimension Lab internal host, run it behind Caddy on a loopback port and mount -persistent state at `/data`: +The checked-in `apps/web/Containerfile` builds the React client and Bun server +from the workspace root into a runtime image. For the Dimension Lab internal +host, run it behind Caddy on a loopback port and mount persistent state at +`/data`: ```sh podman build -f apps/web/Containerfile -t localhost/dimensionlab-website:latest . diff --git a/apps/web/Containerfile b/apps/web/Containerfile index 9ec2baa..7f12f94 100644 --- a/apps/web/Containerfile +++ b/apps/web/Containerfile @@ -1,25 +1,19 @@ -FROM docker.io/oven/bun:1.3.14 AS base +FROM docker.io/oven/bun:1.3.14 AS deps WORKDIR /repo -ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/dashboard-model/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH - -FROM base AS pruner - -COPY . . -RUN bunx turbo prune @dimensionlab/web --docker - -FROM base AS deps - -COPY --from=pruner /repo/out/json/ ./ +ENV PATH=/repo/apps/web/node_modules/.bin:/repo/packages/ui/node_modules/.bin:/repo/node_modules/.bin:$PATH +COPY package.json bun.lock turbo.json tsconfig.base.json ./ +COPY apps/web/package.json apps/web/package.json +COPY packages/dashboard-model/package.json packages/dashboard-model/package.json +COPY packages/ui/package.json packages/ui/package.json RUN bun install --frozen-lockfile FROM deps AS build -COPY --from=pruner /repo/out/full/ ./ -COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./ -RUN bun run build +COPY . . +RUN bun install --frozen-lockfile && bun run build -FROM base AS runtime +FROM deps AS runtime WORKDIR /repo/apps/web ENV NODE_ENV=production diff --git a/apps/web/package.json b/apps/web/package.json index 790663c..2e9fef6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "bun src/server/dev.ts", - "build": "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build", + "build": "vite build && bun build src/server/index.ts --target bun --outdir build", "preview": "HOST=0.0.0.0 PORT=4173 bun build/index.js", "check": "tsc --noEmit", "test": "vitest run", diff --git a/apps/web/src/lib/workspace-boundary.test.ts b/apps/web/src/lib/workspace-boundary.test.ts index 9cde8e8..4f4200c 100644 --- a/apps/web/src/lib/workspace-boundary.test.ts +++ b/apps/web/src/lib/workspace-boundary.test.ts @@ -114,72 +114,35 @@ describe("workspace boundaries", () => { ); }); - test("builds the internal container from a turbo-pruned web workspace", () => { + test("stages web workspace dependency manifests before container install", () => { + const webPackage = JSON.parse( + readFileSync(join(root, "apps/web/package.json"), "utf8"), + ) as { dependencies?: Record }; + const workspacePackages = [ + "packages/ui/package.json", + "packages/dashboard-model/package.json", + ].map((manifestPath) => { + const packageJson = JSON.parse( + readFileSync(join(root, manifestPath), "utf8"), + ) as { name?: string }; + + return [packageJson.name, manifestPath] as const; + }); + const manifestsByPackageName = new Map(workspacePackages); const containerfile = readFileSync( join(root, "apps/web/Containerfile"), "utf8", ); - const pruneCommand = "turbo prune @dimensionlab/web --docker"; - const jsonCopy = "COPY --from=pruner /repo/out/json/ ./"; - const sourceCopy = "COPY --from=pruner /repo/out/full/ ./"; - const tsconfigCopy = - "COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./"; - const installCommand = "RUN bun install --frozen-lockfile"; - expect(containerfile).toContain("AS pruner"); - expect(containerfile).toContain(pruneCommand); - expect(containerfile).toContain(jsonCopy); - expect(containerfile).toContain(sourceCopy); - expect(containerfile).toContain(tsconfigCopy); - expect(containerfile).not.toContain( - "COPY packages/dashboard-model/package.json", - ); - expect(containerfile).not.toContain("COPY packages/ui/package.json"); + const workspaceDependencyManifests = Object.entries( + webPackage.dependencies ?? {}, + ) + .filter(([, version]) => version.startsWith("workspace:")) + .map(([packageName]) => manifestsByPackageName.get(packageName)); - const jsonCopyIndex = containerfile.indexOf(jsonCopy); - const installIndex = containerfile.indexOf(installCommand); - const sourceCopyIndex = containerfile.indexOf(sourceCopy); - const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy); - - expect(jsonCopyIndex).toBeGreaterThan(-1); - expect(installIndex).toBeGreaterThan(jsonCopyIndex); - expect(sourceCopyIndex).toBeGreaterThan(installIndex); - expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex); - expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan( - tsconfigCopyIndex, - ); - }); - - test("keeps local turbo prune output out of git and container contexts", () => { - const webPackage = JSON.parse( - readFileSync(join(root, "apps/web/package.json"), "utf8"), - ) as { scripts?: Record }; - const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); - const containerignore = readFileSync(join(root, ".containerignore"), "utf8"); - const containerIgnoreRules = new Set( - containerignore - .split(/\r?\n/) - .map((line) => line.trim()) - .filter(Boolean), - ); - - expect(gitignore).toContain("out/"); - expect(containerignore).toContain("out"); - expect([...containerIgnoreRules]).toEqual( - expect.arrayContaining([ - "apps/*/.turbo", - "apps/*/build", - "apps/*/data", - "apps/*/dist", - "apps/*/playwright-report", - "apps/*/test-results", - "packages/*/.turbo", - "packages/*/dist", - "packages/*/storybook-static", - ]), - ); - expect(webPackage.scripts?.build).toBe( - "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build", - ); + expect(workspaceDependencyManifests).not.toContain(undefined); + for (const manifestPath of workspaceDependencyManifests) { + expect(containerfile).toContain(`COPY ${manifestPath} ${manifestPath}`); + } }); });