From adc8acc8dda19cc42868297070ded710406a0c4f Mon Sep 17 00:00:00 2001 From: Jonathan Danse Date: Fri, 28 Nov 2025 10:26:04 +0100 Subject: [PATCH 1/8] Fix domain assignment logic in getSFUrl method --- src/Controller/AdminThemeManagerController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Controller/AdminThemeManagerController.php b/src/Controller/AdminThemeManagerController.php index 6b3f7cb8..823f3b5d 100644 --- a/src/Controller/AdminThemeManagerController.php +++ b/src/Controller/AdminThemeManagerController.php @@ -167,10 +167,9 @@ private function buildShopUri($shop) */ private function getSFUrl($route, $entity = 'sf') { - $useDomain = version_compare(_PS_VERSION_, '9.0.0.0', '<'); - $domain = $useDomain ? '' : \Tools::getShopDomainSsl(true); + $domain = !$useDomain ? '' : \Tools::getShopDomainSsl(true); return $domain . \Link::getUrlSmarty([ 'entity' => $entity, From 7cb87871bc3ad8be9785432312c7fa14aa83cdd3 Mon Sep 17 00:00:00 2001 From: Benoit Charpentier Date: Tue, 14 Apr 2026 14:38:19 +0200 Subject: [PATCH 2/8] fix(app) : instance_id duplicates on clone button --- controllers/front/ajax.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controllers/front/ajax.php b/controllers/front/ajax.php index 984b06a6..ef499ac7 100644 --- a/controllers/front/ajax.php +++ b/controllers/front/ajax.php @@ -271,12 +271,14 @@ public function displayAjaxduplicateState() 'id_shop', 'id_lang', 'position', + 'instance_id', ]; foreach ($excludedProperties as $property) { unset($originalValues[$property]); } $newBlock = new PrettyBlocksModel(); $newBlock->hydrate($originalValues); + $newBlock->instance_id = uniqid(); $newBlock->id_shop = $idShop; $newBlock->id_lang = (int) $language; $newBlock->add(); From d3017c3e253050ee5796d298e2e0c26de4edf231 Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 14:41:03 +0200 Subject: [PATCH 3/8] Fix editor iframe URL on PrestaShop 9 --- src/Controller/AdminThemeManagerController.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Controller/AdminThemeManagerController.php b/src/Controller/AdminThemeManagerController.php index 823f3b5d..26213c06 100644 --- a/src/Controller/AdminThemeManagerController.php +++ b/src/Controller/AdminThemeManagerController.php @@ -236,7 +236,14 @@ public function indexAction() $ajax_editing_url = $this->getSFUrl('prettyblocks_get_connected_employees'); $blockAvailableUrls = $this->getSFUrl('prettyblocks_api_get_blocks_available'); $settingsUrls = $this->getSFUrl('prettyblocks_theme_settings'); - $shop_url = $context->shop->getBaseUrl(true) . $this->getLangLink($context->language->id, $context, $context->shop->id); + $shop_url = $link->getPageLink( + 'index', + true, + (int) $context->language->id, + [], + false, + (int) $context->shop->id + ); $translator = \Context::getContext()->getTranslator(); $shops = $this->getShops(); $available_language_ids = \Language::getLanguages(true, $context->shop->id); From 9e4585f2c7f5add8d102cdbcee1fe049ef9624ab Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 14:41:03 +0200 Subject: [PATCH 4/8] Add PrestaShop 8.2 and 9.1 Docker stacks --- docker/Dockerfile | 10 ++ docker/README.md | 57 +++++++++++ docker/compose.yml | 133 +++++++++++++++++++++++++ docker/scripts/install-prettyblocks.sh | 31 ++++++ docker/scripts/smoke-test.sh | 50 ++++++++++ 5 files changed, 281 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/compose.yml create mode 100755 docker/scripts/install-prettyblocks.sh create mode 100755 docker/scripts/smoke-test.sh diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..b2c85a26 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +ARG PRESTASHOP_IMAGE=prestashop/prestashop:8.2.7-8.1-apache + +FROM composer:2 AS composer + +FROM ${PRESTASHOP_IMAGE} + +COPY --from=composer /usr/bin/composer /usr/local/bin/composer +COPY scripts/install-prettyblocks.sh /tmp/init-scripts/90-install-prettyblocks.sh + +RUN chmod +x /tmp/init-scripts/90-install-prettyblocks.sh diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..9f0d2edd --- /dev/null +++ b/docker/README.md @@ -0,0 +1,57 @@ +# PrettyBlocks compatibility stacks + +This Compose project runs the current PrettyBlocks checkout against two isolated +shops: + +| Service | PrestaShop | PHP | URL | +| --- | --- | --- | --- | +| `ps82` | 8.2.7 | 8.1 | | +| `ps91` | 9.1.4 | 8.5 | | + +Both shops use MariaDB 10.11. The repository is bind-mounted into each shop, +while each stack keeps its own Composer `vendor` volume. A one-shot Node +container builds the Vite assets before the shops start, then the image init +hook installs PrettyBlocks automatically on the first boot. + +## Start + +```bash +docker compose -f docker/compose.yml up --build -d --wait +docker/scripts/smoke-test.sh +``` + +The back office is available at `/admin-dev` on both ports: + +- login: `demo@prestashop.com` +- password: `prestashop_demo` + +Use `PS82_PORT` and `PS91_PORT` to override the host ports if needed. The PS 9.1 +stack uses its recommended PHP 8.5 runtime by default. To compare both +PrestaShop cores on PHP 8.1 instead: + +```bash +PS91_IMAGE=prestashop/prestashop:9.1.4-8.1 \ + docker compose -f docker/compose.yml up --build -d --wait +``` + +After changing files in `_dev`, rebuild the editor bundle with: + +```bash +docker compose -f docker/compose.yml run --rm assets +``` + +## Logs and shell access + +```bash +docker compose -f docker/compose.yml logs -f ps82 ps91 +docker compose -f docker/compose.yml exec ps82 bash +docker compose -f docker/compose.yml exec ps91 bash +``` + +## Reset everything + +This removes both shops and databases, including all test content: + +```bash +docker compose -f docker/compose.yml down -v +``` diff --git a/docker/compose.yml b/docker/compose.yml new file mode 100644 index 00000000..688f8131 --- /dev/null +++ b/docker/compose.yml @@ -0,0 +1,133 @@ +name: prettyblocks + +x-prestashop-environment: &prestashop-environment + PS_INSTALL_AUTO: "1" + PS_LANGUAGE: fr + PS_COUNTRY: FR + PS_ALL_LANGUAGES: "0" + PS_DEV_MODE: "1" + PS_FOLDER_ADMIN: admin-dev + PS_FOLDER_INSTALL: install-dev + PS_HANDLE_DYNAMIC_DOMAIN: "0" + ADMIN_MAIL: demo@prestashop.com + ADMIN_PASSWD: prestashop_demo + DB_NAME: prestashop + DB_USER: prestashop + DB_PASSWD: prestashop + +x-database-environment: &database-environment + MARIADB_DATABASE: prestashop + MARIADB_USER: prestashop + MARIADB_PASSWORD: prestashop + MARIADB_ROOT_PASSWORD: root + MARIADB_AUTO_UPGRADE: "1" + +x-database-healthcheck: &database-healthcheck + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 20s + +x-prestashop-healthcheck: &prestashop-healthcheck + interval: 10s + timeout: 5s + retries: 30 + start_period: 5m + +services: + assets: + image: node:20-alpine + working_dir: /app/_dev + command: + - sh + - -lc + - corepack yarn install --frozen-lockfile --non-interactive && corepack yarn build + volumes: + - ..:/app + - prettyblocks-node-modules:/app/_dev/node_modules + + db82: + image: mariadb:10.11 + environment: *database-environment + command: --max-allowed-packet=64M + volumes: + - db82-data:/var/lib/mysql + healthcheck: *database-healthcheck + networks: + - net82 + + ps82: + build: + context: . + args: + PRESTASHOP_IMAGE: ${PS82_IMAGE:-prestashop/prestashop:8.2.7-8.1-apache} + depends_on: + assets: + condition: service_completed_successfully + db82: + condition: service_healthy + environment: + <<: *prestashop-environment + DB_SERVER: db82 + PS_DOMAIN: "localhost:${PS82_PORT:-8082}" + ports: + - "${PS82_PORT:-8082}:80" + volumes: + - ps82-data:/var/www/html + - ..:/var/www/html/modules/prettyblocks + - prettyblocks-vendor82:/var/www/html/modules/prettyblocks/vendor + healthcheck: + <<: *prestashop-healthcheck + test: ["CMD", "curl", "-fsS", "-H", "Host: localhost:${PS82_PORT:-8082}", "http://127.0.0.1/"] + networks: + - net82 + + db91: + image: mariadb:10.11 + environment: *database-environment + command: --max-allowed-packet=64M + volumes: + - db91-data:/var/lib/mysql + healthcheck: *database-healthcheck + networks: + - net91 + + ps91: + build: + context: . + args: + PRESTASHOP_IMAGE: ${PS91_IMAGE:-prestashop/prestashop:9.1.4-apache} + depends_on: + assets: + condition: service_completed_successfully + db91: + condition: service_healthy + environment: + <<: *prestashop-environment + DB_SERVER: db91 + PS_DOMAIN: "localhost:${PS91_PORT:-8091}" + ports: + - "${PS91_PORT:-8091}:80" + volumes: + - ps91-data:/var/www/html + - ..:/var/www/html/modules/prettyblocks + - prettyblocks-vendor91:/var/www/html/modules/prettyblocks/vendor + healthcheck: + <<: *prestashop-healthcheck + test: ["CMD", "curl", "-fsS", "-H", "Host: localhost:${PS91_PORT:-8091}", "http://127.0.0.1/"] + networks: + - net91 + +networks: + net82: + net91: + +volumes: + db82-data: + db91-data: + ps82-data: + ps91-data: + prettyblocks-vendor82: + prettyblocks-vendor91: + prettyblocks-node-modules: diff --git a/docker/scripts/install-prettyblocks.sh b/docker/scripts/install-prettyblocks.sh new file mode 100755 index 00000000..7764a42e --- /dev/null +++ b/docker/scripts/install-prettyblocks.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -eu + +module_dir=/var/www/html/modules/prettyblocks +install_marker=/var/www/html/var/.prettyblocks-installed + +echo "\n* Preparing PrettyBlocks dependencies..." +export COMPOSER_ALLOW_SUPERUSER=1 +export COMPOSER_HOME=/tmp/composer + +cd "$module_dir" +composer install \ + --no-dev \ + --no-interaction \ + --no-progress \ + --optimize-autoloader \ + --prefer-dist + +chown -R www-data:www-data "$module_dir/vendor" + +if [ ! -f "$install_marker" ]; then + echo "\n* Installing PrettyBlocks..." + cd /var/www/html + runuser -g www-data -u www-data -- \ + php -d memory_limit=-1 bin/console prestashop:module install prettyblocks --no-interaction + + touch "$install_marker" + chown www-data:www-data "$install_marker" +else + echo "\n* PrettyBlocks is already installed." +fi diff --git a/docker/scripts/smoke-test.sh b/docker/scripts/smoke-test.sh new file mode 100755 index 00000000..12083cf0 --- /dev/null +++ b/docker/scripts/smoke-test.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +project_dir=$(dirname "$(dirname "$script_dir")") +compose_file="$project_dir/docker/compose.yml" + +for service in ps82 ps91; do + docker compose -f "$compose_file" exec -T "$service" php -r ' + require "/var/www/html/config/config.inc.php"; + $module = Module::getInstanceByName("prettyblocks"); + if (!$module || !Module::isInstalled("prettyblocks") || !$module->active) { + fwrite(STDERR, "PrettyBlocks is not installed and enabled.\n"); + exit(1); + } + printf( + "PrestaShop %s | PHP %s | PrettyBlocks %s\n", + _PS_VERSION_, + PHP_VERSION, + $module->version + ); + ' + + docker compose -f "$compose_file" exec -T -e PS_DEV_MODE=0 "$service" \ + sh -lc 'php bin/console debug:router | grep -q prettyblocks' + + front_url=$(docker compose -f "$compose_file" exec -T "$service" php -r ' + require "/var/www/html/config/config.inc.php"; + $context = Context::getContext(); + echo (new Link())->getPageLink( + "index", + true, + (int) $context->language->id, + [], + false, + (int) $context->shop->id + ), PHP_EOL; + ' | tail -n 1 | tr -d '\r') + + front_page=$(curl -fsS "${front_url}?prettyblocks=1") + if printf '%s' "$front_page" | grep -Fq '[Debug] This page has moved'; then + echo "PrettyBlocks iframe URL redirects through a debug page: $front_url" >&2 + exit 1 + fi +done + +curl -fsS "http://localhost:${PS82_PORT:-8082}/" >/dev/null +curl -fsS "http://localhost:${PS91_PORT:-8091}/" >/dev/null + +echo "PrettyBlocks smoke tests passed on both PrestaShop versions." From c6b8a1e7a424fd3b049052e83c6ef96916e01696 Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 15:06:18 +0200 Subject: [PATCH 5/8] Build and test release packages locally --- .dockerignore | 20 +++ .github/workflows/build-release.yml | 94 ++++--------- .gitignore | 3 +- docker/Dockerfile | 1 + docker/README.md | 24 ++++ docker/compose.release-test.yml | 19 +++ docker/release.Dockerfile | 93 +++++++++++++ .../scripts/install-prettyblocks-release.php | 30 ++++ docker/scripts/install-prettyblocks.sh | 43 +++++- docker/scripts/smoke-test.sh | 2 +- scripts/build-release.sh | 130 ++++++++++++++++++ scripts/test-release.sh | 87 ++++++++++++ 12 files changed, 473 insertions(+), 73 deletions(-) create mode 100644 .dockerignore create mode 100644 docker/compose.release-test.yml create mode 100644 docker/release.Dockerfile create mode 100644 docker/scripts/install-prettyblocks-release.php create mode 100755 scripts/build-release.sh create mode 100755 scripts/test-release.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..389be000 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +.git +.github +.dockerignore +.php-cs-fixer.cache +.php-cs-fixer.dist.php +.vscode +.env* +build +config_*.xml +dist +docker +node_modules +scripts +vendor +work +_dev/node_modules +views/css/_dev +views/images/* +!views/images/favicon.ico +**/.DS_Store diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 1aec0e8e..dab2d9b8 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,74 +1,32 @@ -name: Build -on: [push, pull_request] +name: Build release package + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read jobs: - deploy: - name: Build dependencies & create artifact + build-release: + name: Build installable ZIP runs-on: ubuntu-latest + timeout-minutes: 20 + steps: - name: Checkout - uses: actions/checkout@v3 - - name: Install composer dependencies - run: composer install --no-dev -o - - name: Setup node env - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Install node dependencies - working-directory: ./_dev - run: npm i - - name: Build assets - working-directory: ./_dev - run: npm run build - - name: Remove unuseful data - run: | - rm .git/ -rf - rm .github/ -rf - - name: Prepare auto-index tool - run: | - composer global require prestashop/autoindex - - name: Generate index.php - run: | - ~/.composer/vendor/bin/autoindex - - name: Create & upload artifact - uses: actions/upload-artifact@v1 - with: - name: ${{ github.event.repository.name }} - path: ../ - update_release_draft: - runs-on: ubuntu-latest - needs: [deploy] - if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' - steps: - - name: Download artifact - uses: actions/download-artifact@v1 - with: - name: ${{ github.event.repository.name }} - - id: release_info - uses: toolmantim/release-drafter@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Prepare for Release - run: | - cd ${{ github.event.repository.name }} - zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }} - - name: Clean existing assets - shell: bash - run: | - curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.1 - assets=`bin/hub api -t repos/${{ github.repository }}/releases/${{ steps.release_info.outputs.id }}/assets | awk '/\].url/ { print $2 }'` - for asset in $assets - do - bin/hub api -X DELETE $asset - done - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish to GitHub Release - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/checkout@v4 + + - name: Build and validate release + run: scripts/build-release.sh + + - name: Upload release packages + uses: actions/upload-artifact@v4 with: - upload_url: ${{ steps.release_info.outputs.upload_url }} - asset_path: ./${{ github.event.repository.name }}/${{ github.event.repository.name }}.zip - asset_name: ${{ github.event.repository.name }}.zip - asset_content_type: application/zip + name: prettyblocks-${{ github.sha }} + path: dist/*.zip + if-no-files-found: error + retention-days: 14 diff --git a/.gitignore b/.gitignore index 63a42462..b13d8e30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ config_*.xml build/ +dist/ vendor/ node_modules/ **/node_modules @@ -12,4 +13,4 @@ test.php .php-cs-fixer.cache _dev/yarn.lock .env -!views/js/build \ No newline at end of file +!views/js/build diff --git a/docker/Dockerfile b/docker/Dockerfile index b2c85a26..f5451284 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,5 +6,6 @@ FROM ${PRESTASHOP_IMAGE} COPY --from=composer /usr/bin/composer /usr/local/bin/composer COPY scripts/install-prettyblocks.sh /tmp/init-scripts/90-install-prettyblocks.sh +COPY scripts/install-prettyblocks-release.php /usr/local/lib/prettyblocks/install-release.php RUN chmod +x /tmp/init-scripts/90-install-prettyblocks.sh diff --git a/docker/README.md b/docker/README.md index 9f0d2edd..c7099b2b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -55,3 +55,27 @@ This removes both shops and databases, including all test content: ```bash docker compose -f docker/compose.yml down -v ``` + +## Build and test the release ZIP + +The release builder runs Composer and Vite in Docker, creates an installable ZIP +with a top-level `prettyblocks/` directory, and validates its contents: + +```bash +scripts/build-release.sh +``` + +The versioned archive and its stable `prettyblocks.zip` alias are written to +`dist/`. To prove that the ZIP is self-contained, run it against two fresh, +temporary shops. This test disables both the source asset build and the runtime +Composer install, so only files present in the archive can be used: + +```bash +scripts/test-release.sh +``` + +The release test validates and extracts the ZIP with PrestaShop's native ZIP +handler, then installs that extracted artifact with the PrestaShop CLI. It uses +ports `8182` and `8191` by default and deletes its shops, databases, and +extracted module when it finishes. Override the ports with `PS82_RELEASE_PORT` +and `PS91_RELEASE_PORT` if needed. diff --git a/docker/compose.release-test.yml b/docker/compose.release-test.yml new file mode 100644 index 00000000..cb5f898d --- /dev/null +++ b/docker/compose.release-test.yml @@ -0,0 +1,19 @@ +services: + assets: + working_dir: /tmp + command: ["true"] + volumes: !override [] + + ps82: + environment: + PRETTYBLOCKS_RELEASE_ARCHIVE: /tmp/prettyblocks.zip + volumes: !override + - ps82-data:/var/www/html + - ${PRETTYBLOCKS_RELEASE_ARCHIVE_PATH}:/tmp/prettyblocks.zip:ro + + ps91: + environment: + PRETTYBLOCKS_RELEASE_ARCHIVE: /tmp/prettyblocks.zip + volumes: !override + - ps91-data:/var/www/html + - ${PRETTYBLOCKS_RELEASE_ARCHIVE_PATH}:/tmp/prettyblocks.zip:ro diff --git a/docker/release.Dockerfile b/docker/release.Dockerfile new file mode 100644 index 00000000..396aa22f --- /dev/null +++ b/docker/release.Dockerfile @@ -0,0 +1,93 @@ +# syntax=docker/dockerfile:1.7 + +FROM node:20-alpine AS assets + +WORKDIR /src +COPY _dev/package.json _dev/yarn.lock ./_dev/ +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \ + cd _dev \ + && corepack yarn install --frozen-lockfile --non-interactive + +COPY _dev ./_dev +RUN cd _dev && corepack yarn build + + +FROM composer:2 AS production-dependencies + +WORKDIR /src +COPY . . +RUN --mount=type=cache,target=/tmp/cache \ + COMPOSER_CACHE_DIR=/tmp/cache composer install \ + --no-dev \ + --no-interaction \ + --no-progress \ + --prefer-dist \ + --optimize-autoloader \ + --classmap-authoritative + + +FROM composer:2 AS release-tools + +WORKDIR /src +COPY . . +RUN --mount=type=cache,target=/tmp/cache \ + COMPOSER_CACHE_DIR=/tmp/cache composer install \ + --no-interaction \ + --no-progress \ + --prefer-dist + + +FROM composer:2 AS package + +ARG MODULE_VERSION + +RUN apk add --no-cache zip + +COPY --from=production-dependencies /src /stage/prettyblocks +COPY --from=assets /src/build /stage/prettyblocks/build +COPY --from=release-tools /src/vendor /tools/vendor + +RUN set -eux; \ + test -n "$MODULE_VERSION"; \ + rm -rf \ + /stage/prettyblocks/.git \ + /stage/prettyblocks/.github \ + /stage/prettyblocks/_dev \ + /stage/prettyblocks/docker \ + /stage/prettyblocks/dist \ + /stage/prettyblocks/node_modules \ + /stage/prettyblocks/scripts \ + /stage/prettyblocks/work; \ + rm -f \ + /stage/prettyblocks/.dockerignore \ + /stage/prettyblocks/.env \ + /stage/prettyblocks/.env.local \ + /stage/prettyblocks/.env.development.local \ + /stage/prettyblocks/.env.test.local \ + /stage/prettyblocks/.env.production.local \ + /stage/prettyblocks/.gitignore \ + /stage/prettyblocks/.php-cs-fixer.cache \ + /stage/prettyblocks/.php-cs-fixer.dist.php \ + /stage/prettyblocks/config.xml \ + /stage/prettyblocks/config_*.xml; \ + find /stage/prettyblocks/views/images \ + -mindepth 1 \ + -maxdepth 1 \ + -type f \ + ! -name favicon.ico \ + ! -name index.php \ + -delete; \ + /tools/vendor/bin/autoindex \ + prestashop:add:index \ + --exclude=vendor \ + --no-interaction \ + /stage/prettyblocks; \ + mkdir -p /out; \ + cd /stage; \ + zip -q -r "/out/prettyblocks-${MODULE_VERSION}.zip" prettyblocks + + +FROM scratch AS release + +ARG MODULE_VERSION +COPY --from=package /out/ / diff --git a/docker/scripts/install-prettyblocks-release.php b/docker/scripts/install-prettyblocks-release.php new file mode 100644 index 00000000..572847b7 --- /dev/null +++ b/docker/scripts/install-prettyblocks-release.php @@ -0,0 +1,30 @@ +employee = new Employee(1); + + $sourceHandler = new ZipSourceHandler(_PS_MODULE_DIR_, $context->getTranslator()); + $moduleName = $sourceHandler->getModuleName($argv[1]); + + if ('prettyblocks' !== $moduleName) { + throw new RuntimeException(sprintf('Unexpected module name in ZIP: %s', $moduleName)); + } + + $sourceHandler->handle($argv[1]); + printf("Validated and extracted %s from the release ZIP.\n", $moduleName); +} catch (Throwable $exception) { + fwrite(STDERR, $exception::class . ': ' . $exception->getMessage() . "\n"); + exit(1); +} diff --git a/docker/scripts/install-prettyblocks.sh b/docker/scripts/install-prettyblocks.sh index 7764a42e..da0c620f 100755 --- a/docker/scripts/install-prettyblocks.sh +++ b/docker/scripts/install-prettyblocks.sh @@ -4,7 +4,44 @@ set -eu module_dir=/var/www/html/modules/prettyblocks install_marker=/var/www/html/var/.prettyblocks-installed -echo "\n* Preparing PrettyBlocks dependencies..." +if [ -n "${PRETTYBLOCKS_RELEASE_ARCHIVE:-}" ]; then + if [ ! -f "$install_marker" ]; then + printf '\n* Installing the packaged PrettyBlocks release...\n' + runuser -g www-data -u www-data -- \ + php -d memory_limit=-1 \ + /usr/local/lib/prettyblocks/install-release.php \ + "$PRETTYBLOCKS_RELEASE_ARCHIVE" + + cd /var/www/html + runuser -g www-data -u www-data -- \ + php -d memory_limit=-1 \ + bin/console prestashop:module install prettyblocks --no-interaction + + # shellcheck disable=SC2016 + runuser -g www-data -u www-data -- php -r ' + require "/var/www/html/config/config.inc.php"; + $module = Module::getInstanceByName("prettyblocks"); + if (!$module || !Module::isInstalled("prettyblocks") || !$module->active) { + fwrite(STDERR, "PrettyBlocks was not installed and enabled from the ZIP.\n"); + exit(1); + } + printf( + "Installed PrettyBlocks %s from ZIP on PrestaShop %s.\n", + $module->version, + _PS_VERSION_ + ); + ' + + touch "$install_marker" + chown www-data:www-data "$install_marker" + else + printf '\n* Packaged PrettyBlocks release is already installed.\n' + fi + + exit 0 +fi + +printf '\n* Preparing PrettyBlocks dependencies...\n' export COMPOSER_ALLOW_SUPERUSER=1 export COMPOSER_HOME=/tmp/composer @@ -19,7 +56,7 @@ composer install \ chown -R www-data:www-data "$module_dir/vendor" if [ ! -f "$install_marker" ]; then - echo "\n* Installing PrettyBlocks..." + printf '\n* Installing PrettyBlocks...\n' cd /var/www/html runuser -g www-data -u www-data -- \ php -d memory_limit=-1 bin/console prestashop:module install prettyblocks --no-interaction @@ -27,5 +64,5 @@ if [ ! -f "$install_marker" ]; then touch "$install_marker" chown www-data:www-data "$install_marker" else - echo "\n* PrettyBlocks is already installed." + printf '\n* PrettyBlocks is already installed.\n' fi diff --git a/docker/scripts/smoke-test.sh b/docker/scripts/smoke-test.sh index 12083cf0..9ab68f77 100755 --- a/docker/scripts/smoke-test.sh +++ b/docker/scripts/smoke-test.sh @@ -1,7 +1,7 @@ #!/bin/sh set -eu -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) project_dir=$(dirname "$(dirname "$script_dir")") compose_file="$project_dir/docker/compose.yml" diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100755 index 00000000..eb2d697d --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +script_dir=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +project_dir=$(dirname "$script_dir") +output_dir=${1:-"$project_dir/dist"} + +version=$(awk -F"'" '/\$this->version[[:space:]]*=/{print $2; exit}' "$project_dir/prettyblocks.php") +if [[ ! $version =~ ^[0-9A-Za-z][0-9A-Za-z.+-]*$ ]]; then + echo "Unable to read a valid module version from prettyblocks.php." >&2 + exit 1 +fi + +for command_name in docker unzip; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "Required command not found: $command_name" >&2 + exit 1 + fi +done + +sha256_file() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + echo "A SHA-256 checksum command is required." >&2 + return 1 + fi +} + +temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/prettyblocks-build.XXXXXX") +trap 'rm -rf "$temporary_dir"' EXIT + +echo "Building PrettyBlocks ${version} release with Docker..." +docker build \ + --file "$project_dir/docker/release.Dockerfile" \ + --target release \ + --build-arg "MODULE_VERSION=$version" \ + --output "type=local,dest=$temporary_dir/output" \ + "$project_dir" + +archive_name="prettyblocks-${version}.zip" +built_archive="$temporary_dir/output/$archive_name" +if [[ ! -f $built_archive ]]; then + echo "Docker did not produce the expected archive: $archive_name" >&2 + exit 1 +fi + +listing="$temporary_dir/archive.list" +unzip -tq "$built_archive" +unzip -Z1 "$built_archive" > "$listing" + +required_paths=( + prettyblocks/prettyblocks.php + prettyblocks/vendor/autoload.php + prettyblocks/vendor/composer/installed.php + prettyblocks/build/.vite/manifest.json + prettyblocks/views/js/build/build.js + prettyblocks/views/css/iframe.css + prettyblocks/views/css/dist/main.css + prettyblocks/index.php +) + +for required_path in "${required_paths[@]}"; do + if ! grep -Fxq "$required_path" "$listing"; then + echo "Release archive is missing: $required_path" >&2 + exit 1 + fi +done + +if grep -Evq '^prettyblocks(/|$)' "$listing"; then + echo "Release archive contains entries outside the prettyblocks/ directory." >&2 + exit 1 +fi + +if grep -Eq '^prettyblocks/(\.git[^/]*|\.github|\.php-cs-fixer[^/]*|_dev|docker|dist|node_modules|scripts|views/css/_dev|work)(/|$)' "$listing"; then + echo "Release archive contains development-only root files." >&2 + exit 1 +fi + +if grep -Eq '(^|/)node_modules(/|$)' "$listing"; then + echo "Release archive contains Node dependencies." >&2 + exit 1 +fi + +if grep -Eq '(^|/)\.\.(/|$)' "$listing" || grep -Fq $'\\' "$listing"; then + echo "Release archive contains an unsafe path." >&2 + exit 1 +fi + +if [[ -n $(sort "$listing" | uniq -d) ]]; then + echo "Release archive contains duplicate paths." >&2 + exit 1 +fi + +if grep -Eq '^prettyblocks/\.env($|\.)' "$listing"; then + echo "Release archive contains a local environment file." >&2 + exit 1 +fi + +if grep -Eq '^prettyblocks/vendor/(prestashop/(autoindex|php-dev-tools)|friendsofphp|squizlabs)(/|$)' "$listing"; then + echo "Release archive contains development Composer dependencies." >&2 + exit 1 +fi + +if ! grep -Eq '^prettyblocks/build/assets/.+\.js$' "$listing"; then + echo "Release archive does not contain the built editor JavaScript." >&2 + exit 1 +fi + +if ! grep -Eq '^prettyblocks/build/assets/.+\.css$' "$listing"; then + echo "Release archive does not contain the built editor CSS." >&2 + exit 1 +fi + +mkdir -p "$output_dir" +cp "$built_archive" "$output_dir/$archive_name" +cp "$built_archive" "$output_dir/prettyblocks.zip" + +versioned_checksum=$(sha256_file "$output_dir/$archive_name") +stable_checksum=$(sha256_file "$output_dir/prettyblocks.zip") +if [[ $versioned_checksum != "$stable_checksum" ]]; then + echo "Versioned and stable release archives differ." >&2 + exit 1 +fi + +echo "Release ready: $output_dir/$archive_name" +echo "Stable alias: $output_dir/prettyblocks.zip" +echo "SHA-256: $versioned_checksum" diff --git a/scripts/test-release.sh b/scripts/test-release.sh new file mode 100755 index 00000000..a2d2b6b8 --- /dev/null +++ b/scripts/test-release.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +script_dir=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +project_dir=$(dirname "$script_dir") +compose_files=( + -f "$project_dir/docker/compose.yml" + -f "$project_dir/docker/compose.release-test.yml" +) + +"$script_dir/build-release.sh" + +version=$(awk -F"'" '/\$this->version[[:space:]]*=/{print $2; exit}' "$project_dir/prettyblocks.php") +export PRETTYBLOCKS_RELEASE_ARCHIVE_PATH="$project_dir/dist/prettyblocks-${version}.zip" +export COMPOSE_PROJECT_NAME="prettyblocks-release-test-${$}" +export PS82_PORT=${PS82_RELEASE_PORT:-8182} +export PS91_PORT=${PS91_RELEASE_PORT:-8191} + +cleanup() { + status=$? + if [ "$status" -ne 0 ]; then + echo "Release test failed; PrestaShop logs follow:" >&2 + docker compose "${compose_files[@]}" logs --no-color ps82 ps91 >&2 || true + fi + docker compose "${compose_files[@]}" down -v --remove-orphans >/dev/null 2>&1 || true + return "$status" +} +trap cleanup EXIT INT TERM + +echo "Installing the packaged ZIP on clean PrestaShop 8.2 and 9.1 stacks..." +docker compose "${compose_files[@]}" up \ + --build \ + --detach \ + --wait \ + --wait-timeout "${PRETTYBLOCKS_WAIT_TIMEOUT:-600}" + +"$project_dir/docker/scripts/smoke-test.sh" + +for service in ps82 ps91; do + docker compose "${compose_files[@]}" exec -T "$service" php -r ' + require "/var/www/html/modules/prettyblocks/vendor/autoload.php"; + + if (!class_exists("ScssPhp\\ScssPhp\\Compiler")) { + fwrite(STDERR, "The production SCSS dependency is missing.\n"); + exit(1); + } + + if (Composer\InstalledVersions::isInstalled("prestashop/php-dev-tools")) { + fwrite(STDERR, "A development Composer dependency leaked into the release.\n"); + exit(1); + } + ' + + docker compose "${compose_files[@]}" exec -T "$service" sh -lc \ + 'find modules/prettyblocks -type f -name "*.php" -print0 | xargs -0 -n 1 php -l >/dev/null' +done + +for service_and_port in "ps82:$PS82_PORT" "ps91:$PS91_PORT"; do + service=${service_and_port%%:*} + port=${service_and_port##*:} + + asset_paths=$(docker compose "${compose_files[@]}" exec -T "$service" php -r ' + $manifest = json_decode( + file_get_contents("modules/prettyblocks/build/.vite/manifest.json"), + true, + 512, + JSON_THROW_ON_ERROR + ); + + foreach ($manifest as $entry) { + echo $entry["file"], "\n"; + foreach ($entry["css"] ?? [] as $css) { + echo $css, "\n"; + } + } + ' | tr -d '\r') + + while IFS= read -r asset_path; do + [ -n "$asset_path" ] || continue + curl -fsS "http://localhost:${port}/modules/prettyblocks/build/${asset_path}" >/dev/null + done <<< "$asset_paths" + + curl -fsS "http://localhost:${port}/modules/prettyblocks/views/js/build/build.js" >/dev/null + curl -fsS "http://localhost:${port}/modules/prettyblocks/views/css/dist/main.css" >/dev/null +done + +echo "Packaged release passed on both clean PrestaShop stacks." From c8f9a1bfea2454db00cc857eebda7bd5e604dcff Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 15:29:19 +0200 Subject: [PATCH 6/8] Fix editor iframe loading race --- _dev/src/components/Iframe.vue | 10 +++------- _dev/src/store/pinia.js | 30 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/_dev/src/components/Iframe.vue b/_dev/src/components/Iframe.vue index db3ba71e..780b94b2 100644 --- a/_dev/src/components/Iframe.vue +++ b/_dev/src/components/Iframe.vue @@ -34,12 +34,7 @@ let currentPrettyBlocksId = computed(() => currentBlock.id_prettyblocks) * onMounted events */ onMounted(() => { - - document.addEventListener('DOMContentLoaded', () => { - setTimeout(() => { - prettyBlocksContext.setIframe() - }, 200) - }) + prettyBlocksContext.setIframe() }) watch(currentBlock, (newVal, oldValue) => { @@ -71,6 +66,7 @@ watch(prettyBlocksContext.psContext, () => { allow="geolocation; microphone; camera; midi; encrypted-media" :sandbox="iframe_sandbox" :class="[height, width, showLoader ? 'opacity-50' : '']" class="border-none h-full mx-auto rounded" :src="filteredURL" + @load="prettyBlocksContext.iframeLoaded" frameborder="0"> Chargement en cours @@ -81,4 +77,4 @@ watch(prettyBlocksContext.psContext, () => { #website-iframe { transition: all 0.5s ease; } - \ No newline at end of file + diff --git a/_dev/src/store/pinia.js b/_dev/src/store/pinia.js index 721e2aa1..bdaf68e3 100644 --- a/_dev/src/store/pinia.js +++ b/_dev/src/store/pinia.js @@ -123,8 +123,8 @@ export const usePrettyBlocksContext = defineStore('prettyBlocksContext', { setIframe() { this.$patch((state) => { state.iframe.domElement = ref(document.getElementById('website-iframe')) - this.listenIframe() }) + this.listenIframe() }, displaySettingsPanel() { this.$patch((state) => { @@ -157,16 +157,24 @@ export const usePrettyBlocksContext = defineStore('prettyBlocksContext', { }) }, listenIframe() { + window.removeEventListener("message", eventHandler); window.addEventListener("message", eventHandler); - this.iframe.domElement.addEventListener('load', (e) => { - setTimeout(() => { - this.sendPrettyBlocksEvents('initIframe') - this.sendPrettyBlocksEvents('getContext') - this.sendPrettyBlocksEvents('getZones') - this.hideLoader() - this.emit('iframeLoaded') - },100) - }) + }, + iframeLoaded(event) { + if (!this.iframe.domElement && event && event.currentTarget) { + this.$patch((state) => { + state.iframe.domElement = ref(event.currentTarget) + }) + this.listenIframe() + } + + setTimeout(() => { + this.sendPrettyBlocksEvents('initIframe') + this.sendPrettyBlocksEvents('getContext') + this.sendPrettyBlocksEvents('getZones') + this.hideLoader() + this.emit('iframeLoaded') + },100) }, sendPrettyBlocksEvents(eventType, data = []) { @@ -440,4 +448,4 @@ export const storedBlocks = defineStore('storedBlocks', { } -}) \ No newline at end of file +}) From bb5faf1006ef7c8275b7cbb24518e3e1123f50a9 Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 16:12:27 +0200 Subject: [PATCH 7/8] Bump module version to 3.2.1 --- prettyblocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prettyblocks.php b/prettyblocks.php index 8f45520f..1f43a669 100755 --- a/prettyblocks.php +++ b/prettyblocks.php @@ -73,7 +73,7 @@ public function __construct() { $this->name = 'prettyblocks'; $this->tab = 'administration'; - $this->version = '3.2.0'; + $this->version = '3.2.1'; $this->author = 'PrestaSafe'; $this->need_instance = 1; $this->js_path = $this->_path . 'views/js/'; From 6b9cafaa41fd71f6635bf5543ebdfadc3aad1cb4 Mon Sep 17 00:00:00 2001 From: Prestasafe Date: Fri, 10 Jul 2026 16:14:52 +0200 Subject: [PATCH 8/8] Use Node 24 GitHub Actions --- .github/workflows/build-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index dab2d9b8..4d40d240 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -18,13 +18,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Build and validate release run: scripts/build-release.sh - name: Upload release packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: prettyblocks-${{ github.sha }} path: dist/*.zip