stolt / composer-peel
A small development tool to strip development-only metadata from composer.json when preparing PHP packages for distribution.
Requires
- php: >=8.2
- sebastian/diff: ^6.0 || ^7.0 || ^8.0 || ^9.0
- symfony/console: ^7.4 || ^8.1
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A small PHP development tool for removing configurable development-only metadata from composer.json files when
releasing packages for distribution.
Why?
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.
Keep the release package manifest focused on what consumers need and leave development metadata behind.
Removing require-dev from a release manifest does not imply lower package quality. Development dependencies describe
how a package is tested and maintained, not what consumers need to run it. The complete development setup remains
in the source repository; composer-peel simply keeps those development concerns out of the distributed package.
What gets peeled?
The sections removed by composer-peel are configurable. They are removed from the release composer.json; they are
not permanently removed from the source repository's development configuration.
The default configuration removes:
| Section | Purpose |
|---|---|
require-dev |
Development-only dependencies |
autoload-dev |
Development-only autoloading |
scripts |
Composer scripts used during development and CI |
scripts-descriptions |
Composer script descriptions |
scripts-aliases |
Composer script aliases |
Runtime dependencies and package autoloading remain untouched.
A real-world example
Even widely used packages can carry significant development metadata into their published composer.json.
For example, peeling the require-dev section from symfony/console v8.1.8 would reduce its manifest from
1,813 bytes to 1,170 bytes — a saving of 643 bytes (35.5%).
With more than 1.2 billion installations, that amounts to a theoretical cumulative saving of approximately 782 GB of Composer metadata.
The example illustrates the principle behind composer-peel: small savings in a package manifest can become
significant at scale.
Installation
Install composer-peel as a development dependency:
composer require --dev stolt/composer-peel
Usage
Peel
Remove the configured sections from the current composer.json:
composer-peel peel
Preview the changes without modifying the manifest:
composer-peel peel --dry-run
Use a custom configuration:
composer-peel peel --config=.composer-peel.php
Create a backup before modifying the manifest:
composer-peel peel --backup-file=.composer-unpeeled.json
Initialize configuration
The init command generates a .composer-peel.php file from the current internal defaults. This is useful when you
want the peeling policy to be explicit and version-controlled:
composer-peel init
An existing configuration is not overwritten by default:
composer-peel init --overwrite
Preview changes
The --dry-run option 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.
For example:
$ composer-peel 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 composer.json and the configured
peeling rules.
To see the exact structural changes:
composer-peel peel --dry-run --diff
For machine-readable output:
composer-peel peel --dry-run --format=json
When --format=json and --diff are combined, the JSON output contains an additional diff key with the unified diff.
Check status
Use status to get a read-only overview of the current Composer Peel state and determine whether the
repository is ready for the release workflow:
composer-peel status
The command inspects:
- the current Composer manifest state (peeled, unpeeled, or modified);
- whether a Composer backup exists;
- the optional Composer package version (separate from the application version);
- discovered application version sources and their consistency;
- the latest Git tag;
- the CHANGELOG version;
- the working tree state;
- overall release readiness.
Package version vs. application version
The status command distinguishes between two version concepts:
- Package version: the optional
versionfield incomposer.json. This is metadata for the Composer package and is not used as an application version source. - Application version: discovered from supported source files such as
bin/files,src/Console/Application.php,src/Application.php, andsrc/Server.php. This follows the same detection rules asversion-aligner.
composer.json is never used as an application version source.
Example output
For a healthy, release-ready state:
Composer Peel status
Composer manifest peeled
Backup available
Package version 1.4.0
Application versions
bin/composer-peel 1.4.0
src/Console/Application.php 1.4.0
Application version 1.4.0
Version consistency consistent
Latest Git tag v1.4.0
CHANGELOG.md 1.4.0
Working tree clean
Release state ready
For an inconsistent state:
Composer Peel status
Composer manifest peeled
Backup available
Application versions
bin/composer-peel 1.3.0
src/Console/Application.php 1.4.0
Application version 1.4.0
Version consistency inconsistent
Latest Git tag v1.3.0
CHANGELOG.md 1.4.0
Working tree clean
Release state not ready
Issues:
- bin/composer-peel contains version 1.3.0 but the other application version source contains 1.4.0
- Application version 1.4.0 does not match the latest Git tag v1.3.0
The command exits with 0 when the repository is release-ready, and with a non-zero exit code when issues are
detected. It is safe to run repeatedly and does not modify any files, Git state, commits, or tags.
A custom configuration file can be specified:
composer-peel status --config=.composer-peel.php
Validate the peeled manifest
Use validate to verify the peeled composer.json before releasing it:
composer-peel validate
The 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 with the backup;
composer validate --no-check-lockreports no errors forcomposer.jsonand the backup.
The command exits with a non-zero status code if a check fails, making it suitable for CI. Checks that depend on the backup are skipped when backups are disabled.
The Composer validation checks require the composer binary to be available on PATH. Warnings do not fail validation,
but errors — including publish errors such as a missing description — do.
The lock file is not checked because it is expected to retain the development dependencies removed from the release manifest.
To skip the Composer validation checks:
composer-peel validate --skip-composer-validate
A custom backup or configuration file can also be specified:
composer-peel validate --backup-file=my-backup.json --config=.composer-peel.php
Roll back changes
When peel has created a backup, rollback restores the original composer.json:
composer-peel rollback
By default, the backup file is deleted after a successful rollback. Keep it with:
composer-peel rollback --keep-backup
Restore and commit the development manifest immediately:
composer-peel rollback --commit
The commit uses the configured rollback commit message. Override it for a single invocation with:
composer-peel rollback --commit --commit-message="chore: start next development cycle"
--commit-message requires --commit.
The --commit option can be combined with --keep-backup. If committing fails, the command exits with an error and
keeps the backup so the rollback can be retried.
Before restoring, rollback verifies that composer.json is still the peeled version of the backup. If the manifest
has been modified since it was peeled, the rollback is aborted to avoid discarding those changes.
To restore the backup anyway:
composer-peel rollback --force
If composer.json already matches the backup, it is left untouched.
Use --dry-run to preview the rollback checks, the commit message, and whether the backup would be removed:
composer-peel rollback --dry-run composer-peel rollback --commit --dry-run
Custom backup and configuration files can also be supplied:
composer-peel rollback --backup-file=my-backup.json --config=.composer-peel.php
Configuration
composer-peel supports an optional PHP configuration file named .composer-peel.php in the project root.
The configuration can define:
- which Composer sections are peeled;
- the release backup;
- which files may be included in a release commit;
- release and rollback commit messages.
<?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', ], 'files' => [ 'CHANGELOG.md', 'bin/', ], 'commit_message' => 'chore: release version {{version}}', ], 'rollback' => [ 'commit_message' => 'chore: restore development Composer manifest', ], ];
Peel sections
peel.sections 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 behavior predictable and lets each package 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 causes the operation to fail.
Release backup
The release backup stores the original composer.json before it is peeled:
'release' => [ 'backup' => [ 'enabled' => true, 'path' => '.composer-unpeeled.json', ], ],
The backup provides the source for restoring the development manifest if the release process fails before restoration.
Make sure the backup file is not unintentionally included in the release.
Release files
By default, the release workflow allows the peeled composer.json, the backup, CHANGELOG.md, and modified files in
bin/ to be committed.
The default CHANGELOG.md and bin/ allowances can be replaced through release.files:
'release' => [ 'files' => [ 'CHANGELOG.md', 'bin/', ], ],
Directories must end with /.
Git commit messages
The release workflow uses separate commit messages for the peeled release manifest and the restored development manifest:
'release' => [ 'commit_message' => 'chore: release version {{version}}', ], 'rollback' => [ 'commit_message' => 'chore: restore development Composer manifest', ],
release.commit_message is used for the commit containing the peeled manifest. The {{version}} placeholder is
replaced with the release tag.
rollback.commit_message is used by rollback --commit when restoring the development manifest.
Both can be overridden for an individual invocation with --commit-message.
Release workflow
The release command commits and tags an already peeled composer.json. It does not peel the manifest itself.
A complete release workflow is:
composer-peel peel composer-peel validate composer-peel release v1.0.0 composer-peel rollback --commit
The workflow is:
- Run
peelto remove the configured development-only sections and create the backup. - 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 restoration.
Release validation
Before proceeding, release verifies that:
- the requested tag is a valid Semantic Version, such as
v1.0.0; - the tag does not already exist;
- the tag is strictly greater than the latest released version;
- the working tree is in the expected release state;
- the backup exists;
composer.jsonis exactly the backup with the configured sections removed;- only allowed files have changed.
By default, the allowed release changes are the peeled composer.json, the backup, CHANGELOG.md, and modified files
in bin/.
If the release state is invalid, no commit or tag is created.
To override the release commit message:
composer-peel release v1.0.0 --commit-message="chore: release {{version}}"
To preview the files, commit message, and tag without creating them:
composer-peel release v1.0.0 --dry-run
The release tag points to the commit containing the peeled manifest. After rollback --commit, the development branch
contains the restored manifest:
Development commit
│
▼
Peeled manifest commit ──► Release tag (v4.2.1)
│
▼
Restored development manifest commit
Important
The release tag points to a commit containing a different composer.json from 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 are no longer available.
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 lock file
composer-peel modifies only composer.json. It does not modify composer.lock.
AI skill
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.
License
This CLI and its library are licensed under the MIT license. Please see LICENSE.md for more details.
Changelog
All noteworthy changes are documented in CHANGELOG.md.
Contributing
If you're considering contributing to this project, have a look at this repository's CONTRIBUTING.md for more advice.
