Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Module Installer Laravel Package

saucebase/module-installer

A Laravel Composer installer for Saucebase modules that simplifies registering, installing, and organizing modular packages in your application. It streamlines module discovery and setup so you can add features without manual configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a Composer plugin, so there is nothing to register in config/app.php and no artisan command to run. You install it into the root application once, and from then on any dependency whose composer.json declares "type": "saucebase-module" is placed in modules/<name> instead of vendor/.

Minimal setup:

composer require saucebase/module-installer

Composer 2.2+ will prompt to trust the plugin; accept it, or commit this to the root composer.json:

"config": {
    "allow-plugins": {
        "saucebase/module-installer": true
    }
}

Then pull in a module:

composer require saucebase/blog-module
ls modules/            # → modules/blog-module (directory name is lowercased)
composer show saucebase/blog-module

Where to look first:

  • src/Plugin.php — the PluginInterface implementation; this is where the installer is registered on PluginEvents::INIT and where any configurable base directory is read from.
  • src/Installer.php — supports() (the exact type string saucebase-module) and getInstallPath() (the lowercase modules/<name> logic). This is the file that answers "why did my package land there?".
  • src/Exceptions/ — the failure modes the plugin deliberately handles, usually around malformed module metadata or missing vendor/name prefixes.
  • The repository README's config table for the base-directory option, if you want modules somewhere other than modules/.

First real use case: add one module to a Sauce Base app, confirm it appears under modules/, and confirm its service provider is discovered by the host app's module loader — not by Composer's autoloader alone.

Implementation Patterns

Standard lifecycle. Treat modules exactly like normal dependencies: composer require to add, composer update saucebase/blog-module to bump, composer remove saucebase/blog-module to delete (see the gotcha about deletion below). Never hand-edit vendor/ and never hand-copy a module into modules/ — you lose the lock-file linkage and the next composer install will not reconcile it.

Authoring a module. In the module's own composer.json:

{
    "name": "saucebase/blog-module",
    "type": "saucebase-module",
    "autoload": { "psr-4": { "Saucebase\\BlogModule\\": "src/" } }
}

Because LibraryInstaller returns a real install path, Composer still registers the module's autoload rules relative to modules/blog-module, so PSR-4 classes resolve normally. The host app's module loader handles service providers, routes, migrations, and config publishing on top of that.

Distribution and versioning. A custom installer has zero influence on version resolution. Modules can come from Packagist, Private Packagist, a Satis repo, a VCS repository, or a path repository. The installer only decides where the resolved package is written.

Local module development. The clean pattern is to develop the module in a separate repository checked out outside modules/ (e.g. packages/blog-module), then point at it:

"repositories": [
    { "type": "path", "url": "packages/blog-module", "options": { "symlink": true } }
]

Avoid using modules/blog-module itself as the path-repo source — see the gotcha below.

Production and CI. Keep the plugin in require, not require-dev, and commit composer.lock plus the allow-plugins block. Then composer install --no-dev --optimize-autoloader in deploy installs the plugin and the modules. If modules/ is gitignored (recommended), the deploy step is the only thing that produces it, so this ordering matters.

Custom base directory. If you relocate modules (e.g. to app/Modules), change the installer's base-dir option and the host app's module discovery path in the same commit. The installer only controls the filesystem location; it does not tell the framework where to look.

Post-install integration. If modules need a one-time setup (publish config, run migrations, generate a manifest), hook it from the host app rather than forking the plugin — a composer.json script on post-autoload-dump, or a small artisan command invoked from deploy. The plugin ships no such hook by default.

Gotchas and Tips

  • allow-plugins is the number-one cause of "it went to vendor".* If the plugin is not trusted, Composer silently skips it, and a saucebase-module package then has no installer. Symptom: modules/ is empty and everything sits in vendor/. Fix config.allow-plugins and re-run composer install.

  • Type string must match exactly. "type": "saucebase-module" — singular, hyphenated, lowercase. supports() is a strict comparison; a typo like saucebase-modules or saucebase_module falls through to the default library installer and lands in vendor/ with no error.

  • Do not develop a module in modules/<name> via a path repository. The installer's target path and the package source path become the same directory, which produces confusing symlink/overwrite behaviour on install and can destroy your working tree on composer remove. Keep the dev source outside modules/.

  • composer remove deletes the directory. LibraryInstaller::uninstall() removes the install path recursively. Because modules live outside vendor/ this is easy to forget — any local edits, .env tweaks, or uploaded assets inside modules/blog-module are gone. Keep modules/ in .gitignore and treat it as build output.

  • Lowercasing can break case-sensitive assumptions. The directory is the lowercased package name. On Linux CI, a host-app path built as modules/Blog or a hardcoded ucfirst($name) will not resolve, even though it works on a case-insensitive macOS filesystem. Derive paths from the package name, not the namespace.

  • --no-dev and --no-plugins are both dangerous here. --no-dev drops the plugin if you mistakenly required it as a dev dependency; --no-plugins bypasses the installer entirely. Both leave you with modules either missing or in the wrong place, and neither fails loudly at the module level.

  • Debugging install paths. Use composer install -vvv to see the installer being selected, and COMPOSER_DEBUG_EVENTS=1 composer install -vvv to trace plugin event subscriptions (notably PluginEvents::INIT, where the installer is registered). composer diagnose catches allow-plugins problems. composer show <module> confirms the installed version but not the path — verify the path from the filesystem.

  • A stale lock can mask a broken setup. If a composer require fails midway (rejected plugin, private repo auth), the lock file may still reference a module that is not on disk. composer install then either fails or leaves an inconsistent modules/ tree. composer update --lock or deleting the entry and re-requiring resolves it.

  • Changing the base directory does not migrate existing modules. composer.lock records versions, not install paths. After changing the base dir, delete the old modules/ tree and run composer install to re-materialise packages in the new location, then update host-app discovery config.

  • Extension point if you need more. The plugin is small and MIT-licensed: Plugin::getSubscribedEvents() is the place to add hooks (e.g. PackageEvents::POST_PACKAGE_INSTALL for per-module setup), and Installer::getInstallPath() for a different naming scheme. Forking is cheap — but run composer test and ./vendor/bin/pint --test from the plugin repo before publishing the fork, and pin your fork in the root repositories block.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels