PHP tool and GitHub Actions bundle that parses a CHANGELOG.md in CommonMark
format into structured JSON and feeds the result into the central
jtl-software/changelog-page
repository.
This repo ships two things:
- The PHP CLI tool (
src/,bin/changelog-extractor, distributed as a PHAR) - A reusable workflow (
.github/workflows/update-changelog.yaml) for the end-to-end flow including cross-repo PR and auto-merge
Every release published by a consumer repo (e.g. connector-shopify) should
automatically produce a structured changelog entry in the central
changelog-page. The extractor parses the consumer's CHANGELOG.md, merges
the result into the system context file
(changelog-page/storage/systems/<system-name>.json), opens a pull request,
and lets auto-merge finish the job.
The following decisions come from the DP-379 migration plan and are wired into this repo:
The reusable workflow pins PHP 8.3. There is no php-version input. Reason:
consistency with the JTL production servers and self-hosted runners. When
the internal PHP target version changes, four places in this repo have to
be updated:
.github/workflows/update-changelog.yaml—setup-phpstep.github/workflows/check.yaml— everysetup-phpstep.github/workflows/release.yaml—setup-phpstepcomposer.json—require.php
See the "PHP version upgrade" section below.
The reusable workflow downloads the extractor as a PHAR from the GitHub
release asset, not via composer global require. There is no rolling
release to download from: the workflow resolves which release tag was
built from the exact commit the consumer pinned uses: to (see E2.4) and
downloads that release's PHAR specifically. This gives us:
- Reproducible builds (the resolved release pins an exact commit plus its resolved dependencies)
- Supply chain robustness (no remote package resolution at runtime, and no rolling pointer that can silently change what a pinned consumer runs)
- Faster cold runs (one
curlinstead of a Composer install)
If changelog-page/storage/systems/<system-name>.json does not exist, the
workflow aborts with a clear error message. This matches the GitLab
behaviour. New systems have to be created manually in changelog-page
first (see "Onboarding new systems").
The reusable workflow is released via v2.x.y tags, each with its own
GitHub Release carrying the PHAR built at that commit. Consumers pin
uses: to the exact commit SHA that tag points at - never a rolling tag
(@v2 no longer exists) and never the bare v2.x.y tag name either
(tags are mutable; a SHA isn't). Find the SHA for a given tag with:
git rev-parse v2.2.0The legacy 1.0.x tags from the GitLab era remain as history but are no
longer advanced.
The regular case. The reusable workflow handles the end-to-end flow:
generating the app token, checking out the required repos, running the
extractor, opening a PR in changelog-page, and enabling auto-merge.
In the consumer repo as .github/workflows/update-changelog.yaml:
name: update-changelog
on:
release:
types: [published]
jobs:
call:
# Pin to the exact commit SHA of a release tag, never the tag name or a
# rolling ref - see "E2.4 Versioning" above.
uses: jtl-software/changelog-extractor/.github/workflows/update-changelog.yaml@e4c939fe6535edeb4b432e6a6945664bd2a6d036 # v2.2.0
with:
system-name: shopify
# changelog-file: CHANGELOG.md # optional, default
secrets:
APP_ID: ${{ secrets.CHANGELOG_PAGE_APP_ID }}
APP_PRIVATE_KEY: ${{ secrets.CHANGELOG_PAGE_APP_PRIVATE_KEY }}| Input | Required | Default | Description |
|---|---|---|---|
system-name |
yes | — | System name (see above) |
changelog-file |
no | CHANGELOG.md |
Path to the changelog file |
| Secret | Description |
|---|---|
APP_ID |
App ID of the jtl-changelog-page-writer GitHub App. Org secret CHANGELOG_PAGE_APP_ID |
APP_PRIVATE_KEY |
Private key of the app. Org secret CHANGELOG_PAGE_APP_PRIVATE_KEY |
Both org secrets have visibility "Selected repositories" — new consumer repos must be added to that list before they can call this workflow.
The generated app token is restricted to repositories: [changelog-page]
(E3.2). The caller repo does not need any token access to changelog-page.
Releases are automated via .github/workflows/release.yaml when a v* tag
is pushed:
- Create the tag:
git tag v2.0.1 git push origin v2.0.1
- The release workflow:
- Installs Composer dependencies (
--no-dev --prefer-dist) - Builds
changelog-extractor.pharviabuildPhar.php - Runs the fixture self-test (
.github/scripts/verify-fixture.sh) - Creates a GitHub release with
changelog-extractor.pharas an asset
- Installs Composer dependencies (
If the fixture self-test fails, the release is not published. Existing consumers, pinned to older commit SHAs, are unaffected either way - there is nothing rolling for a new release to move underneath them.
v2.0.0 is created manually by Q after review of this PR. The workflow
handles every subsequent release automatically.
When the internal PHP target version changes (e.g. to 8.4), four places in this repo need to be touched:
.github/workflows/update-changelog.yaml—setup-phpstep.github/workflows/check.yaml— everysetup-phpstep.github/workflows/release.yaml—setup-phpstepcomposer.json—require.php
After the change run composer update locally and commit composer.lock.
Bump to a new major tag v3.0.0 when consumers have to migrate as well,
otherwise do a minor bump on v2.
For local development or when the PHAR distribution is temporarily unavailable:
git clone https://github.com/jtl-software/changelog-extractor.git
cd changelog-extractor
composer install
php extractor.php --file path/to/CHANGELOG.md --output out.json
# or: ./bin/changelog-extractor --file ...CLI options:
| Option | Default | Description |
|---|---|---|
--file, -f |
CHANGELOG.md |
Input markdown |
--output, -o |
CHANGELOG.json |
Output JSON |
--context, -c |
(empty) | Context JSON. When set, the result is merged into the changelog key of the context |
--ticket-link, -t |
https://issues.jtl-software.de/issues/%s |
URL template for tickets without an explicit link |
The workflow hard-fails when
changelog-page/storage/systems/<system-name>.json does not exist (E2.3).
To add a new system:
- Create a branch in
jtl-software/changelog-page. - Create
storage/systems/<system-name>.json, minimal shape:({ "name": "<system-name>", "label": "<Display name>", "description": "<Short description of the system>" }changelogis filled in by the extractor later; do not add it by hand.) - Merge the PR.
- Invoke the reusable workflow from the consumer repo (see above). On the
next release the file is automatically extended with the
changelogkey.
The exact set of required fields in the context file comes from the
template in changelog-page — please keep this doc in sync with changes
there.
.github/workflows/check.yaml runs on every PR (and on push to the
default branch):
- PHPCS against
src/,bin/,extractor.php,buildPhar.phpusing theConnectorStandardruleset shipped inphpcs.xml.dist(PSR12 base plus the JTL-connector sniffs fromslevomat/coding-standard). - PHPStan (level 5) against
src/. Runs as an informational, non-blocking step (the legacy GitLab CI defaulted toENABLE_PHPSTAN=falseand never ran PHPStan at all; we keep the findings visible in the step log without gating the merge). - Fixture self-test: builds the PHAR, runs it against
tests/fixtures/and asserts structural properties of the result (seetests/fixtures/README.md). - Negative test: missing context file must produce a non-zero exit code (E2.3).
PHPCS, PHPStan and their standards are declared in composer.json
under require-dev so composer install provisions the full toolchain
locally; CI invokes them via vendor/bin/phpcs and
vendor/bin/phpstan.
Original tool: Tim Platzke, JTL-Software GmbH.