diff --git a/.config/shopware-extension.yml b/.config/shopware-extension.yml new file mode 100644 index 0000000..fa16e4b --- /dev/null +++ b/.config/shopware-extension.yml @@ -0,0 +1,22 @@ +compatibility_date: 2026-02-11 +# shopware-cli extension configuration (build/package/validate) +build: + zip: + pack: + excludes: + paths: + - tests + - docs + - scripts + - phpunit.xml + - docker-compose.yml + - skills-lock.json + - AGENTS.md + - CLAUDE.md + - QWEN.md + - .claude + - .scratch + - .wayfinder + - src/Resources/config/plugin-transparent.png +validation: + ignore: [] diff --git a/.gitea/actions/package/action.yml b/.gitea/actions/package/action.yml new file mode 100644 index 0000000..131f957 --- /dev/null +++ b/.gitea/actions/package/action.yml @@ -0,0 +1,35 @@ +name: package-extension +description: >- + Package the Shopware extension with shopware-cli (builds admin/storefront assets), validate the + zip and assert it contains the built JS. Shared by the PR checks and the release workflow. + Must run in the ghcr.io/shopware/shopware-cli image, after actions/checkout. + +inputs: + filename: + description: Name of the zip written to .build/ + default: package.zip + build: + description: "'false' skips packaging (a prebuilt zip is already in .build/) and only validates it" + default: 'true' + +runs: + using: composite + steps: + - uses: actions/cache@v4 + if: inputs.build == 'true' + with: + path: | + ~/.npm + ~/.cache/shopware-cli + key: swcli-${{ runner.os }}-${{ hashFiles('composer.json', 'src/Resources/app/**/package*.json') }} + restore-keys: swcli-${{ runner.os }}- + - name: Package extension + if: inputs.build == 'true' + shell: bash + run: shopware-cli extension package . --output-directory .build --filename "${{ inputs.filename }}" --git-commit "$GITHUB_SHA" + - name: Validate packaged zip + shell: bash + run: shopware-cli extension validate ".build/${{ inputs.filename }}" + - name: Assert built JS is in the zip + shell: bash + run: scripts/ci/assert-built-assets.sh ".build/${{ inputs.filename }}" diff --git a/.gitea/workflows/plugin-checks.yml b/.gitea/workflows/plugin-checks.yml new file mode 100644 index 0000000..e437554 --- /dev/null +++ b/.gitea/workflows/plugin-checks.yml @@ -0,0 +1,148 @@ +name: plugin-checks + +# Plugin-agnostic: nothing below names this plugin. Name, version and paths come from composer.json. +# shopware-cli runs from its official Docker image, intentionally unpinned (latest); a red build +# after a CLI release means fix the config. + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +# A new push to the PR cancels the superseded run. +concurrency: + group: plugin-checks-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Every PR into main must bump the version and document it in CHANGELOG.md. + version-changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check version bump and changelog entry + run: | + set -e + PR_VERSION=$(grep -m1 '"version"' composer.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') + MAIN_VERSION=$(git show origin/main:composer.json | grep -m1 '"version"' | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') + + echo "main version: $MAIN_VERSION" + echo "PR version: $PR_VERSION" + + if [ "$MAIN_VERSION" = "$PR_VERSION" ]; then + echo "::error::composer.json version ($PR_VERSION) was not bumped from main ($MAIN_VERSION)" + exit 1 + fi + + HIGHEST=$(printf '%s\n%s\n' "$MAIN_VERSION" "$PR_VERSION" | sort -V | tail -n1) + if [ "$HIGHEST" != "$PR_VERSION" ]; then + echo "::error::composer.json version ($PR_VERSION) is not greater than main's ($MAIN_VERSION)" + exit 1 + fi + + if ! grep -qF "## [$PR_VERSION]" CHANGELOG.md; then + echo "::error::CHANGELOG.md is missing a '## [$PR_VERSION]' heading" + exit 1 + fi + + echo "OK: version bumped to $PR_VERSION and documented in CHANGELOG.md" + + # Packages the extension (builds admin/storefront assets) and validates the resulting zip, + # which is the artifact that ships. A separate directory validate would only repeat these checks. + package: + runs-on: ubuntu-latest + container: + image: ghcr.io/shopware/shopware-cli:latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.gitea/actions/package + # The release workflow reuses this tested zip when main ends up with the same tree + # (it falls back to packaging again otherwise). + - name: Tree hash of the packaged commit + id: tree + run: echo "hash=$(git rev-parse "${GITHUB_SHA}^{tree}")" >> "$GITHUB_OUTPUT" + - name: Save tested zip for the release + uses: actions/cache/save@v4 + with: + path: .build/package.zip + key: plugin-zip-${{ steps.tree.outputs.hash }} + + phpunit: + runs-on: ubuntu-latest + # Same image as above: it already ships PHP, Composer and Node, so no apt install. + container: + image: ghcr.io/shopware/shopware-cli:latest + services: + database: + # Shopware 6.6 supports MariaDB 10.11 / MySQL 8; 11.4 broke Shopware's own migrations. + image: mariadb:10.11 + env: + MARIADB_ROOT_PASSWORD: root + env: + # Already the *_test name: Shopware's TestBootstrapper would otherwise append _test and run every migration a second time. + DATABASE_URL: mysql://root:root@database:3306/shopware_test + APP_ENV: test + APP_URL: http://localhost + APP_SECRET: ci-secret-not-for-production + INSTANCE_ID: ci + COMPOSER_MEMORY_LIMIT: '-1' + COMPOSER_ALLOW_SUPERUSER: '1' + steps: + - uses: actions/checkout@v4 + with: + path: plugin-src + - name: Month stamp (refreshes the cached project monthly) + id: stamp + run: echo "month=$(date +%Y-%m)" >> "$GITHUB_OUTPUT" + # The project (incl. vendor/ and phpunit) does not depend on the plugin, so it is cached on its own. + - name: Restore prepared Shopware project + id: project + uses: actions/cache/restore@v4 + with: + path: shopware + key: shopware-6.6-prepared-${{ steps.stamp.outputs.month }} + - name: Create Shopware 6.6 project + if: steps.project.outputs.cache-hit != 'true' + run: | + set -e + composer create-project shopware/production:6.6.* shopware --no-interaction --no-scripts + composer require --dev phpunit/phpunit:^10 --no-interaction --no-scripts -d shopware + - name: Save prepared Shopware project + if: steps.project.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: shopware + key: shopware-6.6-prepared-${{ steps.stamp.outputs.month }} + - name: Wait for the database + run: | + for i in $(seq 60); do + (echo > /dev/tcp/database/3306) 2>/dev/null && exit 0 + sleep 2 + done + echo "::error::database service did not come up"; exit 1 + - name: Relax database durability (throwaway CI database, speeds up the migrations) + run: | + php -r '$p = new PDO("mysql:host=database;port=3306", "root", "root"); $p->exec("SET GLOBAL innodb_flush_log_at_trx_commit=0"); $p->exec("SET GLOBAL sync_binlog=0");' + - name: Resolve plugin name and install the plugin sources + run: | + # plugin directory name = short class name of extra.shopware-plugin-class + CLASS=$(php -r 'echo json_decode(file_get_contents("plugin-src/composer.json"), true)["extra"]["shopware-plugin-class"];') + PLUGIN_NAME="${CLASS##*\\}" + echo "PLUGIN_NAME=${PLUGIN_NAME}" >> "$GITHUB_ENV" + rm -rf "shopware/custom/plugins/${PLUGIN_NAME}" + cp -r plugin-src "shopware/custom/plugins/${PLUGIN_NAME}" + # One install into shopware_test; the bootstrapper sees an installed DB, skips its own install and + # only activates the plugin (tests/TestBootstrap.php forces plugin installation). + - name: Install Shopware (once) + working-directory: shopware + run: bin/console system:install --basic-setup --create-database --force --no-interaction + - name: Run phpunit + working-directory: shopware + run: vendor/bin/phpunit -c "custom/plugins/${PLUGIN_NAME}/phpunit.xml" diff --git a/.gitea/workflows/pr-version-check.yml b/.gitea/workflows/pr-version-check.yml deleted file mode 100644 index 3f5a2d8..0000000 --- a/.gitea/workflows/pr-version-check.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: version-changelog - -on: - pull_request: - branches: [main] - -jobs: - version-changelog: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check version bump and changelog entry - run: | - set -e - PR_VERSION=$(grep -m1 '"version"' composer.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') - MAIN_VERSION=$(git show origin/main:composer.json | grep -m1 '"version"' | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') - - echo "main version: $MAIN_VERSION" - echo "PR version: $PR_VERSION" - - if [ "$MAIN_VERSION" = "$PR_VERSION" ]; then - echo "::error::composer.json version ($PR_VERSION) was not bumped from main ($MAIN_VERSION)" - exit 1 - fi - - HIGHEST=$(printf '%s\n%s\n' "$MAIN_VERSION" "$PR_VERSION" | sort -V | tail -n1) - if [ "$HIGHEST" != "$PR_VERSION" ]; then - echo "::error::composer.json version ($PR_VERSION) is not greater than main's ($MAIN_VERSION)" - exit 1 - fi - - if ! grep -qF "## [$PR_VERSION]" CHANGELOG.md; then - echo "::error::CHANGELOG.md is missing a '## [$PR_VERSION]' heading" - exit 1 - fi - - echo "OK: version bumped to $PR_VERSION and documented in CHANGELOG.md" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index ed1e8bb..3a91d7e 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,31 +1,52 @@ name: release +# Plugin-agnostic: owner, repo, host, package name and version come from the `github` context +# and composer.json. shopware-cli runs from its official Docker image, intentionally unpinned (latest). +# RELEASE_TOKEN is the single token used for releases, assets and the Composer registry. + on: push: branches: [main] -env: - SERVER_URL: https://git.arthurerlich.de +permissions: + contents: write + packages: write + +# Never run two releases at once; a second push waits instead of racing on the tag. +concurrency: + group: release + cancel-in-progress: false jobs: release: runs-on: ubuntu-latest + container: + image: ghcr.io/shopware/shopware-cli:latest steps: + - name: Ensure helper tools (jq, curl, unzip, git) + run: | + command -v jq >/dev/null && command -v curl >/dev/null && command -v unzip >/dev/null && command -v git >/dev/null && exit 0 + (apt-get update -q && apt-get install -y -q --no-install-recommends jq curl unzip git) || apk add --no-cache jq curl unzip git + - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - name: Determine version and guard against re-release - id: version + id: meta run: | set -e - VERSION=$(grep -m1 '"version"' composer.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') - PACKAGE_NAME=$(grep -m1 '"name"' composer.json | sed -E 's/.*"name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') + VERSION=$(jq -r .version composer.json) + PACKAGE_NAME=$(jq -r .name composer.json) + REPO_NAME="${GITHUB_REPOSITORY#*/}" TAG="v${VERSION}" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "package_name=${PACKAGE_NAME}" >> "$GITHUB_OUTPUT" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + { + echo "version=${VERSION}" + echo "package_name=${PACKAGE_NAME}" + echo "tag=${TAG}" + echo "zip=${REPO_NAME}-${TAG}.zip" + } >> "$GITHUB_OUTPUT" if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then echo "skip=true" >> "$GITHUB_OUTPUT" echo "Tag ${TAG} already exists, nothing to release." @@ -33,68 +54,66 @@ jobs: echo "skip=false" >> "$GITHUB_OUTPUT" fi + # Reuse the zip the PR workflow already built and tested for this exact tree. + - name: Tree hash of this commit + if: steps.meta.outputs.skip == 'false' + id: tree + run: echo "hash=$(git rev-parse "${GITHUB_SHA}^{tree}")" >> "$GITHUB_OUTPUT" + + - name: Restore the tested zip from the PR run + if: steps.meta.outputs.skip == 'false' + id: prebuilt + uses: actions/cache/restore@v4 + with: + path: .build/package.zip + key: plugin-zip-${{ steps.tree.outputs.hash }} + + - name: Name the prebuilt zip + if: steps.meta.outputs.skip == 'false' && steps.prebuilt.outputs.cache-hit == 'true' + run: mv .build/package.zip ".build/${{ steps.meta.outputs.zip }}" + + # Packages only on a cache miss; always validates and asserts what ships (guards a bad cache entry). + - uses: ./.gitea/actions/package + if: steps.meta.outputs.skip == 'false' + with: + filename: ${{ steps.meta.outputs.zip }} + build: ${{ steps.prebuilt.outputs.cache-hit != 'true' }} + - name: Extract changelog section - if: steps.version.outputs.skip == 'false' + if: steps.meta.outputs.skip == 'false' run: | - awk -v ver="${{ steps.version.outputs.version }}" ' + awk -v ver="${{ steps.meta.outputs.version }}" ' BEGIN { found=0 } /^## \[/ { if (found) exit - if ($0 ~ "\[" ver "\]") { found=1; next } + if ($0 ~ "\\[" ver "\\]") { found=1; next } } found { print } ' CHANGELOG.md > release-notes.md cat release-notes.md - - name: Create and push tag - if: steps.version.outputs.skip == 'false' + - name: Create Gitea release (creates the tag) and upload the zip + if: steps.meta.outputs.skip == 'false' + env: + TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - git config user.name "gitea-actions" - git config user.email "actions@git.arthurerlich.de" - git tag "${{ steps.version.outputs.tag }}" - git push origin "${{ steps.version.outputs.tag }}" + set -e + API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" + BODY=$(jq -n --arg tag "${{ steps.meta.outputs.tag }}" --arg sha "$GITHUB_SHA" --rawfile notes release-notes.md \ + '{tag_name:$tag, target_commitish:$sha, name:$tag, body:$notes}') + RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -d "$BODY" "${API}/releases" | jq -r .id) + echo "release id: ${RELEASE_ID}" + curl -sf -H "Authorization: token ${TOKEN}" \ + -F "attachment=@.build/${{ steps.meta.outputs.zip }}" \ + "${API}/releases/${RELEASE_ID}/assets?name=${{ steps.meta.outputs.zip }}" > /dev/null - - name: Install shopware-cli - if: steps.version.outputs.skip == 'false' - run: | - curl -sL -o /tmp/shopware-cli.tar.gz \ - https://github.com/shopware/shopware-cli/releases/latest/download/shopware-cli_Linux_x86_64.tar.gz - mkdir -p /tmp/swcli-extract - tar -xzf /tmp/shopware-cli.tar.gz -C /tmp/swcli-extract - cp /tmp/swcli-extract/shopware-cli /usr/local/bin/shopware-cli - - - name: Validate extension (informational only, does not gate the release) - if: steps.version.outputs.skip == 'false' - run: shopware-cli extension validate . || true - - - name: Package Store-style zip for the Gitea Release asset - if: steps.version.outputs.skip == 'false' - run: | - shopware-cli extension package . --output-directory .build \ - --filename "AeonDumpManager-${{ steps.version.outputs.tag }}.zip" - - - name: Build root-level zip for the Composer registry - if: steps.version.outputs.skip == 'false' - run: git archive --format=zip -o "composer-package-${{ steps.version.outputs.tag }}.zip" "${{ steps.version.outputs.tag }}" - - - name: Create Gitea Release - if: steps.version.outputs.skip == 'false' - uses: akkuman/gitea-release-action@v1 - with: - server_url: ${{ env.SERVER_URL }} - token: ${{ secrets.RELEASE_TOKEN }} - tag_name: ${{ steps.version.outputs.tag }} - name: ${{ steps.version.outputs.tag }} - body_path: release-notes.md - files: .build/AeonDumpManager-${{ steps.version.outputs.tag }}.zip - - - name: Publish to Composer registry - if: steps.version.outputs.skip == 'false' + - name: Publish the same zip to the Composer registry + if: steps.meta.outputs.skip == 'false' run: | STATUS=$(curl -s -o /tmp/composer-publish.log -w "%{http_code}" \ - -u "haylan:${{ secrets.RELEASE_TOKEN }}" \ - -T "composer-package-${{ steps.version.outputs.tag }}.zip" \ - "https://git.arthurerlich.de/api/packages/haylan/composer") + -u "${GITHUB_REPOSITORY_OWNER}:${{ secrets.RELEASE_TOKEN }}" \ + -T ".build/${{ steps.meta.outputs.zip }}" \ + "${GITHUB_SERVER_URL}/api/packages/${GITHUB_REPOSITORY_OWNER}/composer") cat /tmp/composer-publish.log if [ "$STATUS" != "201" ] && [ "$STATUS" != "409" ]; then echo "Unexpected HTTP status $STATUS publishing to the Composer registry" @@ -102,10 +121,11 @@ jobs: fi - name: Link Composer package to this repository - if: steps.version.outputs.skip == 'false' + if: steps.meta.outputs.skip == 'false' run: | - ENCODED_NAME=$(printf '%s' "${{ steps.version.outputs.package_name }}" | sed 's#/#%2F#g') - STATUS=$(curl -s -o /dev/null -w "%{http_code}" -u "haylan:${{ secrets.RELEASE_TOKEN }}" -X POST "https://git.arthurerlich.de/api/v1/packages/haylan/composer/${ENCODED_NAME}/-/link/AeonDumpManager") + ENCODED_NAME=$(printf '%s' "${{ steps.meta.outputs.package_name }}" | sed 's#/#%2F#g') + STATUS=$(curl -s -o /dev/null -w "%{http_code}" -u "${GITHUB_REPOSITORY_OWNER}:${{ secrets.RELEASE_TOKEN }}" -X POST \ + "${GITHUB_SERVER_URL}/api/v1/packages/${GITHUB_REPOSITORY_OWNER}/composer/${ENCODED_NAME}/-/link/${GITHUB_REPOSITORY#*/}") echo "link status: $STATUS" if [ "$STATUS" != "201" ] && [ "$STATUS" != "400" ]; then echo "::warning::Unexpected status linking the Composer package to this repository (HTTP $STATUS) - not fatal, release already published" diff --git a/CHANGELOG.md b/CHANGELOG.md index d15f8cb..dde9226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.0] - 2026-10-03 + +### Changed + +- Services and routes are now configured in YAML; the plugin requires PHP `^8.2`. +- Release pipeline: the packaged `shopware-cli` zip (with prebuilt assets) is validated and used for both the Gitea Release and the Composer registry; the release is created through the Gitea API after packaging, so a failed build no longer leaves an orphan tag. +- New PR checks: `shopware-cli` validate, package build and phpunit. + +### Added + +- Dedicated `aeon_dump_manager` monolog channel; dump jobs, deletions and purges are logged. +- Extension icon and `.config/shopware-extension.yml`. + +### Removed + +- Empty plugin lifecycle overrides. + ## [0.0.5] - 2026-09-21 ### Fixed diff --git a/CLAUDE.md b/CLAUDE.md index 284ecc7..634eee2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ the full build spec live under `.scratch/admin-dump-manager/` and `.scratch/dump - Plugin bootstrap class: `src/AeonDumpManager.php` (extends `Shopware\Core\Framework\Plugin`) — this is the entry point Shopware calls for install/uninstall/activate/deactivate/update lifecycle hooks. - Plugin class name is wired via `composer.json` → `extra.shopware-plugin-class`. -- Services are registered manually in `src/Resources/config/services.xml` (Symfony DI, XML format, +- Services are registered manually in `src/Resources/config/services.yaml` (Symfony DI, YAML format, autowiring is not configured — every new service/command/task must be added here explicitly with its tag, e.g. `console.command` or `shopware.scheduled.task`). - Plugin store-config UI fields (e.g. `maxDumps`, `retantionDays`) are defined in @@ -66,7 +66,7 @@ vendor/bin/phpunit tests/SomeTest.php # single file ## Adding functionality - New console commands go in `src/Command/`, new scheduled tasks in `src/ScheduledTask/` — both must - be registered in `src/Resources/config/services.xml` with the appropriate tag or Shopware will not + be registered in `src/Resources/config/services.yaml` with the appropriate tag or Shopware will not discover them. - Scheduled tasks need a matching handler (tagged `messenger.message_handler`) to actually run; a bare `ScheduledTask` subclass like `ExampleTask` only defines the schedule, not the behavior. diff --git a/composer.json b/composer.json index 2e90da2..bd55f73 100755 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Export database dumps for Shopware 6 environments.", "type": "shopware-platform-plugin", "license": "MIT", - "version": "0.0.5", + "version": "0.1.0", "keywords": [ "shopware", "shopware6", @@ -25,7 +25,7 @@ "source": "https://git.arthurerlich.de/haylan/AeonDumpManager" }, "require": { - "php": "^8.1", + "php": "^8.2", "shopware/core": "~6.6.0" }, "require-dev": { @@ -39,8 +39,8 @@ "en-GB": "Dump Manager" }, "description": { - "de-DE": "Verwaltet Datenbank-Dumps für Shopware 6 Entwicklungsumgebungen.", - "en-GB": "Manages database dumps for Shopware 6 development environments." + "de-DE": "Für Datenbank-Backups in Shopware 6 Umgebungen erzeugt Dump Manager komprimierte SQL-Dumps per Backend oder Konsole, listet und löscht sie und räumt alte Dumps automatisch auf.", + "en-GB": "To get a database dump for Shopware 6 environments, Dump Manager creates compressed SQL dumps from the admin or console, lists and deletes them, and purges old dumps automatically." }, "manufacturerLink": { "de-DE": "https://arthurerlich.de", diff --git a/docs/spec/shopware-guideline-compliance.md b/docs/spec/shopware-guideline-compliance.md new file mode 100644 index 0000000..3603f38 --- /dev/null +++ b/docs/spec/shopware-guideline-compliance.md @@ -0,0 +1,68 @@ +# Spec: Shopware plugin guideline compliance (build/release pipeline) + +Outcome of the Wayfinder map `.scratch/shopware-guideline-compliance/` (tickets 01-05; research evidence +on branches `research/shopware-guideline-checklist`, `research/shopware-cli-ci-behaviour`, +`research/gitea-28-pipeline-standards`). Target: **Store-ready** artifacts, published to Gitea only +(Gitea Release + Composer registry). Uploading to the Shopware Store is out of scope. + +## 1. Principles + +- Workflows are **plugin-agnostic**: no plugin name, package name, owner, repo or host is hard-coded; + all derive from `composer.json` and the `gitea` context. The same files must work unchanged for a + second plugin and be extractable to Gitea's shared-workflow repository later. +- One artifact: the `shopware-cli extension package` zip (prebuilt admin/storefront JS) is used for + both the Gitea Release asset and the Composer registry. +- `shopware-cli` is intentionally **unpinned** (`latest`). Known tradeoff: a CLI release can turn PRs + red without a repo change; treat that as a signal to fix config. +- `RELEASE_TOKEN` is the only token (release, assets, registry publish, link). Workflows still declare + `permissions:`. + +## 2. Plugin changes + +| # | Change | +|---|--------| +| P1 | Commit `src/Resources/config/plugin.png` (256x256). Do not ship `plugin-transparent.png`. | +| P2 | Logging: monolog channel `aeon_dump_manager` (`Resources/config/packages/monolog.yaml`, `build()` in the plugin class loading `packages/*.yaml`); inject `monolog.logger.aeon_dump_manager` into `PurgeDumpsTaskHandler`, `DumpService`, `CreateDumpHandler`; log dump job start/finish/failure. | +| P3 | Migrate `services.xml` -> `services.yaml` and `routes.php` -> `routes.yaml`. No validation ignore for this. Update the "XML services" rule in `CLAUDE.md`. | +| P4 | `composer.json`: `php` -> `^8.2`. Verify Shopware 6.6's own PHP requirement. | +| P5 | Delete the empty lifecycle overrides in `AeonDumpManager`; keep `uninstall`. | +| P6 | `.config/shopware-extension.yml` for build excludes and validation settings (add ignores only for real, accepted findings). | +| P7 | Keep Keep-a-Changelog `CHANGELOG.md`; verify with a local `shopware-cli extension package --release` how the changelog is read. | + +## 3. Pipeline + +### PR workflows + +Jobs (all required checks on `main`): +`pr-version-check.yml` (job `version-changelog`) is unchanged apart from `permissions:`; its name keeps the existing required context. New `plugin-checks.yml`: + +1. `package` - `extension package` with `--git-commit "$GITHUB_SHA"`, `extension validate` on the resulting zip (hard gate; replaces a separate directory validate, which only repeats those checks), then `scripts/ci/assert-built-assets.sh` (built admin/storefront JS present when sources exist). All shopware-cli jobs run in `ghcr.io/shopware/shopware-cli:latest`; npm and shopware-cli caches via `actions/cache`. +2. `phpunit` - `composer create-project shopware/production` (6.6.x) + MariaDB service, copy the plugin into `custom/plugins`, install/activate, run phpunit. Runs in the same `shopware-cli` image (PHP and Composer included; `shivammathur/setup-php` hangs on this runner and apt installs cost ~4.5 min). MariaDB 10.11, because 11.4 broke Shopware's own migrations. + +### Release workflow (`.gitea/workflows/release.yml`) + +On push to `main`, guarded against re-release by an existing tag: +`extension package --git-commit "$GITHUB_SHA"` -> **validate the zip** -> assert built assets -> create the Gitea +release via API (`POST /repos/{owner}/{repo}/releases`, creates the tag) with the zip as asset -> +upload the same zip to the Composer registry -> link package to repository. +Removes the manual `git tag` step, the `akkuman` action and the `git archive` zip. Packaging happens +before the tag exists, so a failed build leaves no orphan tag. + +### Branch protection (one-time, manual) + +`scripts/protect-main.sh` using `tea api PATCH repos/{owner}/{repo}/branch_protections/main` to set the +required status-check contexts (current: `version-changelog / version-changelog (pull_request)`; add +the new PR jobs). `tea branches protect` cannot set status checks. Not run by the pipeline. + +## 4. Open items to verify while implementing + +- Release POST creates a missing tag (fallback: tag step after packaging). +- Meaning of HTTP 400 on the package-link endpoint. +- Whether bare `validate` runs PHPStan/ESLint (needs PHP/Node on the runner); Node availability on the runner image. +- Accepted size for `metadata.icon.size`; error-vs-warning rule list of validate. +- Whether `routes.php` is flagged by validate; `shopware-cli extension fix` correctness for the YAML migration. +- Exact status-check context names of the new jobs (set after the first run). + +## 5. Out of scope / later + +Shopware Store upload; Gitea shared-workflow repository; phpunit lowest-version matrix; Store listing metadata. diff --git a/scripts/ci/assert-built-assets.sh b/scripts/ci/assert-built-assets.sh new file mode 100755 index 0000000..888a5d9 --- /dev/null +++ b/scripts/ci/assert-built-assets.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Assert that a packaged extension zip contains the built admin/storefront JS when the sources exist. +# Usage: scripts/ci/assert-built-assets.sh (run from the repo root; BusyBox-safe: uses unzip -l) +set -euo pipefail +ZIP="${1:?usage: assert-built-assets.sh }" +LIST=$(unzip -l "$ZIP") + +check() { #