Search by

saucebase / module-installer

saucebase

Custom Composer installer for Sauce Base modules (type: saucebase-module). Installs to modules/<name> (lowercase) with configurable base dir.

Package info

github.com/saucebase-dev/module-installer

Type:composer-plugin

pkg:composer/saucebase/module-installer

Statistics

Installs: 3 710

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 0

v3.2.0 2026-11-03 21:09 UTC

README

PHP Version Composer Tests License

A Composer plugin that installs Saucebase modules into the correct directory. It ships with saucebase-dev/saucebase, so every module your project requires is placed where the app can find and load it.

How It Works

  • Registers a Composer installer for a configurable module package type (defaults to saucebase-module and laravel-module).
  • Installs each module inside a configurable modules directory (modules/ by default, lowercase).
  • Derives the directory name from the package slug: saucebase/example-module → modules/example-module.
  • On composer update, merges the new version into your existing module directory by default, preserving local edits. Conflicts are staged in the git index for review.
  • Removes configurable directories (e.g. .github, .git) after each install or update.
  • Deploys frontend framework files (Vue / React) based on the project's saucebase.json selection.
  • Protects path-repository modules (symlinks or local .git clones) from being overwritten or deleted.
  • Skips folder deletion by default when a module is removed via composer remove — the directory stays on disk for manual review.

Requirements

  • PHP 8.4 or newer
  • Composer 2.x
  • A project based on saucebase-dev/saucebase (the core already requires this plugin)

Installation

saucebase-dev/saucebase already requires this package. When you install the core, Composer pulls in the plugin and activates it through the Saucebase\\ModuleInstaller\\Plugin class — no extra configuration is needed for a typical Saucebase project.

Need the installer for a different Composer project? Require it directly:

composer require saucebase/module-installer

Configuration Reference

All options live in the extra section of the root composer.json. Every key is optional.

Key Default Description
module-type saucebase-module and laravel-module Composer package type the installer handles. Setting it replaces both defaults with that one type.
module-dir modules Directory modules are installed into.
module-exclude-dirs [".github", ".git"] Directories removed from each module after install/update.
module-update-strategy merge merge preserves local edits; overwrite replaces entirely.
module-delete-on-remove true while backups are on Deletes the module folder on composer remove, after backing it up. false keeps it.
module-backups 3 Backups kept per module before an update or removal changes its folder. 0 turns them off.
module-allow-symlink false Set to true to allow a symlinked path repository outside the app.

Example (the Saucebase app configuration):

{
    "extra": {
        "module-type": "saucebase-module",
        "module-dir": "modules"
    }
}

Configuring the Module Type

The installer registers the saucebase-module and laravel-module package types by default. Override it in the root package extra section:

{
    "extra": {
        "module-type": "acme-module"
    }
}

Any module packages must set their composer.json type to the same value.

Configuring the Install Location

By default, modules are installed under modules/ at the project root. Change this with the module-dir key:

{
    "extra": {
        "module-dir": "custom-modules"
    }
}

With the configuration above, saucebase/example-module installs to custom-modules/example-module.

Configuring Excluded Directories

After each install or update, the installer removes directories that should not land in production. The default list is .github and .git. Override it with module-exclude-dirs:

{
    "extra": {
        "module-exclude-dirs": [".github", ".git", ".vscode", "tests"]
    }
}

Set it to [] to skip all removal. The configured list replaces the default entirely.

Configuring Update Behaviour

By default, when you run composer update, the installer merges the new package version into your existing module directory rather than replacing it.

Strategy What happens on composer update
merge (default) New files from the package are added. Your edited files are kept as-is. Conflicts between your edits and upstream changes are staged in the git index (conflict markers in the file, both sides visible in git diff).
overwrite The module directory is replaced entirely with the new package contents. All local edits are lost.

Keeping your customisations (default)

No configuration needed. Running composer update adds new files without touching files you have already edited.

Note: Because your local files take precedence, bug fixes or updates to files you have customised will not be applied automatically. To pick up an upstream change to a specific file, delete it and re-run composer update.

Merge conflicts

When both you and the upstream package changed the same file, the installer stages a 3-way conflict in the git index (stages 1/2/3). The working-tree file contains standard conflict markers and git status shows it as a conflict for normal resolution via git mergetool or your editor.

Full overwrite (CI / reproducible builds)

Set the strategy to overwrite to produce an exact copy of the package on every update:

{
    "extra": {
        "module-update-strategy": "overwrite"
    }
}

This is recommended for CI pipelines and staging environments where reproducibility matters more than preserving local changes.

Getting a completely fresh copy of a module

With the merge strategy (default), delete the module directory and run composer update:

rm -rf modules/example-module
composer update vendor/example-module

The installer performs a clean install into the now-empty path.

Removing a Module

composer remove backs the module folder up to storage/app/module-backups/ (see module-backups), then deletes it. A folder the installer doesn't own is never deleted: a symlink or a git checkout (.git folder or file) is refused, so a path repository's symlink or git folder stays. A path repository copied without its .git is backed up and deleted like any other. A folder another installed module still uses stays too.

With module-backups: 0 there is no backup, so the folder stays unless module-delete-on-remove is true. To always keep it:

{
    "extra": {
        "module-delete-on-remove": false
    }
}
- Keeping the module directory (module-delete-on-remove is off); delete it to finish removing the module: modules/example-module

Delete the folder yourself then: an app with Saucebase core refuses to boot with a module folder Composer doesn't autoload.

Safety

The installer only ever changes its own copy of a module in modules/. Before it deletes or rewrites anything there, it checks the folder is a real directory inside modules/, not a symlink and not a git checkout, and stops with an error otherwise.

Backups

Before an update or a removal changes a module's folder, the installer archives it, hidden files included, to storage/app/module-backups/<module>/<date>-<version>.tar.gz. Laravel's storage/app/.gitignore already keeps them out of git. The newest three are kept per module; set module-backups to change that, or 0 to turn them off. To restore one:

tar -xzf storage/app/module-backups/billing/20261101-120000-v2.1.0.tar.gz -C modules

Path repositories outside the app

A path repository is symlinked by default, so a module from a folder outside the app would be changed in that folder (its .git, .github and unused framework files removed). The installer refuses it and prints the fix. Copy the module instead:

{
    "repositories": [
        { "type": "path", "url": "../my-modules/*", "options": { "symlink": false } }
    ]
}

To keep the link anyway, set "module-allow-symlink": true. The installer then leaves the linked module as it is.

Frontend Framework Support

When a module ships JavaScript resources for multiple frameworks (Vue, React, or Svelte), the installer copies only the files for the active framework flat into resources/js/ and removes the other framework subdirectories.

The active framework is read from saucebase.json in the project root:

{
    "framework": "vue"
}

Valid values are vue and react. If saucebase.json is absent, unreadable, or has "contributor": true, no framework files are copied and all framework subdirectories are left in place.

Module packages should ship framework files under subdirectories matching the framework name:

resources/
  js/
    vue/
      Component.vue
    react/
      Component.tsx

After install with "framework": "vue" active, the result is:

resources/
  js/
    Component.vue   ← copied flat from vue/

Creating Sauce Base Modules

To ship a module that works with this installer:

  1. Set the package type in the module composer.json to whatever the application expects (e.g. saucebase-module).

  2. Organise module code inside the directory the installer will create (e.g. modules/your-module-name).

  3. Have consumers install the module through Composer:

    composer require vendor/your-module-name

    The plugin derives the install directory from the package slug. vendor/your-module-name installs to modules/your-module-name (or the configured module-dir).

Local Development

Clone the repository and install dependencies:

composer install

Useful scripts:

  • composer test — run the PHPUnit 12 suite (tests/).
  • composer lint — apply Laravel Pint formatting; composer lint:test checks it, as CI does.
  • composer validate — verify Composer metadata.

License

Licensed under the MIT License.