11 KiB
Research: shopware-cli changelog handling and the PHP floor for a Shopware 6.6 plugin
Sources are primary only: shopware/shopware-cli source (main branch, fetched 2026-10-04),
shopware/docs (the repo behind developer.shopware.com, main), and shopware/shopware tags.
Paths below are relative to those repos. Refs are branch heads or tags as noted; no commit SHAs
were pinned.
Summary
extension package --releasedoes not parse CHANGELOG.md. It only generates one, from git commits, and only ifchangelog.enabled: trueis set in.config/shopware-extension.yml. Our pipeline does not pass--release.- Parsing happens elsewhere: in
account producer extension upload(Store push) and inextension get-changelog. The parser treats every line starting with#as a version key. - Our Keep-a-Changelog file is not compatible with that parser. The key becomes
# [0.1.0] - 2026-10-03, never0.1.0, so the Store upload would fail with "english changelog is missing". This only matters if we later useaccount producer extension upload; today we publish to Gitea and a Composer registry. - Minimal fix if we ever upload to the Store: add
CHANGELOG_en-GB.mdwith# x.y.zheadings and flat bullets, with no###sub-headings. KeepCHANGELOG.mdfor humans; the localized file wins foren-GB. - PHP: Shopware 6.6.x requires
~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0(6.6.0.0 was~8.2.0 || ~8.3.0). Ourphp: ^8.2is consistent but broader than core; it is harmless becauseshopware/core ~6.6.0already pins the effective range. - Recommendation: keep
^8.2or use>=8.2. Do not copy core's tilde list. Shopware's own docs skeleton declares nophpat all, onlyshopware/core.
Q1. Changelog handling
1.1 What the parser does (verified in source)
File: internal/extension/changelog.go (shopware-cli, main).
File discovery and locale:
files, err := filepath.Glob(path + "/CHANGELOG*.md")
...
language := strings.Trim(strings.ReplaceAll(strings.ReplaceAll(filepath.Base(file), "CHANGELOG", ""), ".md", ""), "_")
if len(language) == 0 { language = "en-GB" }
CHANGELOG.mdmaps toen-GB.CHANGELOG_en-GB.mdmaps toen-GB, andCHANGELOG_de-DE.mdmaps tode-DE.- If both
CHANGELOG.mdandCHANGELOG_en-GB.mdexist, both land in the same map slot.filepath.Globreturns lexically sorted names and.sorts before_, soCHANGELOG_en-GB.mdis processed last and overwrites. This ordering is inferred from Go's documented sorted-directory behaviour; I did not run it.
Section splitting (parseMarkdownChangelog):
for _, line := range strings.Split(content, "\n") {
if strings.HasPrefix(line, "#") {
...
currentVersion = strings.Trim(strings.TrimPrefix(line, "#"), " ")
- Every line that starts with
#, at any heading depth, starts a new "version" section. - The key is the line minus one leading
#, with spaces trimmed. Only a single#is removed. - The body is converted to HTML with goldmark via
internal/markdown. Headings inside bodies are rendered as<span class="hN">. - Upstream's own tests use this exact shape:
"# 1.0.0\n\n- Test\n- Test2\n# 2.0.0\n- Test3\n- Test4\n"(internal/extension/changelog_test.go).
Version lookup (parseExtensionMarkdownChangelog):
- The key looked up is
ext.GetVersion().String(), which is the plain version fromcomposer.json, for example0.1.0. changelogs["en-GB"][version]must exist, or it returns an error ("english changelog is missing").de-DEis optional and falls back to the English file. If ade-DEfile exists but lacks the version, it errors.- The docs agree: "The zip file contains a
CHANGELOG*.mdfile with a changelog entry for the new version. Having a German changelog is optional." (products/tools/cli/shopware-account-commands/releasing-extension-to-shopware-store.md).
1.2 Where it is used
GetChangelog() is called in only two non-test places:
internal/account-api/producer_store_upload.go:account producer extension uploadsendsde_DEanden_GBtexts as the Store changelog.cmd/extension/extension_changelog.go:extension get-changelog.
Not called from cmd/extension/extension_package.go, nor from validation: no changelog reference was found under internal/validation.
1.3 What --release does
cmd/extension/extension_package.go:
extensionPackageCmd.Flags().BoolVar(&extensionReleaseMode, "release", false, "Prepare a release build: generate the changelog if enabled and, for apps, remove the secret from manifest.xml")
internal/extension/zip.go, PrepareExtensionForRelease:
if ext.GetExtensionConfig().Changelog.Enabled {
...
changelogFile := fmt.Sprintf("# %s\n%s", v.String(), content)
os.WriteFile(path.Join(extensionRoot, "CHANGELOG_en-GB.md"), ...)
}
if ext.GetType() == "plugin" { return nil }
// apps only: remove setup/secret from manifest.xml
- Added: a generated
CHANGELOG_en-GB.md, containing# <version>plus one bullet per git commit between the last tag and the current commit (templateinternal/changelog/changelog.tpl). It only happens ifchangelog.enabled: true. The default is false (internal/changelog/changelog.go,Enabled bool). - Stripped: the app secret in
manifest.xml. This does not apply to plugins, which return early. - Not stripped or rewritten: an existing
CHANGELOG.mdis left untouched. When enabled, the generated file overwrites any existingCHANGELOG_en-GB.md. - This confirms
# x.y.zis the format the tool itself writes. - Docs on release mode and
changelog.enabled:products/tools/cli/extension-commands/build.md, "Release mode" section.
1.4 Our repo
.gitea/actions/package/action.yml:29runsshopware-cli extension package . --output-directory .build --filename ... --git-commit "$GITHUB_SHA". There is no--release, and nochangelog:key was found in.configor.gitea.- The release notes come from our own awk in
.gitea/workflows/release.yml, which matches## [x.y.z]. That is independent of shopware-cli. CHANGELOG.mduses## [0.1.0] - 2026-10-03and### Added/Changed/....
1.5 Compatibility verdict
Walking CHANGELOG.md through parseMarkdownChangelog by hand (not executed):
## [0.1.0] - 2026-10-03gives key# [0.1.0] - 2026-10-03, which never equals0.1.0.- Each
### Addedand similar heading becomes its own bogus "version", so the entries under it are split off.
Result: incompatible. account producer extension upload and extension get-changelog would fail with "english changelog is missing". extension package, with or without --release, would not fail, because it does not call the parser. extension validate does not parse it either, as far as I could find.
Minimal change, only needed if we adopt Store upload:
# 0.1.0
- Services and routes are now configured in YAML; the plugin requires PHP `^8.2`.
- Release pipeline: ...
- Put it in
CHANGELOG_en-GB.mdat the plugin root, with one# <exact composer version>section per release and flat bullets. Prefix bullets with Added/Changed/Fixed if the categories matter. - No
###sub-headings, and no brackets or dates in the heading. CHANGELOG_de-DE.mdis optional.- The alternative is to enable
changelog.enabled: trueplus--releaseand let the CLI generate it from commits. That would not use our hand-written entries. - The packaged zip must contain the file. I did not verify whether
.config/shopware-extension.ymlpack excludes would remove it.
Not verified: the Store's own server-side handling of the changelog HTML. The CLI source only shows what is sent (de_DE/en_GB text), not what the Store renders. The Store API and backend are not public.
Q2. PHP constraint
2.1 What Shopware 6.6 requires
require.php in shopware/shopware src/Core/composer.json and the root composer.json:
| Ref | require.php |
|---|---|
v6.6.0.0 |
"php": "~8.2.0 || ~8.3.0" |
v6.6.10.28 (latest 6.6 tag seen) |
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" |
- The
v6.6.10.28rootcomposer.jsonhas the same line as core. - A fetch of the
6.6.xbranch-headcomposer.jsonreturned no usable content, so only the two tags above are verified. - Official docs,
guides/hosting/index.md("Recommended stack and supported versions"): PHP minimum8.2+, recommended8.4. guides/development/dev-environment.md: "PHP Version |8.2,8.3,8.4,8.5".- Fetching the rendered developer.shopware.com requirements page returned no PHP row. It defers to the hosting guide, which is the source above.
- The repo's own
CLAUDE.mdsays PHP^8.1. That is stale for 6.6, since core requires 8.2+. It is outside this task's scope.
2.2 Is php: ^8.2 right?
composer.json today:
"require": { "php": "^8.2", "shopware/core": "~6.6.0" }
^8.2means>=8.2.0 <9.0.0. It is consistent with core's floor.- It is wider than core's ceiling (
<8.6). The plugin's PHP range is the intersection of its own constraint and core's. Becauseshopware/core ~6.6.0is also required, Composer resolves to core's tilde list anyway. The plugin constraint adds no extra restriction in practice. - Copying core's
~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0would only make sense if the plugin itself breaks on a specific PHP minor. That would also force a release every time Shopware adds a PHP minor. - Practical conclusion:
^8.2is fine and not too strict.>=8.2behaves the same in practice. The only real argument for changing it is wanting the plugin's PHP floor to stay visible in its own metadata, which^8.2already does.
2.3 What Shopware's skeletons recommend
guides/plugins/plugins/creating-plugins.md(docs repo): the example plugincomposer.jsonhas only"require": { "shopware/core": "~6.7.0" }. There is nophpkey, and "Depending on other plugins" repeats the same shape.- shopware-cli's scaffolding (
internal/extension/create.go) does not set aphpconstraint; the only"php"hit in that package is a test fixture,internal/extension/root_test.go:233, with">=8.1". - So the official guidance is to constrain
shopware/coreand leave PHP to core. A pluginphpconstraint is optional. If you want one, mirroring the supported floor (>=8.2or^8.2) is reasonable. - I did not find a
SwagPluginSkeletonrepo (fetchingshopware/SwagPluginSkeletonreturned 404).
Could not verify
- That the Store's server-side processing matches what the CLI sends. The Store backend is not public.
- That
.config/shopware-extension.ymlpack excludes would keep or remove aCHANGELOG_en-GB.mdin the zip. - The exact ordering behaviour of
filepath.Globfor the twoen-GBfiles was inferred from Go's documented sorted results, not run. - No source evidence of whether the Store accepts HTML headings; the CLI renders headings as
<span class="hN">per a comment ininternal/markdown.