Compare commits

...
Author SHA1 Message Date
haylanandClaude Bot be549d4a51 feat: plugin skeleton for Shopware 6.6 and 6.7 with CI and release pipeline
- composer.json (MIT, shopware/core ~6.6.0 || ~6.7.0, PHP ^8.2), plugin class, placeholder icon
- PR checks and release workflow taken over from AeonDumpManager; phpunit runs as a 6.6/6.7 matrix
- CHANGELOG, LICENSE, README, CLAUDE.md, smoke test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:01:24 +01:00
21 changed files with 997 additions and 48 deletions

No files matched your search

+22
View File
@@ -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
- .config
- .gitea
- .claude
- .scratch
validation:
ignore: []
+35
View File
@@ -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 }}"
+195
View File
@@ -0,0 +1,195 @@
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
# The version must be higher than the latest *published release* (not just higher than main), so main can
# carry a version whose release failed or was withdrawn without blocking the next PR.
- name: Check version is unreleased and changelog entry exists
env:
TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -e
PR_VERSION=$(grep -m1 '"version"' composer.json | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')
LATEST=$(curl -sf -H "Authorization: token ${TOKEN}" "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases?limit=100" \
| jq -r '.[].tag_name' | sed 's/^v//' | sort -V | tail -n1)
LATEST="${LATEST:-0.0.0}"
echo "latest release: $LATEST"
echo "PR version: $PR_VERSION"
if [ "$PR_VERSION" = "$LATEST" ] || [ "$(printf '%s\n%s\n' "$LATEST" "$PR_VERSION" | sort -V | tail -n1)" != "$PR_VERSION" ]; then
echo "::error::composer.json version ($PR_VERSION) is not greater than the latest release ($LATEST)"
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: $PR_VERSION is unreleased 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 }}
# One run per supported Shopware minor (keep in sync with require.shopware/core in composer.json).
phpunit:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shopware: ['6.6', '6.7']
# 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 and 6.7 both support MariaDB 10.11; 11.4 broke 6.6'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
# The cache key follows the Shopware version: a new release inside the plugin's supported range changes
# the key, so the old project + database are never reused. Bump -v1 to force a refresh by hand.
- name: Resolve the latest Shopware Version
id: sw
run: |
set -e
# The matrix minor (e.g. ~6.6.0) decides which versions are acceptable;
# Composer lists the matching ones newest first, the first stable one is the latest.
CONSTRAINT="~${{ matrix.shopware }}.0"
VERSION=$(composer show shopware/core "$CONSTRAINT" --all --format=json --no-interaction 2>/dev/null \
| php -r '$d = json_decode(stream_get_contents(STDIN), true); foreach ($d["versions"] as $v) { if (preg_match("/^v?\d+(\.\d+)+$/", $v)) { echo ltrim($v, "v"); exit; } }')
test -n "$VERSION" || { echo "::error::could not resolve a Shopware version for ${CONSTRAINT}"; exit 1; }
echo "Shopware ${VERSION} (constraint ${CONSTRAINT})"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "constraint=${CONSTRAINT}" >> "$GITHUB_OUTPUT"
# Project (vendor/, phpunit, config) and the installed database as a SQL dump. Neither depends on the plugin.
- name: Restore Shopware project and database
id: shopware
uses: actions/cache/restore@v4
with:
path: |
shopware
!shopware/var/cache
!shopware/var/log
shopware-db.sql.gz
key: shopware-${{ steps.sw.outputs.version }}-installed-v1
- name: Create Shopware project
if: steps.shopware.outputs.cache-hit != 'true'
run: |
set -e
# shopware/production pins one exact core version and is not tagged for every core patch
# (e.g. core 6.6.10.31, template only up to 6.6.10.30): take the newest template inside the
# constraint, then require the resolved core version on top of it.
composer create-project "shopware/production:${{ steps.sw.outputs.constraint }}" shopware --no-interaction --no-scripts --no-install
composer require "shopware/core:${{ steps.sw.outputs.version }}" --no-update -d shopware
composer require --dev phpunit/phpunit:^10 --no-update -d shopware
composer update -W --no-interaction --no-scripts -d shopware
- name: Ensure the MariaDB client
run: |
command -v mariadb-dump >/dev/null || command -v mysqldump >/dev/null && exit 0
(apt-get update -q && apt-get install -y -q --no-install-recommends mariadb-client) || apk add --no-cache mariadb-client
- 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 install and the restore)
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");'
# One install into shopware_test; the bootstrapper then sees an installed DB, skips its own install and
# only activates the plugin (tests/TestBootstrap.php forces plugin installation).
- name: Install Shopware
if: steps.shopware.outputs.cache-hit != 'true'
working-directory: shopware
run: bin/console system:install --basic-setup --create-database --force --no-interaction
- name: Dump the installed database
if: steps.shopware.outputs.cache-hit != 'true'
run: |
set -e
DUMP=$(command -v mariadb-dump || command -v mysqldump)
"$DUMP" -h database -P 3306 -u root -proot --skip-ssl --single-transaction --routines --triggers \
--databases shopware_test | gzip > shopware-db.sql.gz
ls -l shopware-db.sql.gz
- name: Save Shopware project and database
if: steps.shopware.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
shopware
!shopware/var/cache
!shopware/var/log
shopware-db.sql.gz
key: shopware-${{ steps.sw.outputs.version }}-installed-v1
- name: Restore the database from the cache
if: steps.shopware.outputs.cache-hit == 'true'
run: |
set -e
CLIENT=$(command -v mariadb || command -v mysql)
gunzip -c shopware-db.sql.gz | "$CLIENT" -h database -P 3306 -u root -proot --skip-ssl
- 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}"
- name: Run phpunit
working-directory: shopware
run: vendor/bin/phpunit -c "custom/plugins/${PLUGIN_NAME}/phpunit.xml"
+154
View File
@@ -0,0 +1,154 @@
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.
#
# Every step is idempotent (release/asset reuse, registry 409 = already published), so a failed run can
# simply be re-run and picks up where it stopped. There is deliberately no "tag exists -> skip" guard:
# it made a half-finished release (tag + release but no registry package) impossible to heal.
on:
push:
branches: [main]
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, zip, git)
run: |
command -v jq >/dev/null && command -v curl >/dev/null && command -v unzip >/dev/null && command -v zip >/dev/null && command -v git >/dev/null && exit 0
(apt-get update -q && apt-get install -y -q --no-install-recommends jq curl unzip zip git) || apk add --no-cache jq curl unzip zip git
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_TOKEN }}
- name: Determine version, tag and file names
id: meta
run: |
set -e
VERSION=$(jq -r .version composer.json)
PACKAGE_NAME=$(jq -r .name composer.json)
REPO_NAME="${GITHUB_REPOSITORY#*/}"
TAG="v${VERSION}"
{
echo "version=${VERSION}"
echo "package_name=${PACKAGE_NAME}"
echo "tag=${TAG}"
echo "zip=${REPO_NAME}-${TAG}.zip"
} >> "$GITHUB_OUTPUT"
# Reuse the zip the PR workflow already built and tested for this exact tree.
- name: Tree hash of this commit
id: tree
run: echo "hash=$(git rev-parse "${GITHUB_SHA}^{tree}")" >> "$GITHUB_OUTPUT"
- name: Restore the tested zip from the PR run
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.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
with:
filename: ${{ steps.meta.outputs.zip }}
build: ${{ steps.prebuilt.outputs.cache-hit != 'true' }}
- name: Extract changelog section
run: |
awk -v ver="${{ steps.meta.outputs.version }}" '
BEGIN { found=0 }
/^## \[/ {
if (found) exit
if ($0 ~ "\\[" ver "\\]") { found=1; next }
}
found { print }
' CHANGELOG.md > release-notes.md
cat release-notes.md
- name: Create Gitea release (creates the tag) and upload the zip
env:
TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -e
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
TAG="${{ steps.meta.outputs.tag }}"
ZIP="${{ steps.meta.outputs.zip }}"
CODE=$(curl -s -o /tmp/release.json -w "%{http_code}" -H "Authorization: token ${TOKEN}" "${API}/releases/tags/${TAG}")
if [ "$CODE" = "200" ]; then
echo "Release ${TAG} already exists, reusing it."
else
BODY=$(jq -n --arg tag "$TAG" --arg sha "$GITHUB_SHA" --rawfile notes release-notes.md \
'{tag_name:$tag, target_commitish:$sha, name:$tag, body:$notes}')
curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -d "$BODY" "${API}/releases" > /tmp/release.json
fi
RELEASE_ID=$(jq -r .id /tmp/release.json)
echo "release id: ${RELEASE_ID}"
if jq -e --arg n "$ZIP" '.assets[]? | select(.name == $n)' /tmp/release.json > /dev/null; then
echo "Asset ${ZIP} already uploaded."
else
curl -sf -H "Authorization: token ${TOKEN}" -F "attachment=@.build/${ZIP}" \
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP}" > /dev/null
fi
# Gitea's Composer registry needs composer.json at the zip root, but shopware-cli zips wrap
# everything in one top-level folder (the registry answers 400 "composer.json file is missing").
# Same prebuilt content, repacked root-level.
- name: Repack the zip for the Composer registry
run: |
set -e
rm -rf /tmp/repack && mkdir /tmp/repack
unzip -q ".build/${{ steps.meta.outputs.zip }}" -d /tmp/repack
if [ -f /tmp/repack/composer.json ]; then
ROOT=/tmp/repack
else
ROOT=$(find /tmp/repack -mindepth 1 -maxdepth 1 -type d | head -1)
fi
test -f "$ROOT/composer.json" || { echo "::error::no composer.json found in the packaged zip"; exit 1; }
OUT="$PWD/.build/composer-${{ steps.meta.outputs.zip }}"
# explicit file list with the leading "./" stripped, so entries are stored as "composer.json", "src/..."
(cd "$ROOT" && find . -mindepth 1 | sed 's|^\./||' | zip -q "$OUT" -@)
unzip -l "$OUT" | grep -E ' composer\.json$' > /dev/null || { echo "::error::repacked zip has no root-level composer.json"; exit 1; }
echo "repacked: $OUT"
- name: Publish to the Composer registry
run: |
STATUS=$(curl -s -o /tmp/composer-publish.log -w "%{http_code}" \
-u "${GITHUB_REPOSITORY_OWNER}:${{ secrets.RELEASE_TOKEN }}" \
-T ".build/composer-${{ 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"
exit 1
fi
- name: Link Composer package to this repository
run: |
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"
fi
+33 -48
View File
@@ -1,54 +1,39 @@
# ---> Symfony
# Cache and logs (Symfony2)
/app/cache/*
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
# PHP
vendor/
composer.phar
.phpunit.result.cache
.phpunit.cache/
.php-cs-fixer.cache
# Email spool folder
/app/spool/*
# Symfony / Shopware
/var/
/public/bundles/
/public/assets/
/custom/
/src/Shopware/Platform
# Cache, session files and logs (Symfony3)
/var/cache/*
/var/logs/*
/var/sessions/*
!var/cache/.gitkeep
!var/logs/.gitkeep
!var/sessions/.gitkeep
# Frontend
*.vite/
**/public/administration/assets/
**/public/static/js/
# Logs (Symfony4)
/var/log/*
!var/log/.gitkeep
# Environment
.env
.env.local
.env.*.local
# Parameters
/app/config/parameters.yml
/app/config/parameters.ini
# Agent skills (symlinked via skills.sh, tracked through skills-lock.json)
/.agents/
/.claude/
/.qwen/
/.opencode/
/.scratch/
# Managed by Composer
/app/bootstrap.php.cache
/var/bootstrap.php.cache
/bin/*
!bin/console
!bin/symfony_requirements
/vendor/
# Assets and user uploads
/web/bundles/
/web/uploads/
# PHPUnit
/app/phpunit.xml
/phpunit.xml
# Build data
/build/
# Composer PHAR
/composer.phar
# Backup entities generated with doctrine:generate:entities command
**/Entity/*~
# Embedded web-server pid file
/.web-server-pid
# IDE
.idea/
.vscode/
*.swp
# OS
.DS_Store
Thumbs.db
+1
View File
@@ -0,0 +1 @@
@CLAUDE.md
+15
View File
@@ -0,0 +1,15 @@
# Changelog
All notable changes to AeonShopwareTools will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.0.1] - 2026-11-02
### Added
- Plugin skeleton for Shopware 6.6 and 6.7 (`shopware/core ~6.6.0 || ~6.7.0`, PHP `^8.2`).
- PR checks (version/changelog, package + validate, phpunit against the latest 6.6 and 6.7) and the release pipeline (Gitea Release + Composer registry), taken over from AeonDumpManager.
+40
View File
@@ -0,0 +1,40 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
AeonShopwareTools is a public Shopware 6 plugin (`shopware-platform-plugin`, MIT) that bundles scripting
and developer tooling. It supports **Shopware 6.6 and 6.7 from one codebase**
(`shopware/core ~6.6.0 || ~6.7.0`, PHP `^8.2`): only use APIs that exist in both, and test against both.
- Plugin bootstrap class: `src/AeonShopwareTools.php` (wired via `composer.json` → `extra.shopware-plugin-class`).
- Services, commands and scheduled tasks are registered explicitly in `src/Resources/config/services.yaml`
(create it with the first service; no autowiring), each with its tag (`console.command`,
`shopware.scheduled.task`, ...).
## Running the plugin
There is no standalone runtime. `docker-compose.yml` mounts the repo into a `dockware/shopware` container at
`custom/plugins/AeonShopwareTools`; install with `bin/console plugin:refresh` and
`bin/console plugin:install --activate AeonShopwareTools` inside the container.
## Tests
PHPUnit through Shopware's `TestBootstrapper` (`tests/TestBootstrap.php`), so tests need a real Shopware
installation and database.
```bash
vendor/bin/phpunit -c custom/plugins/AeonShopwareTools/phpunit.xml
```
## CI / release pipeline
Same Gitea pipeline as AeonDumpManager, plugin-agnostic (name/version come from `composer.json`):
- `workflows/plugin-checks.yml` (PRs into `main`): `version-changelog` (version greater than the latest
release, matching `## [x.y.z]` heading in CHANGELOG.md), `package` (`shopware-cli` package + validate,
`scripts/ci/assert-built-assets.sh`) and `phpunit`, a matrix over the latest 6.6 and 6.7. Keep the matrix
in sync with `require.shopware/core`.
- `workflows/release.yml` (push to `main`): Gitea Release with the zip, Composer registry publish. Uses `RELEASE_TOKEN`.
- Required checks on `main` are set with `scripts/protect-main.sh` (`tea api`).
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Arthur Erlich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
@CLAUDE.md
+21
View File
@@ -1,2 +1,23 @@
# AeonShopwareTools
Scripting and developer tooling for Shopware 6.6 and 6.7.
## Installation
```bash
composer require arthurerlich/aeon-shopware-tools
bin/console plugin:refresh
bin/console plugin:install --activate AeonShopwareTools
```
Or drop the release zip into `custom/plugins/` and install it from the admin.
## Compatibility
| Shopware | PHP |
|----------|--------|
| 6.6, 6.7 | ^8.2 |
## License
MIT, see [LICENSE](LICENSE). Changes are listed in [CHANGELOG.md](CHANGELOG.md).
+65
View File
@@ -0,0 +1,65 @@
{
"name": "arthurerlich/aeon-shopware-tools",
"description": "Scripting and developer tooling for Shopware 6.6 and 6.7.",
"type": "shopware-platform-plugin",
"license": "MIT",
"version": "0.0.1",
"keywords": [
"shopware",
"shopware6",
"plugin",
"tooling",
"scripting",
"developer-tools"
],
"homepage": "https://git.arthurerlich.de/haylan/AeonShopwareTools",
"authors": [
{
"name": "Arthur Erlich",
"homepage": "https://arthurerlich.de",
"role": "Developer"
}
],
"support": {
"issues": "https://git.arthurerlich.de/haylan/AeonShopwareTools/issues",
"source": "https://git.arthurerlich.de/haylan/AeonShopwareTools"
},
"require": {
"php": "^8.2",
"shopware/core": "~6.6.0 || ~6.7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"extra": {
"shopware-plugin-class": "AeonShopwareTools\\AeonShopwareTools",
"copyright": "(c) by Arthur Erlich",
"label": {
"de-DE": "Shopware Tools",
"en-GB": "Shopware Tools"
},
"description": {
"de-DE": "Für Entwickler und Betreiber von Shopware 6.6 und 6.7 bündelt Shopware Tools Skripte und Konsolenwerkzeuge, die wiederkehrende Aufgaben im Shop automatisieren.",
"en-GB": "For developers and operators of Shopware 6.6 and 6.7, Shopware Tools bundles scripts and console tooling that automate recurring maintenance tasks in the shop."
},
"manufacturerLink": {
"de-DE": "https://arthurerlich.de",
"en-GB": "https://arthurerlich.de"
},
"supportLink": {
"de-DE": "https://git.arthurerlich.de/haylan/AeonShopwareTools/issues",
"en-GB": "https://git.arthurerlich.de/haylan/AeonShopwareTools/issues"
}
},
"autoload": {
"psr-4": {
"AeonShopwareTools\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"AeonShopwareTools\\Tests\\": "tests/"
}
},
"minimum-stability": "stable"
}
+31
View File
@@ -0,0 +1,31 @@
services:
shopware:
image: dockware/shopware:6.7.14.2
container_name: shopware
ports:
- "80:80" # Shop / Admin / Adminer / Mailcatcher (all served via path on this port)
- "443:443" # HTTPS
#- "3306:3306" # MySQL (exposed for external DB clients, e.g. TablePlus/DBeaver)
- "22:22" # SSH into the container
- "8888:8888" # Admin watcher (bin/build-administration.sh --watch)
- "9998:9998" # Storefront watcher proxy
- "9999:9999" # Storefront watcher
volumes:
- "db_volume:/var/lib/mysql"
- "shop_volume:/var/www/html"
- "./:/var/www/html/custom/plugins/AeonShopwareTools"
networks:
- web
environment:
- PHP_VERSION=8.3
- XDEBUG_ENABLED=1
- SW_CURRENCY=EUR
- SSH_USER=shopware
- SSH_PWD=shopware
volumes:
db_volume:
shop_volume:
networks:
web:
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="tests/TestBootstrap.php"
executionOrder="random">
<coverage>
<include>
<directory>./src/</directory>
</include>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="Shopware\Core\Kernel"/>
<env name="APP_ENV" value="test"/>
<env name="APP_DEBUG" value="1"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="weak"/>
</php>
<testsuites>
<testsuite name="AeonShopwareTools Testsuite">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
+17
View File
@@ -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 <zip> (run from the repo root; BusyBox-safe: uses unzip -l)
set -euo pipefail
ZIP="${1:?usage: assert-built-assets.sh <zip>}"
LIST=$(unzip -l "$ZIP")
check() { # <label> <entry-glob-prefix> <expected path in zip>
local label="$1" entry="$2" expected="$3"
if [ -f "${entry}/main.js" ] || [ -f "${entry}/main.ts" ]; then
echo "$LIST" | grep -q "$expected" || { echo "::error::${label} entry exists but the zip has no built ${label} JS (${expected})"; exit 1; }
echo "ok: ${label} JS is in the zip"
fi
}
check admin src/Resources/app/administration/src "Resources/public/administration/assets/"
check storefront src/Resources/app/storefront/src "Resources/app/storefront/dist/"
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# One-time (re-runnable) setup: require the PR checks on `main` using the Gitea `tea` CLI.
# `tea branches protect` cannot set status-check contexts, hence `tea api`.
# Usage: scripts/protect-main.sh [owner/repo] (default: derived from the git remote)
set -euo pipefail
REPO="${1:-$(git config --get remote.origin.url | sed -E 's#^.*[:/]([^/]+/[^/]+)$#\1#; s#\.git$##')}"
tea api -X PATCH "repos/${REPO}/branch_protections/main" -d '{
"enable_status_check": true,
"status_check_contexts": [
"plugin-checks / version-changelog (pull_request)",
"plugin-checks / package (pull_request)",
"plugin-checks / phpunit (6.6) (pull_request)",
"plugin-checks / phpunit (6.7) (pull_request)"
]
}'
+269
View File
@@ -0,0 +1,269 @@
{
"version": 1,
"skills": {
"symfony:api-platform-dto-resources": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-dto-resources/SKILL.md",
"computedHash": "0b5a49ef3b26381320570fc65262fe60f467869af7e9f34ea4b0f48762af2512"
},
"symfony:api-platform-filters": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-filters/SKILL.md",
"computedHash": "b0a911aeb39f7e9788470356ebbab960d2a025da92452ee508e67192a387d020"
},
"symfony:api-platform-resources": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-resources/SKILL.md",
"computedHash": "5d313ce0217ef198c5543728c4d0672c48fe1e9f0f0ffe8ae89e07c5c3cf30fb"
},
"symfony:api-platform-security": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-security/SKILL.md",
"computedHash": "0c7a91b33c470450a699940c083cccea5256ea28a83b4a0acffd8575fedee4ec"
},
"symfony:api-platform-serialization": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-serialization/SKILL.md",
"computedHash": "658ead8e467c493e429e39f4ff5465ecece42fdb49a2abbba708fd42535d4223"
},
"symfony:api-platform-state-providers": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-state-providers/SKILL.md",
"computedHash": "2ef06b1eba9cc3619d3d50bb5c48f313d0d864d1cd18fef3767d5e3e705a8706"
},
"symfony:api-platform-tests": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-tests/SKILL.md",
"computedHash": "351654fffb8b216acca349ff18053f4fdfba630f1678f8827a7ab3faeea258c8"
},
"symfony:api-platform-versioning": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/api-platform-versioning/SKILL.md",
"computedHash": "0249c23a63075fb29bea006561b873f56530838505cc6c2d56c6c75b0630271a"
},
"symfony:bootstrap-check": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/bootstrap-check/SKILL.md",
"computedHash": "b0770d00e974d3077f0dbab3aaab5b644a67cce72f099a9363fd68a6e7262b43"
},
"symfony:brainstorming": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/brainstorming/SKILL.md",
"computedHash": "33d085759b6c70252df183c9589e86d1819807b371d3cbc08a7629469dc5b024"
},
"symfony:config-env-parameters": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/config-env-parameters/SKILL.md",
"computedHash": "29c30ab73cf591f6940991e1e297836499e1760d390d0fea4faffbf9bc0d42d0"
},
"symfony:controller-cleanup": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/controller-cleanup/SKILL.md",
"computedHash": "e581ac4fa9f7e59035e17a96cdb306ae6fb4c1fe2bb2ddea75fbc8b3c61b7a7f"
},
"symfony:cqrs-and-handlers": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/cqrs-and-handlers/SKILL.md",
"computedHash": "eae9fd4fb0ba0e470e460c5bd5c64064a4a06b64b52398d199c54eb58fe283bc"
},
"symfony:daily-workflow": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/daily-workflow/SKILL.md",
"computedHash": "de275687dca8eaec1c7f28f395494585a6ebb56dd2fcadfce16ccd9730db3ab2"
},
"symfony:doctrine-batch-processing": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-batch-processing/SKILL.md",
"computedHash": "1c25ad982501efe1e1284f94e89661e83eaf67e60f6e9a86e021c2c8b8c65885"
},
"symfony:doctrine-events": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-events/SKILL.md",
"computedHash": "0543f6b3a9077b53624fb034cf88a5aadc57e14b37138c82beaa13fdad155fe9"
},
"symfony:doctrine-fetch-modes": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-fetch-modes/SKILL.md",
"computedHash": "8020117b42ddc00e9165ca9cf9cd7e1e2d0cf688fb5fdd10a82b05a91bd0fe5e"
},
"symfony:doctrine-fixtures-foundry": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-fixtures-foundry/SKILL.md",
"computedHash": "dbaa95893400a1be733ff779539986ba52fdd8431e9c5298fcaad1d69c9053a7"
},
"symfony:doctrine-migrations": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-migrations/SKILL.md",
"computedHash": "f567cc35a98fe8ac6e6632d4da5990f974d4028c9517672586c42a3528861425"
},
"symfony:doctrine-relations": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-relations/SKILL.md",
"computedHash": "cecba41492de5efed7b2b0d53c3d198c79c063d5232e18e3ce7b6342cecac69d"
},
"symfony:doctrine-transactions": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/doctrine-transactions/SKILL.md",
"computedHash": "1682b0b3fa1dc53db39471751467528d132bd72842b9d9e5d4c1dcb28bfcd1a2"
},
"symfony:e2e-panther-playwright": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/e2e-panther-playwright/SKILL.md",
"computedHash": "37071992ee5e27b3c0922f509d1ff0708912ec17f9149e81f4befd2f7d246284"
},
"symfony:effective-context": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/effective-context/SKILL.md",
"computedHash": "1e5fa157148bde5a3e48b7f2c275880e915781e43667d8b621cc1acdcafd04d9"
},
"symfony:executing-plans": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/executing-plans/SKILL.md",
"computedHash": "deff91ba8c9c68e7f1b63ae9c91638ebe14e63e9503cb48303a5e1aa0edea288"
},
"symfony:form-types-validation": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/form-types-validation/SKILL.md",
"computedHash": "3c1264ac1883e8d332181128b9bf620ed32ddd443ade6c42abc35eee178dfd03"
},
"symfony:functional-tests": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/functional-tests/SKILL.md",
"computedHash": "fe487c7a86acc172e4d2d9130d04f7377f3950ce654d095f64a25829b9811435"
},
"symfony:interfaces-and-autowiring": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/interfaces-and-autowiring/SKILL.md",
"computedHash": "18efd79e2b99806aaf7e3311391191e3041fede2e347d474a3c592af5b6e43e7"
},
"symfony:messenger-retry-failures": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/messenger-retry-failures/SKILL.md",
"computedHash": "760b7115b9c1c7c4ec0042b21d91bdd6a58ba9b451452485eae71e5057332e29"
},
"symfony:ports-and-adapters": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/ports-and-adapters/SKILL.md",
"computedHash": "e426df4395f29814ab6e89b703cd961c303958c4a832a575550e563d1cf8300c"
},
"symfony:quality-checks": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/quality-checks/SKILL.md",
"computedHash": "603a2a61a4515727f2de2a93d5ef7fcc6bb24da6b3d096f58724b81f18b437a4"
},
"symfony:rate-limiting": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/rate-limiting/SKILL.md",
"computedHash": "061cdadeb1e21fb322712c9951a2dc09acc8613376faf1002951b151d7369862"
},
"symfony:runner-selection": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/runner-selection/SKILL.md",
"computedHash": "238744ad7e55f67f29e3e0de838e8d675e4cf51a58fddc2056b0bf27b43c2b5d"
},
"symfony:strategy-pattern": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/strategy-pattern/SKILL.md",
"computedHash": "fe487cd678da11650e325c8f28b29a81e84b4938cfe4e42641b68c7bb622d377"
},
"symfony:symfony-cache": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/symfony-cache/SKILL.md",
"computedHash": "92bfade421c2190100ba603b9d0d05d260594be3b4725ba98a9c8963433d7a97"
},
"symfony:symfony-messenger": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/symfony-messenger/SKILL.md",
"computedHash": "4f7d3772433a2a03d570a3957d0a84f7f3d86782b273c14a8a75dae3aceb1e4d"
},
"symfony:symfony-scheduler": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/symfony-scheduler/SKILL.md",
"computedHash": "8978979cf09c166b3a68dc6f64f47f38ce805ef654f55c3e913e41fbee502096"
},
"symfony:symfony-voters": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/symfony-voters/SKILL.md",
"computedHash": "c9e782244caedf9787041d001ba90383b02a6a0e03f4645a699f6cbc204637f6"
},
"symfony:tdd-with-pest": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/tdd-with-pest/SKILL.md",
"computedHash": "c5eb112bf920982639a4ad5b5eec8341a97c31fa6d693b94cc28eb5ed01e6667"
},
"symfony:tdd-with-phpunit": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/tdd-with-phpunit/SKILL.md",
"computedHash": "484f05f7a16bf1e161eb4d2770b693526eb156f2a7502ea5833f64f5ef83e233"
},
"symfony:test-doubles-mocking": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/test-doubles-mocking/SKILL.md",
"computedHash": "943d6a81a333d1a3a454510cf972c38181428a6d6186d1a19cfbe99ea7ffe3e1"
},
"symfony:twig-components": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/twig-components/SKILL.md",
"computedHash": "ee7c1f99558ff8d28bbbb92fe47c41abc772ac76ef512ce645dbb88956cc20e5"
},
"symfony:using-symfony-superpowers": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/using-symfony-superpowers/SKILL.md",
"computedHash": "8666a5b730f5cd8d1b66d07bfdf2afee3bdf0b41acc2a3d699a2bcff55c4151b"
},
"symfony:value-objects-and-dtos": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/value-objects-and-dtos/SKILL.md",
"computedHash": "6db6bab1d41959b7e31ca3f5793548e057765b0bd1768b3f584357f49b7a92bb"
},
"symfony:writing-plans": {
"source": "dev-toolings/superpowers-symfony",
"sourceType": "github",
"skillPath": "skills/writing-plans/SKILL.md",
"computedHash": "13387e37e27958478548530a78d130b8cc31fddc9f2903525e07ae1d3c7e80c0"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace AeonShopwareTools;
use Shopware\Core\Framework\Plugin;
class AeonShopwareTools extends Plugin
{
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

+16
View File
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace AeonShopwareTools\Tests;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\KernelTestBehaviour;
class PluginBootTest extends TestCase
{
use KernelTestBehaviour;
public function testPluginIsActiveInTheKernel(): void
{
static::assertArrayHasKey('AeonShopwareTools', static::getKernel()->getBundles());
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
use Shopware\Core\TestBootstrapper;
$loader = (new TestBootstrapper())
->addCallingPlugin()
->addActivePlugins('AeonShopwareTools')
->setForceInstallPlugins(true)
->bootstrap()
->getClassLoader();
$loader->addPsr4('AeonShopwareTools\\Tests\\', __DIR__);