The complete manual · published in full, before you pay
Shopware Upgrade Radar — the manual
These are the documents that ship inside the paid kit, rendered from the same files it contains — README.md, INSTALL-AND-RUN.md, HOW-TO-READ-THE-REPORT.md. Read every line before you decide.
The shop is the demo fixture that ships inside the kit, not a customer's: its plugin names are invented so that one run exercises every verdict. The run, the verdicts and the quoted constraints are the engine's own output.
Shopware Upgrade Radar
(Shopware 6 plugin-stack upgrade check. sw67 remains the import/package
name and the repo path — a frozen machine id, never the product's name.)
Predicts plugin/composer conflicts BEFORE a Shopware 6.6→6.7-class update — the radar an agency sweeps across every client shop when planning update windows, without composer runs, vendor trees, or shop access. Adjacent tools are reactive only (Stone&Water Update Manager = rollback, Shopmon = monitoring) — Upgrade Radar is the check that runs before either.
What this version does
This document describes Upgrade Radar v0.3.0, released 2026-08-18 — the release in
the archive you unzipped. python3 -m sw67.cli --version prints it back to you. Nothing
here is a statement about a later release.
- Reads DECLARED state:
composer.lock(typeshopware-platform-plugin),custom/plugins/*,custom/static-plugins/*— from a checkout or copied files. - Evaluates each plugin's
shopware/*constraints against a target version with a composer-semantics evaluator: 15 constraint rules (caret/tilde/wildcard/comparisons/AND/OR/ hyphen ranges/stability flags/branch constraints), each dispatched on by name insw67/constraints.py::RULESand covered by a 38-case truth table. - Verdict per plugin: COMPATIBLE / CONFLICT (constraint quoted) / UNKNOWN (no constraint or unevaluable dev/branch) — UNKNOWN is never silently assumed fine.
- Every plugin carries its next action, including the honest one: where no compatible release exists, the report says there is no fix to install and to ask the vendor for a date.
- Honesty notes printed in every report: declared-constraints-only;
composer update --dry-runstays the definitive gate at update time; transitive non-shopware deps out of scope v0.
Release-availability lookup — the number agencies plan with
For each CONFLICT, --check-releases asks: does a target-compatible release EXIST?
- fixable by plugin update — a release admitting the target is named (e.g.
3.11.1 [packagist]); - genuinely blocking — the plugin's channel was positively checked, nothing admits the target;
- availability n.a. — cannot honestly answer (not on packagist/store, network error) — never guessed.
Providers: packagist p2 metadata (composer-managed) + Shopware store
pluginsByName(public, batched; store absence is disambiguated by a second query at the shop's current core version). BOTH are consulted for every conflict, and each finding names the source that answered it — a packagist-only lookup returned "n.a." on Shopware's ownswag/*plugins, which are store products and mostly absent from packagist, i.e. exactly the ones that decide an update window. Where no store technical name is declared, one is derived from the composer package name and the store is asked; a name it does not recognise is reported as such, never guessed. This is the ONLY networked path; everything else runs offline. Evidence quality = published-release DECLARATIONS — same honesty contract as the constraint check itself.
A report you can keep
Every run writes two files next to you and prints the same answer to the terminal:
upgrade-radar-<shop>-<target>.html (open, print, send to the client) and the same report as
Markdown (paste into a ticket). Each names the product version, the shop and the date, opens
with the verdict and the totals, orders findings by consequence (blocks the upgrade → could not
be judged → ready), and gives every plugin its next action. Written where you run the command
(--report-dir to move them, --no-report to suppress); never inside the shop being read.
Quickstart
See INSTALL-AND-RUN.md for the 5-minute walkthrough. Short form:
python3 -m sw67.cli check <shop-root> --target 6.7.0.0
python3 -m sw67.cli check <shop-root> --target 6.7.0.0 --check-releases
python3 -m sw67.cli check <shop-root> --target 6.7.0.0 --json
What this version does not do
Stated so you know where the edges are, not as a promise about a later release — this document describes the release you are holding and nothing else.
- One shop per run.
checktakes one shop root. A fleet is a loop over shop directories; INSTALL-AND-RUN.md ships the loop. There is no aggregated roll-up. - Declared constraints only. No composer resolution, no vendor tree, no shop access;
composer update --dry-runstays the definitive gate at update time. - Transitive non-shopware dependencies are out of scope.
License
MIT — see LICENSE.
Support & refunds
Questions, bugs, or something that looks wrong: support@apeirionlabs.com. We offer a 30-day refund window from purchase — see https://apeirionlabs.com/refund/ for details.
Shopware Upgrade Radar — install & first run (5 minutes)
Thanks for buying Upgrade Radar. This is a plain CLI tool — unzip and run, no installer,
no account. (sw67 is the frozen Python package name inside it, not the product's name.)
Requirements
- Python 3.10+ (
python3 --version). Nothing else — no pip installs, no composer, no shop access. - A checkout (or copied files) of any Shopware 6 project:
composer.lockat the root, pluscustom/plugins/andcustom/static-plugins/if you use them.
Run
unzip shopware-upgrade-radar-<version>.zip && cd shopware-upgrade-radar
# 1) smoke run against the bundled demo shop (offline):
python3 -m sw67.cli check demo-shop --target 6.7.0.0
# 2) your first real shop:
python3 -m sw67.cli check /path/to/shop-checkout --target 6.7.0.0
# 3) same, plus "does a fix exist?" for every conflict (the only networked step):
python3 -m sw67.cli check /path/to/shop-checkout --target 6.7.0.0 --check-releases
# 4) machine-readable, for your own tooling:
python3 -m sw67.cli check /path/to/shop-checkout --target 6.7.0.0 --json
Exit codes (useful in scripts): 0 no conflicts · 1 conflicts found · 2 no verdict —
the message on screen says whether that is something you can fix (a mistyped --target, a
path that is not a shop, an unreadable composer.lock) or a bug in this tool. 1 means a
real finding about a real shop and nothing else ever produces it, so a loop like the one
below can trust it. You will never see a Python traceback; if you do, that is a bug worth an
email.
What you get to keep
Every run prints the answer to your terminal and writes two files you own:
| file | what it is for |
|---|---|
upgrade-radar-<shop>-<target>.html |
opens in any browser, prints, survives being emailed to a client |
upgrade-radar-<shop>-<target>.md |
pastes into a Jira / GitHub / Redmine ticket without reformatting |
Both carry the same content: the verdict and the totals first, then the plan, then every plugin with the constraint it declares and what to do about it. Both name the product version, the shop and the date they were made, so a colleague reading one six weeks later knows exactly what they are holding.
They are written into the directory you run the command in. To put them elsewhere, or nowhere:
python3 -m sw67.cli check /path/to/shop-checkout --target 6.7.0.0 --report-dir ./upgrade-reports
python3 -m sw67.cli check /path/to/shop-checkout --target 6.7.0.0 --no-report
Nothing is ever written inside the shop you point it at. It reads composer.lock and
your plugin manifests and touches nothing else.
Sweeping a fleet
One shop = one directory, and one run reads one shop. A fleet is a loop, and this is it — because each report is named after its shop, you end up with one file per client:
for shop in /agency/clients/*/; do
echo "== $shop"
python3 -m sw67.cli check "$shop" --target 6.7.0.0 --check-releases --report-dir "$HOME/upgrade-reports"
case $? in
0) echo " clean" ;;
1) echo " CONFLICTS — see the report" ;;
2) echo " skipped: could not be checked (see the message above)" ;;
esac
done
Separating 1 from 2 is the point of the case block: a shop that could not be read is not a
shop with conflicts, and only you can tell the client which one it was.
What it reads, what it sends
Reads composer.lock + custom/*/composer.json only. Offline by default; with
--check-releases the only outbound traffic is package names to packagist.org and
api.shopware.com (public endpoints). No shop data, no client data, no telemetry.
Both sources are tried for every conflict, which matters most for Shopware's own swag/*
plugins: they are sold in the Shopware store and mostly absent from packagist, so packagist
alone cannot answer them. Every release finding names the source that answered it —
[packagist] or [shopware store] — and when neither could, it names both so you know
where the question still stands.
Reading the output
See HOW-TO-READ-THE-REPORT.md (2 pages).
License, support, refunds
Licensed under MIT — see LICENSE. Questions, bugs, or something that looks wrong: support@apeirionlabs.com. Not what you expected? We offer a 30-day refund window from purchase — see https://apeirionlabs.com/refund/ for details.
Shopware Upgrade Radar — How to read the report
(2 pages. Upgrade Radar is built for update-window planning — it reads what your shops
DECLARE, so you find the conflicts before anyone opens a shop. composer update --dry-run
remains the definitive gate at update time; Upgrade Radar tells you, across a whole fleet, where that
gate will slam shut.)
The report is a file, not a scrollback
Each run writes upgrade-radar-<shop>-<target>.html (open it, print it, send it to the
client) and upgrade-radar-<shop>-<target>.md (paste it into a ticket). The terminal
prints the same thing. Every copy names the product version, the shop it examined and the
date it was made, so a report forwarded to a colleague answers "what is this?" by itself.
It opens with the answer
UPGRADE BLOCKED: 16 of 37 plugins block the upgrade to Shopware 6.7.0.0, 4 could not be judged.
Examined acme-shop against target Shopware 6.7.0.0 (current core: 6.6.10.3), 37 plugins.
totals: 17 compatible / 16 conflict / 4 unknown of 37 plugins
of the 16 conflicts: 1 fixable by plugin update / 0 genuinely blocking / 15 availability unknown
Read it as: update effort = conflicts, update risk = unknowns, update blockers = "genuinely blocking". A window where every conflict is "fixable by plugin update" is a normal maintenance window (update plugins first, then core). One "genuinely blocking" plugin means a conversation with the plugin vendor — or with your client — before you commit a date.
The verdict line is one of three: UPGRADE BLOCKED (at least one conflict), NOT CLEAN (no conflicts, but something could not be judged) or UPGRADE CLEAN.
Underneath it, What to do next is the ordered plan for this specific shop — how many
plugins to deal with first, whether a release lookup is still owed, and the
composer update --dry-run confirmation that closes the job.
Then the findings, worst first
Three blocks, in the order they cost you time. Names are alphabetical inside each block.
Blocks the upgrade — at least one declared constraint excludes the target. The excluding
constraint is always quoted, so you can verify every claim yourself in the plugin's
composer.json:
acme/checkout-pro 2.3.1 (composer.lock) — CONFLICT
why: shopware/core '~6.6.0' EXCLUDES 6.7.0.0
do: Update acme/checkout-pro to 3.0.0 or later before the upgrade (packagist), then
re-run this check. Read its changelog for breaking changes first.
With --check-releases each conflict also answers "does a fix exist?", and the
What to do column changes accordingly:
- fixable by plugin update — a released version admitting the target exists; the version and
the source that answered are named in brackets —
[packagist]or[shopware store]. Plan: bump this plugin in the same window. - genuinely blocking (no compatible release published) — the plugin's release channel was positively checked and nothing published admits the target. The action says so plainly: there is no fix to install, ask the vendor for a date, and plan to replace or remove the plugin if the answer is not a date. This is the list worth escalating to the client.
- release availability n.a. — we could not honestly answer (private/agency plugin, not on packagist AND no store entry, network hiccup). Both sources are named so you can see the question was actually asked twice, the reason is stated, and the action tells you where to take it over by hand — the plugin's page in your Shopware Account under My extensions, or the vendor's changelog. The plugin stays blocking until the vendor answers. We never guess a remedy.
Both sources, every conflict. A conflict is offered to packagist and to the Shopware
store, not to whichever one the package name looks like. That is what makes Shopware's own
swag/* plugins answerable: they are store products and mostly absent from packagist, so a
packagist-only lookup returned "n.a." on exactly the plugins that decide a 6.6→6.7 window.
Where a lock entry does not carry a store technical name, one is derived from the composer
package name (swag/cms-extensions → SwagCmsExtensions) and the store is asked — a name the
store does not recognise is reported as exactly that, never as an answer. Findings that used
the derived name say so.
Could not be judged — the plugin declares no shopware/* constraint, or one we can't honestly
evaluate (dev branches, aliases). UNKNOWN is never silently counted as fine: these are your
manual-check list, usually your own in-house plugins, and the action tells you which of the two
it is and what would settle it.
Ready for the target — every shopware/* constraint this plugin declares admits the target
version. Declared ≠ tested by the vendor against the target; treat it as "no declared reason to
break". They are listed so the report accounts for every plugin in the shop, not just the bad news.
Plugins living in custom/plugins/ or custom/static-plugins/ are your code, and their
action says so — nobody else is going to publish their fix.
Where the data comes from (and what never leaves your machine)
- Input:
composer.lock+custom/plugins/*+custom/static-plugins/*from a repo checkout or copied files. No shop access, no composer run, no vendor tree, no credentials. - Offline by default. With
--check-releases, the only outbound requests are package names sent to packagist.org and api.shopware.com (the same public endpoints composer and the Shopware admin use). Nothing about your client, shop, or configuration is transmitted. - Output: the two report files, written where you ran the command (or
--report-dir). Nothing is written inside the shop being examined.
Honest limits (v0.3.0)
- Declarations, not resolution — a plugin can declare 6.7 support and still have bugs; run
composer update --dry-run+ your test suite at update time. sw67 moves the discovery of hard conflicts from update day to planning day. - Transitive conflicts between plugins' own dependencies are out of scope in this release.
- "Fixable by update" tells you a compatible release exists — check its changelog for breaking changes before you bump it.
If the report ever disagrees with what an update actually did, that's exactly what we want to hear about — email support@apeirionlabs.com and we'll trace it to the constraint.