A small PHP development tool for removing configurable development-only metadata from composer.json files when releasing
packages for distribution.
A package's composer.json often contains metadata that is useful while developing and maintaining the package, but is
not needed by downstream consumers.
For example, development dependencies, development autoloading, and Composer scripts can make up a significant part of a project's development manifest.
composer-peel lets you explicitly define which Composer sections should be removed from a release manifest.
The goal is simple:
Keep the release package manifest i.e.
composer.jsonfocused on what consumers need and leave development metadata behind.
The sections removed by composer-peel are configurable. These are removed from the package's published composer.json;
they are not removed from the source repository's development configuration permanently. Runtime dependencies and package
autoloading remain untouched.
The default configuration includes the following sections:
| Section | Purpose |
|---|---|
require-dev |
Development-only dependencies |
autoload-dev |
Development-only autoloading |
scripts |
Composer scripts used during development and CI |
scripts-descriptions |
Composer scripts descriptions |
scripts-aliases |
Composer scripts aliases |
Install composer-peel as a development dependency:
composer require --dev stolt/composer-peelPeel the current composer.json:
composer-peel peelPreview the changes without modifying the manifest:
composer-peel peel --dry-runUse a custom configuration:
composer-peel peel --config=.composer-peel.phpA backup can also be created before the manifest is modified:
composer-peel peel --backup-file=.composer-unpeeled.jsonThe init command is useful when you want to make the peeling policy explicit and version-controlled
in your repository. It generates a configuration file from the current internal defaults, which you can then customise.
composer-peel initBy default, the init command will not overwrite an existing configuration file. If you want to overwrite it, use the
--overwrite option:
composer-peel init --overwriteUse --dry-run to inspect what composer-peel would remove without modifying composer.json.
composer-peel peel --dry-runThe dry run shows:
- the manifest being processed,
- the configuration being used,
- the sections that would be removed,
- the original manifest size,
- the projected manifest size,
- the estimated size reduction.
With the default configuration:
$ composer-peel --dry-run
Manifest: composer.json
Configuration: internal defaults
Sections to be removed:
- require-dev
- autoload-dev
- scripts
- scripts-descriptions
- scripts-aliases
Original size: 2,480 bytes
Projected size: 1,120 bytes
Estimated reduction: 1,360 bytes (54.8%)
Dry run completed. No files were modified.
The values above are illustrative. The actual result depends on the contents of your composer.json and the configured
peeling rules.
If you also want to see the exact structural changes, you can use the --diff option:
composer-peel peel --dry-run --diffIf you need the dry-run output in a machine-readable format, you can use the --format=json option:
composer-peel peel --dry-run --format=jsonWhen both --format=json and --diff are used, the resulting JSON object will contain an additional diff key with
the unified diff string.
{
"manifest": "composer.json",
"configuration": "internal defaults",
"removed_sections": [
"require-dev",
"autoload-dev",
"scripts",
"scripts-descriptions",
"scripts-aliases"
],
"original_size_bytes": 2480,
"projected_size_bytes": 1120,
"reduction_bytes": 1360,
"reduction_percentage": 54.8
}Use the validate command to verify a peeled composer.json before releasing it:
composer-peel validateThe following checks are performed:
composer.jsoncontains a valid JSON object,- the backup file exists when backups are enabled,
- the backup file contains a valid JSON object,
- none of the configured sections is still present in
composer.json, - the required runtime sections
nameandrequirestill exist, - all other sections are unchanged compared to the backup,
composer validate --no-check-lockreports no errors forcomposer.jsonand the backup file.
$ composer-peel validate
[PASS] composer.json contains valid JSON
[PASS] Backup .composer-unpeeled.json exists
[PASS] Backup .composer-unpeeled.json contains valid JSON
[PASS] Configured sections are absent
[PASS] Required runtime sections exist
[PASS] Runtime sections match the backup
[PASS] composer validate reports no errors for composer.json
[PASS] composer validate reports no errors for .composer-unpeeled.json
Peeled composer.json validated successfully.
The command exits with a non-zero status code if any check fails, which makes it usable in CI. Checks depending on the backup are skipped when backups are disabled via the configuration.
The composer validate checks require the composer binary to be available on the PATH. Warnings do not fail the
validation, but errors, including publish errors like a missing description, do. The lock file is not checked,
because it is expected to still contain the peeled development dependencies. To skip these checks, use the
--skip-composer-validate option:
composer-peel validate --skip-composer-validateA custom backup file or configuration file can be specified as well:
composer-peel validate --backup-file=my-backup.json --config=.composer-peel.phpIf a backup file was created during the peel process, you can restore composer.json to its original state using
the rollback command.
composer-peel rollbackBy default, the rollback command will delete the backup file after successfully restoring the manifest. If you want
to keep the backup file, use the --keep-backup option:
composer-peel rollback --keep-backupTo commit the restored composer.json to Git right away, use the --commit option. The commit uses the configured
after_tag commit message:
composer-peel rollback --commitTo overwrite the default or configured commit message, use the --commit-message option. It requires the --commit
option:
composer-peel rollback --commit --commit-message="chore: start next development cycle"The --commit option can be combined with --keep-backup. If the commit fails, e.g. because the current directory is
not a Git repository, the command exits with an error and the backup file is kept.
Before restoring, the rollback command verifies that composer.json still is the peeled version of the backup, i.e.
the backup without the configured sections. If composer.json has been modified since it was peeled, the rollback is
aborted, as restoring the backup would discard these changes. To restore the backup anyway, use the --force option:
composer-peel rollback --forceIf composer.json already matches the backup, it is left untouched.
You can also specify a custom backup file or configuration file during rollback:
composer-peel rollback --backup-file=my-backup.json --config=.composer-peel.phpcomposer-peel supports an optional PHP configuration file named .composer-peel.php in the project root.
A configuration can define which Composer sections are peeled, as well as release backup and Git commit behaviour:
<?php
declare(strict_types=1);
return [
'peel' => [
'sections' => [
'require-dev',
'autoload-dev',
'scripts',
'scripts-descriptions',
'scripts-aliases',
],
],
'release' => [
'backup' => [
'enabled' => true,
'path' => '.composer-unpeeled.json',
],
],
'git' => [
'commit_messages' => [
'before_tag' => 'chore(dist): prepare Composer manifest for release',
'after_tag' => 'chore: restore development Composer manifest',
],
],
];The peel.sections option defines the top-level Composer sections that should be removed:
'peel' => [
'sections' => [
'require-dev',
'autoload-dev',
'scripts',
'scripts-descriptions',
'scripts-aliases',
],
],Only explicitly configured sections are peeled.
This makes the behaviour predictable and allows each package to decide which metadata belongs exclusively to its development workflow.
Note
composer-peel only permits stripping development-oriented metadata. Attempting to configure protected sections
such as require or autoload will cause the operation to fail with an error.
The release backup stores the original composer.json before it is peeled:
'release' => [
'backup' => [
'enabled' => true,
'path' => '.composer-unpeeled.json',
],
],The backup provides a recovery point if the release process fails before the original manifest is restored.
Make sure the backup file is not unintentionally included in the release.
The automated release workflow uses configurable commit messages:
'git' => [
'commit_messages' => [
'before_tag' => 'chore(dist): prepare Composer manifest for release',
'after_tag' => 'chore: restore development Composer manifest',
],
],before_tag is used for the commit containing the peeled manifest.
after_tag is used by rollback --commit for the commit restoring the original development manifest.
Both messages can be overwritten per invocation via the --commit-message option of the release and
rollback --commit commands.
Use the release command to commit and tag an already peeled composer.json.
The release command does not peel the manifest itself. Run peel, and optionally validate, first:
composer-peel peel
composer-peel validate
composer-peel release v1.0.0
composer-peel rollback --commitThe workflow is:
- Run
peelto remove the configured development-only sections fromcomposer.json. - Optionally run
validateto inspect the peeled manifest independently. - Run
releaseto commit the peeled manifest and create the Git tag. - Run
rollback --committo restore the original developmentcomposer.jsonand commit the changes.
The release command requires a clean working tree. The only allowed changes are the peeled composer.json and the
backup file created by peel.
Before committing, the release command also verifies that composer.json is exactly the backup file without the
configured sections. The release is aborted without creating a commit or tag if the backup file is missing, if
composer.json has not been peeled, or if it has been modified after peeling. In the latter case, run rollback and
peel again. For the full set of checks, including composer validate, use the
validate command.
The commit messages used for the release workflow can be configured through .composer-peel.php. To overwrite the
default or configured commit message for a single release, use the --commit-message option:
composer-peel release v1.0.0 --commit-message="chore: release v1.0.0"The resulting history looks like this:
Development commit
│
▼
Peeled manifest commit
│
└── Release tag (v1.0.0)
│
▼
Restored development manifest commit
The release tag therefore points to the peeled manifest, while the development branch continues with the original manifest.
Important
The release tag points to a commit containing a different composer.json than the development branch.
The automated release workflow only works when the peeled-manifest commit does not need to pass the project's normal development CI checks.
After peeling, development dependencies, development autoloading, and Composer scripts may no longer be available. CI jobs that depend on them can therefore fail.
If your release process requires CI validation of the tagged commit, consider using composer-peel as a separate
distribution/build step instead of tagging the peeled manifest directly.
composer-peel modifies only composer.json. It does not modify composer.lock.
composer-peel includes a repository-local AI skill at .agents/skills/composer-peel/SKILL.md.
The skill teaches compatible coding agents how to inspect the configuration, preview changes, peel the configured sections, and safely perform the release workflow.
This CLI and its library are licensed under the MIT license. Please see LICENSE.md for more details.
All noteworthy changes are documented in the CHANGELOG.md.
If you're considering contributing to this project, have a look at this repository's CONTRIBUTING.md for more advice.
