jbsnewmedia/asset-composer-bundle
A PHP bundle for composing and managing frontend assets, helping you organize, combine, and serve CSS and JavaScript files from a central configuration. It streamlines asset handling in Symfony-style applications, reducing repetitive markup and keeping templates cleaner.
This release moves the baseline to PHP 8.4 and adds Symfony 8 support, so before anything else verify your runtime:
php -v # must be >= 8.4 to install 1.2.0
composer require jbsnewmedia/asset-composer-bundle:^1.2
If you are still on PHP 8.1–8.3 you either stay on the 1.1.x line or upgrade the runtime first — the constraint is hard, Composer will refuse the install. On Laravel projects this is usually the deciding factor: an app still on PHP 8.2/8.3 cannot take this release until the whole stack (CI image, Forge/Ploiid/Vapor runtime, local Docker) is bumped together.
First look at:
config/bundles.php in Symfony, or your bundle provider list) and any package/config file that points the bundle at your vendor directory.First use case, in order of value:
vendor/acme/widget/Resources/public/... rendered through Twig with a cache-busted, correct URL, instead of copying it into your own public/ folder by hand.vendor/ via a Composer path repository. This release is the one that makes that workflow stop throwing containment exceptions, so it is the right thing to test first after upgrading.Composer-package assets as first-class template citizens. Once the bundle is registered and pointed at your vendor dir, templates reference package assets directly instead of duplicating them. Keep the package name in the path so intent is obvious to reviewers, and treat the vendor path as read-only — never edit assets there, because composer install will overwrite them.
Path-repository / monorepo development. With 1.2.0 the containment check runs through assertFileInsideProjectDir() after resolution, with separators and trailing slashes normalized. That means a composer.json entry like:
"repositories": [{ "type": "path", "url": "../packages/acme-widget" }]
resolves cleanly even though the real path leaves the project root via a symlink. The practical pattern is: develop the package in its own directory, symlink it in, edit live, and let the bundle serve the asset — no more "works only after composer install --no-symlink" dance.
Separate environments, separate expectations. The traversal guard rejects any path containing .. outright with BadRequestHttpException, before touching the filesystem. That is a deliberate design choice with a behavioural consequence: it is deterministic and cheap, but it is also indiscriminate. Build any asset path from a fixed package name plus a fixed relative segment, and let the bundle do the joining — do not pass user-influenced strings through it at all.
Integration tip: front-end build output. A common shape is a package that compiles SCSS/TS on its own and exposes the built file under its public directory. Let the package's CI publish the build artefact into the package, and let this bundle reference it. That keeps your application's public/build clean and makes the package independently versioned.
Upgrade sequencing for existing installs. Because PHP 8.4 is mandatory, do the runtime bump as its own deploy, verified in CI, before bumping this bundle. Then bump to ^1.2 and run the asset-facing test suite. If you use Symfony 8 anywhere, this is the release that unlocks it; 6.4 and 7.x continue to work unchanged.
^1.1 explicitly if you cannot move yet — an unpinned ^1.0 constraint will simply fail to resolve on older runtimes, which is noisy but not dangerous. The dangerous case is a CI matrix where one job still runs 8.3.... The new check rejects them before resolution, so a previously-tolerated path like foo/../bar/asset.css that happened to normalise to a valid file will now 400. Grep templates and config for .. segments before deploying; this is the most likely silent regression.assertFileInsideProjectDir() normalises separators and trailing slashes so path repositories resolve. If you see a containment BadRequestHttpException on a legitimately symlinked vendor dir, check that the symlink target itself is sane — the check is now symlink-aware, so a failure usually means a genuinely odd path (relative symlink loops, mixed-separator Windows-style paths in a Linux container, or a bind mount the process cannot realpath).BadRequestHttpException is a 400, not a 500. If traversal attempts are hitting your error tracker as server errors, they are likely being wrapped by a middleware or listener. Log them at warning level and consider alerting on the volume rather than the individual event — a burst is the interesting signal.assertFileInsideProjectDir()), which makes it the natural override or decoration point if you need different trust boundaries (e.g. allowing a read-only shared asset mount). Prefer decorating over forking, so you keep picking up the security fixes that this release series is clearly prioritising.How can I help you explore Laravel packages today?