diff --git a/.config/shopware-extension.yml b/.config/shopware-extension.yml new file mode 100644 index 0000000..8daab5d --- /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 + - .config + - .gitea + - .claude + - .scratch +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..779a0ce --- /dev/null +++ b/.gitea/workflows/plugin-checks.yml @@ -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" diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..7d8bcda --- /dev/null +++ b/.gitea/workflows/release.yml @@ -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 diff --git a/.gitignore b/.gitignore index 13ff59d..66bf08c 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a6fce0a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +@CLAUDE.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f4fd31c --- /dev/null +++ b/CHANGELOG.md @@ -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. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..68c33c4 --- /dev/null +++ b/CLAUDE.md @@ -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`). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1af5d86 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/QWEN.md b/QWEN.md new file mode 100644 index 0000000..a6fce0a --- /dev/null +++ b/QWEN.md @@ -0,0 +1 @@ +@CLAUDE.md \ No newline at end of file diff --git a/README.md b/README.md index ff8bf79..1e40d34 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7f7ac43 --- /dev/null +++ b/composer.json @@ -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" +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f148ac3 --- /dev/null +++ b/docker-compose.yml @@ -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: \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..1e8cb61 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + ./src/ + + + + + + + + + + + + tests + + + diff --git a/scripts/ci/assert-built-assets.sh b/scripts/ci/assert-built-assets.sh new file mode 100644 index 0000000..9baba7a --- /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() { #