Problem
Installing or running DevTools inside the composer-plugin consumer fixture exposed a fatal error in the LICENSE generation flow when the consumer composer.json does not declare a license field:
In Resolver.php line 54:
[TypeError]
FastForward\DevTools\License\Resolver::resolve(): Argument #1 ($license) must be of type string, null given, called in /Users/mentordosnerds/Sites/github.com/php-fast-forward/dev-tools/src/License/Generator.php on line 87
Generator::generateContent() passes ComposerJsonInterface::getLicense() directly into ResolverInterface::resolve(). The resolver requires a non-null string, so repositories without a license field crash before the generator can follow its documented “unsupported or unavailable license returns null” behavior.
Expected Behavior
A missing license in the consumer composer.json MUST be handled as a non-generatable LICENSE state, not as a fatal TypeError.
The license command and any sync/install flow that triggers LICENSE generation SHOULD:
- return
null / skip generation when composer.json has no license field;
- avoid writing a
LICENSE file when no supported license can be resolved;
- emit clear command feedback when the command surface needs to explain why generation was skipped;
- preserve the current behavior for supported and unsupported non-null license strings.
Implementation Notes
Likely areas to inspect:
src/License/Generator.php
src/License/Resolver.php
src/License/ResolverInterface.php
src/Console/Command/LicenseCommand.php
tests/License/GeneratorTest.php
tests/License/ResolverTest.php
tests/Console/Command/LicenseCommandTest.php
tests/Fixtures/composer-plugin-consumer/composer.json
Possible approaches:
- Guard
Generator::generateContent() before calling the resolver when ComposerJsonInterface::getLicense() returns null or an empty string.
- Alternatively, update the resolver contract to accept
?string and normalize missing/empty values to null resolution.
The implementation SHOULD choose the contract that keeps responsibilities clearest and avoids spreading null checks across command orchestration.
Acceptance Criteria
- A consumer project without
license in composer.json no longer crashes when composer dev-tools license or the relevant sync/install flow runs.
- Missing or empty license metadata produces no
LICENSE output and returns a controlled skip/null result.
- Unsupported string license identifiers still return a controlled skip/null result.
- Supported license identifiers continue generating the expected content.
- PHPUnit coverage includes at least one missing-license regression case.
- The composer-plugin consumer fixture can be used to reproduce the no-license path without a fatal TypeError.
CHANGELOG.md documents the bug fix.
Non-Goals
- Do not infer a default license for consumers.
- Do not change supported license template mappings unless needed for this bug.
- Do not broaden this issue into a full license metadata redesign.
Problem
Installing or running DevTools inside the composer-plugin consumer fixture exposed a fatal error in the LICENSE generation flow when the consumer
composer.jsondoes not declare alicensefield:Generator::generateContent()passesComposerJsonInterface::getLicense()directly intoResolverInterface::resolve(). The resolver requires a non-null string, so repositories without alicensefield crash before the generator can follow its documented “unsupported or unavailable license returns null” behavior.Expected Behavior
A missing license in the consumer
composer.jsonMUST be handled as a non-generatable LICENSE state, not as a fatal TypeError.The
licensecommand and any sync/install flow that triggers LICENSE generation SHOULD:null/ skip generation whencomposer.jsonhas nolicensefield;LICENSEfile when no supported license can be resolved;Implementation Notes
Likely areas to inspect:
src/License/Generator.phpsrc/License/Resolver.phpsrc/License/ResolverInterface.phpsrc/Console/Command/LicenseCommand.phptests/License/GeneratorTest.phptests/License/ResolverTest.phptests/Console/Command/LicenseCommandTest.phptests/Fixtures/composer-plugin-consumer/composer.jsonPossible approaches:
Generator::generateContent()before calling the resolver whenComposerJsonInterface::getLicense()returnsnullor an empty string.?stringand normalize missing/empty values tonullresolution.The implementation SHOULD choose the contract that keeps responsibilities clearest and avoids spreading null checks across command orchestration.
Acceptance Criteria
licenseincomposer.jsonno longer crashes whencomposer dev-tools licenseor the relevant sync/install flow runs.LICENSEoutput and returns a controlled skip/null result.CHANGELOG.mddocuments the bug fix.Non-Goals