From a01ae40b36ceb4569a157478abeac523e8bc08d9 Mon Sep 17 00:00:00 2001 From: Samer Ajlawi Date: Thu, 6 Aug 2026 02:25:36 +0300 Subject: [PATCH] fix(adapters): copy vendor/ into the PHP asset stage so package CSS resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated PHP Dockerfile compiles front-end assets in a separate Node stage that never receives the Composer vendor/ tree. Any Laravel app whose CSS pulls from a Composer package — Flux, Filament, or anything that ships a stylesheet — dies at `npm run build` with `Can't resolve ../../vendor/livewire/flux/dist/flux.css`. generatePhpDockerfile emits a `builder` (Composer) stage, an optional node:* asset stage, and the FrankenPHP runtime. The asset stage's `COPY . /workspace` brings the repo but not vendor/ (git-ignored, so never in the build context). resources/css/app.css @imports package CSS straight out of vendor/, and Tailwind 4 @source-scans it — both resolved by npm run build, which then fails. Copy vendor/ from the builder into the asset stage before the build. Reuses sourceDir so a rootDirectory monorepo sub-app copies the right path, and is guarded on installLine (vendor/ exists only when composer install ran). --- .../adapters/src/runtime/docker-build-plan.test.ts | 14 ++++++++++++++ packages/adapters/src/runtime/docker-build-plan.ts | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/adapters/src/runtime/docker-build-plan.test.ts b/packages/adapters/src/runtime/docker-build-plan.test.ts index f775836cb..38dd663f9 100644 --- a/packages/adapters/src/runtime/docker-build-plan.test.ts +++ b/packages/adapters/src/runtime/docker-build-plan.test.ts @@ -113,6 +113,20 @@ describe("generateDockerfile — PHP with a JS asset pipeline", () => { expect(builderStage).not.toContain("npm run build"); }); + it("copies the installed vendor/ into the asset stage before the build so package CSS resolves", () => { + // A Laravel + Flux/Filament app @imports and Tailwind-@sources CSS from + // vendor/, so the asset build needs the Composer tree that only the builder + // has — the asset stage's `COPY . /workspace` doesn't bring git-ignored + // vendor/. Without this the build fails: Can't resolve ../../vendor/…/flux.css. + const assetsStage = df.slice(df.indexOf("AS assets"), df.indexOf("AS runtime")); + expect(assetsStage).toContain("COPY --from=builder /workspace/vendor /workspace/vendor"); + // vendor/ has to land BEFORE the asset build runs, not after it. + const vendorIdx = assetsStage.indexOf("COPY --from=builder /workspace/vendor"); + const buildIdx = assetsStage.indexOf("npm run build"); + expect(vendorIdx).toBeGreaterThan(-1); + expect(vendorIdx).toBeLessThan(buildIdx); + }); + it("preludes corepack for a non-npm package manager in the asset stage", () => { const pnpm = generateDockerfile(phpConfig({ buildCommand: "pnpm install && pnpm build" })); // The project PM is `composer`, so the prelude has to come from the command. diff --git a/packages/adapters/src/runtime/docker-build-plan.ts b/packages/adapters/src/runtime/docker-build-plan.ts index 46de2f1f9..680ceb989 100644 --- a/packages/adapters/src/runtime/docker-build-plan.ts +++ b/packages/adapters/src/runtime/docker-build-plan.ts @@ -222,8 +222,16 @@ function generatePhpDockerfile(config: BuildConfig): string { `WORKDIR /workspace`, `COPY . /workspace`, `WORKDIR ${sourceDir}`, - assetBuildLine, ); + // Flux/Filament (or any Composer package shipping its own CSS) is @import'd + // and Tailwind-@source-scanned from vendor/, so the asset build needs the + // Composer tree the builder stage installed — this stage's `COPY . /workspace` + // only brings the (git-ignored) source, not vendor/. Guarded on installLine: + // vendor/ only exists in the builder when composer install actually ran. + if (installLine) { + lines.push(`COPY --from=builder ${sourceDir}/vendor ${sourceDir}/vendor`); + } + lines.push(assetBuildLine); } const docroot = normalizeRelativePath(config.outputDirectory) || "public";