Add interactive project upgrade wizard#1063
Draft
Soner (shyim) wants to merge 56 commits into
Draft
Conversation
MJML 5 ignores <mj-include> by default for security reasons and
sandboxes resolved include paths to the file's own parent directory,
which silently breaks templates that share partials from a sibling
_includes folder.
Expose two new project-config knobs:
build:
mjml:
enabled: true
search_paths:
- custom/static-plugins
allow_includes: true
For the common layout, where partials live somewhere inside an existing
search_path tree, allow_includes: true is enough: each search_path is
automatically added to the mj-include allowlist for files compiled
within it, so <mj-include path="../_includes/header.mjml"> from a
sibling mail-type dir just works.
For partials living outside any search_path, include_paths is an
escape hatch:
build:
mjml:
allow_includes: true
include_paths:
- shared/email/_includes
Relative include_paths entries are resolved against the project root,
absolute entries are used as-is. Any value in include_paths implies
allow_includes.
Under the hood, allow_includes forwards --config.allowIncludes=true to
mjml, and the merged allowlist (search_path root + resolved extras) is
JSON-encoded and forwarded as --config.includePath=[...].
Compile() and ProcessDirectory() now take a CompileOptions struct.
mjml.NewCompileOptions() builds per-search-path options with the auto
search_path allowlisting. ConfigBuildMJML.ResolveIncludePaths() mirrors
GetPaths semantics for the user-supplied extras. Schema regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
t.TempDir() cleans up its directory automatically, so the explicit os.Remove was a no-op that also tripped golangci-lint's errcheck. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps the all group with 2 updates: [golang.org/x/image](https://github.com/golang/image) and [golang.org/x/net](https://github.com/golang/net). Updates `golang.org/x/image` from 0.40.0 to 0.41.0 - [Commits](golang/image@v0.40.0...v0.41.0) Updates `golang.org/x/net` from 0.54.0 to 0.55.0 - [Commits](golang/net@v0.54.0...v0.55.0) --- updated-dependencies: - dependency-name: golang.org/x/image dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: golang.org/x/net dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the all group with 8 updates: | Package | From | To | | --- | --- | --- | | [docker/github-builder/.github/workflows/build.yml](https://github.com/docker/github-builder) | `1.8.0` | `1.9.0` | | [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.0.0` | `4.1.0` | | [docker/login-action](https://github.com/docker/login-action) | `4.1.0` | `4.2.0` | | [docker/metadata-action](https://github.com/docker/metadata-action) | `6.0.0` | `6.1.0` | | [docker/build-push-action](https://github.com/docker/build-push-action) | `7.1.0` | `7.2.0` | | [step-security/harden-runner](https://github.com/step-security/harden-runner) | `2.19.3` | `2.19.4` | | [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) | `9.2.0` | `9.2.1` | | [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) | `7.2.1` | `7.2.2` | Updates `docker/github-builder/.github/workflows/build.yml` from 1.8.0 to 1.9.0 - [Release notes](https://github.com/docker/github-builder/releases) - [Commits](docker/github-builder@c2782c5...0738332) Updates `docker/setup-buildx-action` from 4.0.0 to 4.1.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](docker/setup-buildx-action@4d04d5d...d7f5e7f) Updates `docker/login-action` from 4.1.0 to 4.2.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](docker/login-action@4907a6d...650006c) Updates `docker/metadata-action` from 6.0.0 to 6.1.0 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](docker/metadata-action@030e881...80c7e94) Updates `docker/build-push-action` from 7.1.0 to 7.2.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@bcafcac...f9f3042) Updates `step-security/harden-runner` from 2.19.3 to 2.19.4 - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](step-security/harden-runner@ab7a940...9af89fc) Updates `golangci/golangci-lint-action` from 9.2.0 to 9.2.1 - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](golangci/golangci-lint-action@1e7e51e...82606bf) Updates `goreleaser/goreleaser-action` from 7.2.1 to 7.2.2 - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](goreleaser/goreleaser-action@1a80836...5daf1e9) --- updated-dependencies: - dependency-name: docker/github-builder/.github/workflows/build.yml dependency-version: 1.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: docker/setup-buildx-action dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: docker/login-action dependency-version: 4.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: docker/metadata-action dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: docker/build-push-action dependency-version: 7.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: step-security/harden-runner dependency-version: 2.19.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all - dependency-name: golangci/golangci-lint-action dependency-version: 9.2.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all - dependency-name: goreleaser/goreleaser-action dependency-version: 7.2.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the all group in /internal/verifier/php with 2 updates: [shopwarelabs/phpstan-shopware](https://github.com/shopwareLabs/phpstan-shopware) and [rector/rector](https://github.com/rectorphp/rector). Updates `shopwarelabs/phpstan-shopware` from 0.2.0 to 0.2.2 - [Release notes](https://github.com/shopwareLabs/phpstan-shopware/releases) - [Commits](shopware/phpstan-shopware@0.2.0...0.2.2) Updates `rector/rector` from 2.4.3 to 2.4.4 - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](rectorphp/rector@2.4.3...2.4.4) --- updated-dependencies: - dependency-name: shopwarelabs/phpstan-shopware dependency-version: 0.2.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all - dependency-name: rector/rector dependency-version: 2.4.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the all group in /internal/verifier/js with 3 updates: [stylelint](https://github.com/stylelint/stylelint), [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `stylelint` from 17.11.1 to 17.12.0 - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md) - [Commits](stylelint/stylelint@17.11.1...17.12.0) Updates `typescript-eslint` from 8.59.4 to 8.60.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.60.0/packages/typescript-eslint) Updates `vitest` from 4.1.6 to 4.1.7 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.7/packages/vitest) --- updated-dependencies: - dependency-name: stylelint dependency-version: 17.12.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all - dependency-name: typescript-eslint dependency-version: 8.60.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all - dependency-name: vitest dependency-version: 4.1.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] <support@github.com>
…erifier/php/all-33b5e38e55 fix(deps): bump the all group in /internal/verifier/php with 2 updates
…06557818c8 fix(deps): bump the all group with 8 updates
…6b5f9d fix(deps): bump the all group with 2 updates
…al/verifier/js/all-677ee332d8 fix(deps): bump the all group in /internal/verifier/js with 3 updates
Automatically generate checksum.json for all extensions after asset builds during shopware-cli project ci. This enables integrity verification for extensions deployed via CI pipelines. - Skips extensions that already have checksum.json - Non-fatal: warns on failure but continues with other extensions - Reuses existing GenerateChecksumJSON with XXH128 algorithm Closes #1058
…ums-in-ci feat: generate extension checksums during project CI builds
feat(mjml): allow mj-include via build config
- Generate coverage profile during test run via COVERAGE_FILE env var - Convert Go coverage to Cobertura XML using gocover-cobertura - Upload coverage report to GitHub's code coverage API - Add code-quality: write permission to test workflow
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).
feat: add code coverage upload via actions/upload-code-coverage
Agent-Logs-Url: https://github.com/shopware/shopware-cli/sessions/2411433d-f0f1-44d6-a812-3b5a8ab56b4b Co-authored-by: shyim <6224096+shyim@users.noreply.github.com>
…map lookup Agent-Logs-Url: https://github.com/shopware/shopware-cli/sessions/09a4e827-7b12-4968-aeeb-f4872cd777be Co-authored-by: shyim <6224096+shyim@users.noreply.github.com>
…cate-appends Make `ConfigDump.EnableClean` idempotent on repeated calls
Agent-Logs-Url: https://github.com/shopware/shopware-cli/sessions/b99c4bba-ac68-4b6f-aa8d-6c5432a9339c Co-authored-by: shyim <6224096+shyim@users.noreply.github.com>
…ir-cleanup Remove redundant temp dir cleanup in `internal/shop/config_test.go`
Replace unquoted $COVER_FLAG string variable with a bash array COVER_FLAGS=() so that when coverage is not requested, the empty array expands to nothing instead of an empty-string argument to go test.
fix: use array for COVER_FLAGS to safely handle empty case
- Move cleanup CI logic from cmd/project/ci.go to internal/extension/cleanup_ci.go - Add comprehensive tests for cleanup functions - Clean up checksum test file
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.
…sums-after-optimization fix: patch extension checksums to remove entries for files deleted during optimization
Write merged snippet files to a temp directory instead of directly into the extension root folder. This avoids clobbering existing locale files (e.g. en-GB) that happen to share the same name as a snippet language.
…ot-locale fix: prevent snippet merge from overwriting root locale files
Contributor
|
Next step: prep this for team demo |
--- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all - dependency-name: rector/rector dependency-version: 2.4.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] <support@github.com>
…erifier/php/all-fb41d19606 fix(deps): bump the all group in /internal/verifier/php with 2 updates
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.
742a657 to
256390f
Compare
…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.
39ef87d to
951109a
Compare
Project creation now validates the project name against the rules for a Docker Compose project name. Names containing umlauts, spaces, dots or other characters that Docker Compose would silently strip or reject (it only allows alphanumerics, dashes and underscores, starting with a letter or digit) are now rejected up front, both in the interactive form and when the name is passed as an argument.
Docker Compose requires project names to contain only lowercase letters, digits, dashes and underscores and to start with a lowercase letter or digit. The previous regex also accepted uppercase letters (e.g. MyShop), which would later fail once the generated Docker setup runs from the project directory. Tighten the regex to lowercase-only and treat uppercase names as invalid.
The interactive create form now validates the project name as the user types instead of only on submit. The input description switches to a red-highlighted hint describing the allowed characters whenever the current value is not a valid Docker Compose project name, and reverts to the normal help text once it is valid. The allowed-character rule is shared between this live hint and the submit-time validation error.
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.
Validate project names for Docker Compose compatibility
The upgrade used to drive the lifecycle by hand: stash a composer.json backup, run system:update:prepare on the old code, swap vendor via composer update, run system:update:finish on the new code, and rewind composer.json on any failure. shopware-deployment-helper already owns that lifecycle (prepare, migrations, finish, theme compile, plugin refresh) and is what production deployments run, so the wizard now delegates to it instead of orchestrating each step itself. - UpdateComposerJson additionally calls EnsureRequire to add shopware/deployment-helper to composer.json when the project does not require it yet. The subsequent composer update pulls it in. - The wizard's task list drops the dedicated "Back up composer.json", "system:update:prepare" and "system:update:finish" tasks and gains a single "vendor/bin/shopware-deployment-helper run" task that runs after composer update. Five tasks total instead of seven. - All composer.json backup / restore plumbing is removed from both the wizard and the headless flow. The pre-flight clean-git-tree check already guarantees `git checkout composer.json composer.lock` is a one-liner if the user wants to revert. - packagist.ComposerJson.EnsureRequire is the new shared helper for "add this require entry if missing"; the devtui setup guide reuses it in place of its private branch.
The compatibility phase used to call the Shopware account API to ask "which extensions are blocked by this Shopware version?". The same question is already answered by the composer-managed plugin metadata the resolver consults right after: for each platform plugin, its require.shopware/* tells us whether it satisfies the target, and the registry (Packagist + Shopware store) tells us whether a newer release would. Use that consistent source instead and drop the account API dependency from the upgrade flow: - projectupgrade.CheckPluginCompatibility is a dry-run of the resolver: for every shopware-platform-plugin in composer.json's require it reports CompatCompatible / CompatUpdatable (with the target version) / CompatBlocker (no compatible release) / CompatUnknown (registry unreachable - e.g. store token missing). - The wizard's loadCompatibility now calls that instead of account_api.GetFutureExtensionUpdates, and renders rows as `name — current → new` (Updatable) or `name — current — status`. - The headless runCompatibilityCheck mirrors the wizard's logic and renders a Plugin / Current / Status table. - packagist.InstalledPackage gains a Version field so the report can show the installed version. - WizardOptions.Extensions and the "skip compat phase when no extensions" branch are removed - the phase always runs against the composer data, and the step counter is always 4. The account-api package itself stays in the codebase; it is still used by `project upgrade-check` and other commands that genuinely need the store's extension catalog.
fix(project): guard project ci on dirty local worktrees
…vQki Resolve conflicts in cmd/project (ci.go imports, ci_test.go helpers, project_create.go decomposition). Revert internal/git additions (IsRepository/WorkingTreeStatus) in favor of origin/main's IsWorkingTreeDirty; rewire project upgrade clean-tree check accordingly.
Replace the homegrown registry + constraint resolver with composer itself. The upgrade now runs `composer require --no-install -W shopware/core:<target> <plugins…>` and reads composer's verdict: success means the upgrade resolves, a non-zero exit with "could not be resolved" means it doesn't, and the blocking plugin(s) are dropped and retried. - new composer_resolve.go: DryRunRequire (compat preview, --dry-run, nothing written) and ApplyRequire (real require, drops unresolvable plugins and retries). Store plugins resolve via the project's own composer config / auth.json - no token plumbing. - delete registry.go, compatibility.go and the version-finding half of plugins.go; keep FindNonComposerPlugins and the ResolveResult reporting type. - wizard + command rewired off Registry; compat phase shows composer's own resolution and the plugins it cannot resolve. - drop now-unused packagist.ConstraintsSatisfiedBy / BumpConstraint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 Teacomposer.go- Composer.json rewriting for target versionsplugins.go- Plugin compatibility resolution and constraint bumpingregistry.go- Package registry abstraction for version lookupsreleases.go- Version filtering and selection logiccmd/project/project_upgrade.go- Newproject upgradecommand with:Upgrade Workflow
The wizard guides users through:
composer update --with-all-dependenciesbin/console system:update:preparebin/console system:update:finishPlugin Resolution
Registry System
CombinedRegistryroutes lookups to appropriate backend (Store vs Packagist)PackagistRegistryqueries repo.packagist.org with minified format supportSupporting Changes
internal/git/withIsRepository()andWorkingTreeStatus()functionsinternal/flexmigrator/withCleanupByHash()for recipe file cleanupImplementation Details
https://claude.ai/code/session_01F4pXpGEuJGsxpfTudpnoLo