Skip to content

fix(adapters): copy vendor/ into the PHP asset stage so package CSS resolves - #467

Open
sudanese wants to merge 1 commit into
oblien:mainfrom
sudanese:fix/php-asset-stage-vendor
Open

fix(adapters): copy vendor/ into the PHP asset stage so package CSS resolves#467
sudanese wants to merge 1 commit into
oblien:mainfrom
sudanese:fix/php-asset-stage-vendor

Conversation

@sudanese

@sudanese sudanese commented Aug 6, 2026

Copy link
Copy Markdown

Summary

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 —
therefore dies at npm run build with
Can't resolve ../../vendor/livewire/flux/dist/flux.css. Copy vendor/ from the
builder stage into the asset stage before the asset build runs.

Motivation

generatePhpDockerfile (packages/adapters/src/runtime/docker-build-plan.ts:199
on main) emits three stages: a builder (Composer) stage, an optional node:*
asset stage, and the FrankenPHP runtime. The asset stage is built at :219-227:

FROM node:22-bookworm-slim AS assets
WORKDIR /workspace
COPY . /workspace
WORKDIR /workspace
RUN … npm i --force && npm run build …

COPY . /workspace brings the repo, but vendor/ is git-ignored, so it is never
in the build context and never lands in this stage. That only breaks the asset
build, because of how a modern Laravel front-end resolves CSS:

  • resources/css/app.css @imports package stylesheets straight out of vendor,
    e.g. @import '../../vendor/livewire/flux/dist/flux.css';
  • Tailwind 4 @source '../../vendor/...' scans vendor for class usage.

Both are resolved by npm run build. The vendor/ tree exists only in the
builder stage (that is where composer install ran), so the compile fails to
resolve the import and the whole deploy fails — for every Flux project and any
Filament project with a custom theme (both @import package CSS through the
app's Vite build), which is exactly the stack Openship's PHP recipe supports end
to end.

Fix: pull vendor/ from the builder into the asset stage before the build:

FROM node:22-bookworm-slim AS assets
WORKDIR /workspace
COPY . /workspace
WORKDIR /workspace
COPY --from=builder /workspace/vendor /workspace/vendor   <-- added
RUN … npm run build …

It reuses sourceDir (the same variable the builder WORKDIR and both runtime
copies already use), so a monorepo sub-app with rootDirectory set copies
/workspace/<sub>/vendor, not a hardcoded path. It is guarded on the install
step: vendor/ exists in the builder only when composer install ran, so a PHP
project with an asset build but no install command is left exactly as it was. The
runtime stage is untouched — it already copies the compiled docroot from the
asset stage.

Related issue

None — bug fix (per CONTRIBUTING, bug fixes go straight to a PR).

Changes

packages/adapters

  • src/runtime/docker-build-plan.ts — in generatePhpDockerfile, add
    COPY --from=builder ${sourceDir}/vendor ${sourceDir}/vendor to the asset
    stage before the asset-build RUN line, guarded on installLine.
  • src/runtime/docker-build-plan.test.ts — one regression test in the existing
    PHP with a JS asset pipeline block: the asset stage copies vendor from the
    builder, and does so before npm run build.

Nothing else is touched — no reformatting of lines I did not change.

Verification

RED first — the new test against the unfixed source (fix reverted, test kept):

$ bun run --cwd packages/adapters test docker-build-plan
   × copies the installed vendor/ into the asset stage before the build so package CSS resolves
  Tests  1 failed | 20 passed (21)

GREEN — fix applied:

$ bun run --cwd packages/adapters test docker-build-plan
        Tests  21 passed (21)

$ bun run --cwd packages/adapters test        # full adapters suite
        Tests  1008 passed (1008)

Typecheck:

$ bun run --cwd packages/adapters lint   # tsc --noEmit
(exit 0)

I verified with the scoped packages/adapters runner rather than root
bun run test: root currently fails on main itself — #462 added
openship@^0.5.0 to apps/cli's deps, a turbo self-dependency unrelated to this
change. bun format is intentionally not run repo-wide: both touched files
carry pre-existing Prettier drift on lines I didn't change (confirmed on pristine
main), while the lines I added are Prettier-clean — so the diff stays scoped
per the contributing guidelines.

Checklist

  • One change per PR — one bug, or one agreed feature, with nothing unrelated bundled in
  • The diff is scoped — no reformatting or lint fixes on lines I wasn't otherwise changing
  • A test fails without this change and passes with it (or I explained above why there isn't one)
  • bun run test, bun run --cwd <workspace> lint, and bun format all pass locally
  • I understand every line of this diff and can explain it in review

Heads-up on CI: the workflow appears to fail at startup for all PRs since ~Aug 5, so this may show a red check unrelated to the change. Separately, root bun run test currently fails on main#462 added openship@^0.5.0 to apps/cli's deps (a turbo self-dependency) — so I verified with the scoped bun run --cwd packages/adapters test (1008 passed) + bun run --cwd packages/adapters lint (clean).

…esolves

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant