Skip to content

Add interactive project upgrade wizard#1063

Draft
Soner (shyim) wants to merge 10 commits into
nextfrom
claude/great-carson-fvQki
Draft

Add interactive project upgrade wizard#1063
Soner (shyim) wants to merge 10 commits into
nextfrom
claude/great-carson-fvQki

Conversation

@shyim
Copy link
Copy Markdown
Member

Summary

Implements an interactive upgrade wizard for Shopware projects that guides users through the upgrade process with a TUI interface. This mirrors the behavior of the shopware/web-installer but runs from the command line.

Key Changes

New Packages

  • internal/projectupgrade/ - Core upgrade logic package containing:

    • wizard.go - Interactive TUI-based upgrade wizard using Bubble Tea
    • composer.go - Composer.json rewriting for target versions
    • plugins.go - Plugin compatibility resolution and constraint bumping
    • registry.go - Package registry abstraction for version lookups
    • releases.go - Version filtering and selection logic
    • Comprehensive test coverage for all modules
  • cmd/project/project_upgrade.go - New project upgrade command with:

    • Interactive wizard mode (default when TTY available)
    • Headless mode for CI/CD pipelines
    • Git working tree validation
    • Composer-managed plugin verification

Upgrade Workflow

The wizard guides users through:

  1. Welcome screen - Confirm upgrade intent
  2. Version selection - Choose target Shopware version
  3. Compatibility check - Query Shopware Account API for extension compatibility
  4. Review - Confirm selected version and changes
  5. Execution - Run upgrade tasks with live progress:
    • Back up composer.json
    • Clean up stale recipe files
    • Resolve incompatible custom plugins
    • Rewrite composer.json
    • Run composer update --with-all-dependencies
    • Execute bin/console system:update:prepare
    • Execute bin/console system:update:finish

Plugin Resolution

  • Detects incompatible custom plugins by checking Shopware package constraints
  • Attempts to find compatible versions via registry (Packagist or Shopware Store)
  • Bumps constraints to compatible versions when available
  • Removes plugins with no compatible release
  • Restores composer.json on failure

Registry System

  • CombinedRegistry routes lookups to appropriate backend (Store vs Packagist)
  • PackagistRegistry queries repo.packagist.org with minified format support
  • Extensible interface for custom registry implementations

Supporting Changes

  • Enhanced internal/git/ with IsRepository() and WorkingTreeStatus() functions
  • Extended internal/flexmigrator/ with CleanupByHash() for recipe file cleanup
  • Version filtering logic matching web-installer behavior

Implementation Details

  • Uses Bubble Tea for interactive TUI with spinner animations
  • Streams subprocess output (composer, console commands) to live log view
  • Graceful error handling with composer.json restoration on failure
  • Context-based cancellation support for long-running operations
  • Comprehensive test coverage including wizard state machine and plugin resolution

https://claude.ai/code/session_01F4pXpGEuJGsxpfTudpnoLo

Mirrors the shopware/web-installer Update flow so projects can be
upgraded from the command line:

- Reads the current Shopware version from composer.lock
- Filters available releases the same way as ReleaseInfoProvider (next
  major + remaining patches of the current major, no RCs)
- Prompts for the target version (or `--to` flag), then runs the
  existing extension compatibility check before continuing
- Backs up composer.json, cleans up recipe-managed stale files by MD5,
  removes incompatible symlinked custom plugins, rewrites composer.json
  (shopware/core + administration/storefront/elasticsearch when
  required, minimum-stability for RC targets, symfony/runtime
  constraint relax)
- Runs `composer update --with-all-dependencies --no-scripts` and
  restores the backup on failure
- Runs `bin/console system:update:prepare` and `system:update:finish`
- Tracks the outcome

Extracts the MD5-based cleanup map from `flexmigrator.Cleanup` into a
new `flexmigrator.CleanupByHash` helper so the upgrade flow can reuse
it without also deleting flex-migration-specific files.
The interactive flow is now a small bubbletea Program that mirrors
the install-wizard / setup-guide visual idiom:

- Welcome card (cowsay mascot) with current version + project root
- Step 1: select target version (RenderSelectList)
- Step 2 (when extensions are installed): compatibility lookup with
  spinner, then per-extension checkmark/blocker icons
- Step 3: review card with from/to/executor and the full task list
- Step 4: running phase with per-task spinner / checkmark / failure
  icons and a live tail of the composer/console output
- Done card summarising success or failure, restored composer.json on
  failure, and listing any plugins that were dropped

Non-interactive mode (`-n`) and `--to <version>` continue to use the
existing headless flow so CI runs are unchanged.
The upgrade rewrites composer.json, deletes recipe-managed files, and
drops incompatible plugins. Mixing those rewrites with unrelated
uncommitted changes makes it hard to review the diff or roll back, so
the command now refuses to run with a dirty working tree.

- Adds `git.IsRepository` and `git.WorkingTreeStatus` helpers so other
  commands can reuse the same checks.
- When the project directory is not inside a git working tree the
  check is skipped (greenfield projects, tarball-installed copies).
- The error message lists up to ten changed paths and points at
  `--allow-dirty` as the explicit override.
…gins

Before doing anything destructive, `project upgrade` now requires every
directory under custom/plugins/ to be tracked by composer (i.e. appear
in vendor/composer/installed.json). When plain file-drop plugins are
detected the upgrade aborts with a pointer at
`project autofix composer-plugins`. The `--allow-non-composer` flag
opts out for projects that have not migrated yet.

When a composer-managed plugin's declared shopware/core constraint is
not satisfied by the upgrade target, the resolver now queries a package
registry (repo.packagist.org for plain composer packages,
packages.shopware.com for store.shopware.com/* packages) for the newest
release whose require.shopware/core does satisfy the target and rewrites
the composer.json constraint to "^<that-version>". Only when no
compatible release is found does the plugin fall back to being dropped,
matching the old behaviour.

The Shopware Packages token is read from SHOPWARE_PACKAGES_TOKEN or the
project's auth.json. When neither is present and the project has store
plugins the interactive flow prompts for the token (and skips store
lookups gracefully if the prompt is left empty).

The wizard's "Done" card now lists bumped constraints (old → new) in
addition to the removed plugins, so users can see exactly what shifted.

Tests: 9 new tests covering the resolver (bump, remove, registry error,
no installed.json), FindNonComposerPlugins, and the
ensureAllPluginsAreComposerManaged pre-flight check. All packages pass.
- Drop trailing punctuation from the dirty-git-tree and
  non-composer-plugin error strings (ST1005).
- Make the phase / task-status switches exhaustive (exhaustive).
- Rewrite the compat-result if/else chain as a tagless switch
  (gocritic).
- Rename the `max` parameter in `truncate` to `maxRunes` so it stops
  shadowing the predeclared builtin (predeclared).
- Wrap `resp.Body.Close()` in a small `closeBody` helper so we don't
  ignore its error inline (errcheck).
- Rename the test-only `stringErr` type to `testError` to match the
  `xxxError` naming convention (errname).
- Add `t.Parallel()` to the render-smoke subtest (tparallel).
- Drop the unused `upgradeDoneMsg` type (unused).
- Drop the unused `projectRoot` parameter from `runCompatibilityCheck`
  (unparam).
The registry duplicated the packagist HTTP client, the composer v2
minified-metadata unminifier, the response-body closer, and the
packages.json fetch. Move the generic lookups into the packagist
package instead:

- Add packagist.GetComposerPackageVersions(ctx, name) for any composer
  package and rebuild GetShopwarePackageVersions on top of it.
- Add a Require field to packagist.PackageVersion so store-package
  metadata carries its shopware/core constraint.

PackagistRegistry and ShopwareStoreRegistry now delegate to the
packagist package, dropping ~120 lines of duplicated logic.
@lasomethingsomething
Copy link
Copy Markdown
Contributor

Next step: prep this for team demo

The upgrade flow parsed vendor/composer/installed.json by hand, resolved
install paths, and re-implemented composer version-constraint checks.
Move that composer logic into the packagist package where the rest of
the composer model (composer.json, composer.lock, auth.json) already
lives:

- packagist.InstalledJson / InstalledPackage / ReadInstalledJson model
  and read vendor/composer/installed.json.
- InstalledPackage.InstallDirName resolves a package's install location
  (symlinks included) to its directory name under a given base dir.
- packagist.ConstraintsSatisfiedBy reports whether a require map's
  constraints for a set of packages are satisfied by a target version.
- packagist.BumpConstraint turns a concrete version into a caret
  constraint.

projectupgrade now consumes these helpers and keeps only the upgrade
policy (which Shopware packages matter, registry resolution). Its
duplicate ShopwarePackages list is reused in place of the former
pluginShopwarePackages. plugins.go drops ~145 lines.
@shyim Soner (shyim) force-pushed the claude/great-carson-fvQki branch 2 times, most recently from 742a657 to 256390f Compare May 29, 2026 07:15
…teps

Several fixes to the project upgrade flow surfaced by real upgrades:

- Treat store "with new Shopware version" status as resolvable, not a
  blocker. Classification now keys on the semantic status name
  (notCompatible) instead of the display color, matching the platform's
  ExtensionCompatibility constants.
- Resolve plugin constraints for vendor-installed plugins, not just those
  under custom/plugins/. Scope candidates by the root composer.json require
  so store plugins (swag/*, frosh/*) installed into vendor/ get bumped too.
- Look up store-owned, vendor-named plugins via the store registry first
  (falling back to Packagist) instead of routing only by name prefix.
- Run system:update:prepare before composer update so it executes on the
  still-installed Shopware; restore composer.json if prepare fails.
- Center the wizard in the terminal and replay the full failed-step log
  after the alt-screen tears down.

Adds tests for status classification, vendor-installed resolution, registry
routing, and upgrade step ordering.
@shyim Soner (shyim) force-pushed the claude/great-carson-fvQki branch from 39ef87d to 951109a Compare May 29, 2026 07:23
Replace the wizard's hand-rolled version-list cursor and rendering with the
reusable tui.SelectList component: it owns the cursor, windowing, paging
(PgUp/PgDn, Home/End) and the navigation shortcuts, so the wizard only
forwards keys and renders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants