adachsoft / changelog-linter
Changelog linter tool - validate and convert changelog formats
Requires
- php: ^8.3
- adachsoft/collection: ^3.0
- justinrainbow/json-schema: ^5.2 || ^6.0
- twig/twig: ^3.0
Requires (Dev)
- adachsoft/cli-process-tester: ^0.1
- adachsoft/php-code-style: ^0.5
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.1
- rector/rector: ^2.4
- symplify/phpstan-rules: ^14.9
README
A minimal PHP library for validating and converting changelog formats between JSON and Markdown.
It follows a constrained, explicit changelog schema inspired by Keep a Changelog, with first-class support for the following change sections:
AddedChangedFixedRemovedDeprecatedSecurityMigration Notes(JSON key:migration_notes)
Installation
Install via Composer:
composer require --dev adachsoft/changelog-linter
Usage
CLI Commands
Validate changelog (JSON or Markdown)
vendor/bin/changelog validate --path=changelog.json [--lenient]
vendor/bin/changelog validate --path=CHANGELOG.md [--lenient]
- For
.jsonfiles, validation checks the JSON schema, required fields, version ordering and section keys. - For
.mdfiles, validation parses the Markdown and applies the same rules to the logical model.
--lenient applies only to Markdown input and is ignored for JSON:
- tolerates unknown/unsupported sections (they are reported as ignored),
- allows version headers with or without
vprefix, - recognizes
[Unreleased]section.
Generate Markdown from JSON
vendor/bin/changelog generate-md --input=changelog.json --output=CHANGELOG.md
- Reads a
changelog.jsonfile and renders aCHANGELOG.mdusing the built-in Twig template. - All supported sections, including
Migration Notes, are rendered as dedicated Markdown sections:### Added### Changed### Fixed### Removed### Deprecated### Security### Migration Notes
Generate JSON from Markdown
vendor/bin/changelog generate-json \
--input=CHANGELOG.md \
--output=changelog.json \
[--lenient] [--with-created-at]
Options:
--lenient: enables tolerant parsing/validation mode for Markdown → JSON.--with-created-at: when set, adds or preservescreatedAtfor each version.
Behavior of --with-created-at:
- If the output JSON already exists, the tool reads the existing file and preserves
createdAtfor known versions. - New versions (present only in Markdown) receive a fresh
createdAtvalue. - If
--with-created-atis not provided, nocreatedAtfield is added (the schema allows it to be omitted).
Programmatic Usage
The library can be used directly from PHP code through the public facade (no internal services needed):
use AdachSoft\ChangelogLinter\Facade\ChangelogFacadeFactory;
use AdachSoft\ChangelogLinter\Facade\Dto\GenerateJsonOptionsDto;
// Create the facade (all dependencies are wired automatically)
$facade = ChangelogFacadeFactory::create();
// 1. Validate a changelog file (JSON or Markdown)
$result = $facade->validate('changelog.json');
if (!$result->isValid()) {
// handle ValidationResult with errors
}
$resultMd = $facade->validate('CHANGELOG.md');
// 2. Generate Markdown from JSON
$facade->generateMarkdown('changelog.json', 'CHANGELOG.md');
// 3. Generate JSON from Markdown with options
$options = new GenerateJsonOptionsDto(
inputPath: 'CHANGELOG.md',
outputPath: 'changelog.json',
lenient: true,
withCreatedAt: true,
);
$facade->generateJson($options);
You can also use the facade directly to work with the in-memory changelog model:
use AdachSoft\ChangelogLinter\Facade\Dto\AddVersionEntryDto;
use AdachSoft\ChangelogLinter\Model\ChangeItem;
use AdachSoft\ChangelogLinter\Model\ChangeItemsCollection;
$facade = ChangelogFacadeFactory::create();
// Read from JSON or Markdown
db
$changelog = $facade->readChangelog('changelog.json');
// Add a new version entry with Migration Notes
$dto = new AddVersionEntryDto(
version: '1.2.0',
date: '2025-01-01',
added: new ChangeItemsCollection([new ChangeItem('New feature')]),
changed: new ChangeItemsCollection([]),
fixed: new ChangeItemsCollection([]),
removed: new ChangeItemsCollection([]),
deprecated: new ChangeItemsCollection([]),
security: new ChangeItemsCollection([]),
migrationNotes: new ChangeItemsCollection([new ChangeItem('Run database migrations')]),
);
$updated = $facade->addEntry($changelog, $dto);
$facade->writeChangelog($updated, 'changelog.json');
Composer Scripts
The package can expose convenient composer scripts (depending on your project setup):
composer run changelog:validate # Validate changelog.json or CHANGELOG.md
composer run changelog:generate # Generate Markdown from JSON
composer run changelog:from-md # Generate JSON from Markdown
Configuration (changelog-linter.json)
Optional configuration file located at the repository root.
{
"markdown": {
"version_header_prefix": "v",
"allow_prologue": true
},
"json": {
"created_at_field_name": "createdAt",
"created_at_format": "Y-m-d H:i:s"
}
}
markdown.version_header_prefix: Prefix in version headings in Markdown (default:"v").markdown.allow_prologue: Whether to allow any text between the title and first version (default:true).json.created_at_field_name: Field name for creation timestamp in JSON (default:"createdAt").json.created_at_format: Datetime format forcreatedAt(default:"Y-m-d H:i:s").
File Formats
changelog.json (JSON Schema excerpt)
Root-level fields:
title(string, optional)intro(string, optional)unreleased(object withchanges, optional)versions(array of version objects; required)
unreleased object:
changes(object, same structure as for versions)
Version object:
version(string, X.Y.Z)date(string,YYYY-MM-DD)createdAt(string,YYYY-MM-DD HH:MM:SS; optional)changes(object) – allowed keys:added: array of stringschanged: array of stringsfixed: array of stringsremoved: array of stringsdeprecated: array of stringssecurity: array of stringsmigration_notes: array of strings (Migration Notes)
Example changelog.json
{
"title": "Changelog",
"intro": "All notable changes to this project will be documented in this file.",
"unreleased": {
"changes": {
"added": ["Upcoming feature"],
"migration_notes": ["Run database migrations before deploying"]
}
},
"versions": [
{
"version": "0.3.0",
"date": "2025-11-10",
"createdAt": "2025-11-10 10:00:00",
"changes": {
"added": [
"Support for title/intro/unreleased",
"New sections: Deprecated/Security/Migration Notes"
],
"changed": ["createdAt optional in schema"],
"security": [],
"migration_notes": ["Run migration XYZ before upgrading"]
}
}
]
}
CHANGELOG.md (Markdown)
# Changelog
## [Unreleased]
### Added
- Upcoming feature
### Migration Notes
- Run database migrations before deploying
## [v0.3.0] - 2025-11-10
### Added
- Support for title/intro/unreleased
- New sections: Deprecated/Security/Migration Notes
### Changed
- createdAt optional in schema
### Migration Notes
- Run migration XYZ before upgrading
Validation Rules
The validator checks for:
- Valid JSON format
- Required fields
- Semantic versioning format (
X.Y.Z) - Valid date format (
YYYY-MM-DD) - Optional
createdAtformat (YYYY-MM-DD HH:ii:ss) when present - Versions sorted in descending order
- No duplicate versions
- Valid change section keys (
added,changed,fixed,removed,deprecated,security,migration_notes) - For Markdown: that only supported sections are used (unsupported sections are reported/ignored depending on mode)
Development
Running Tests
composer test
Code Style
composer cs:check # Check code style
composer cs:fix # Fix code style
Static Analysis
composer stan
Requirements
- PHP >= 8.2
- Composer
Dependencies
- justinrainbow/json-schema: JSON Schema validation
- twig/twig: Markdown generation templates
- adachsoft/collection: Immutable collections
- phpunit/phpunit: Testing framework