feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

68
.github/workflows/code-quality.yaml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Code Quality
on:
pull_request:
push:
branches:
- 1.0
- 1.1
- 1.2
- develop
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
codeQuality:
runs-on: ubuntu-latest
name: PHP
steps:
- name: Checkout changes
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install PHP and PHP Code Sniffer
uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip
tools: phpcs
- name: Run code quality checks (on push)
if: github.event_name == 'push'
run: ./.github/workflows/utilities/phpcs-push ${{ github.sha }}
- name: Run code quality checks (on pull request)
if: github.event_name == 'pull_request'
run: ./.github/workflows/utilities/phpcs-pr ${{ github.base_ref }}
codeQualityJS:
runs-on: ubuntu-latest
name: JavaScript
steps:
- name: Checkout changes
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install Node
uses: actions/setup-node@v1
with:
node-version: 12
- name: Install Node dependencies for System Module
working-directory: ./modules/system
run: npm install
- name: Run code quality checks on System Module
working-directory: ./modules/system
run: npx eslint .
- name: Install Node dependencies for Backend Module
working-directory: ./modules/backend
run: npm install
- name: Run code quality checks on Backend Module
working-directory: ./modules/backend
run: npx eslint .

128
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,128 @@
name: Docker
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
concurrency:
group: docker-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
updateDockerImage:
name: Update Docker image
runs-on: ubuntu-latest
env:
version: ${{ github.ref_name }}
steps:
- name: Checkout changes
uses: actions/checkout@v4
with:
repository: wintercms/docker
ref: main
token: ${{ secrets.WINTER_BOT_TOKEN }}
- name: Determine latest FrankenPHP, PHP and Node versions
id: versions
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# Stay within the major versions currently used by the Dockerfile
frankenphpMajor=$(sed -n 's/^ARG FRANKENPHP_VERSION="\([0-9]*\)\..*"$/\1/p' Dockerfile)
phpMajor=$(sed -n 's/^ARG PHP_VERSION="\([0-9]*\)\..*"$/\1/p' Dockerfile)
nodeMajor=$(sed -n 's/^ARG NODE_VERSION="v\([0-9]*\)\..*"$/\1/p' Dockerfile)
# The base image is a combined FrankenPHP + PHP tag, so both versions must be resolved
# together - the latest of each individually may not exist as a published image. Walk
# back through the FrankenPHP releases until one with a Trixie image is found.
frankenphp=""
php=""
for candidate in $(gh api "repos/php/frankenphp/releases?per_page=100" \
--jq '.[] | select(.draft == false and .prerelease == false) | .tag_name' \
| sed -n "s/^v\($frankenphpMajor\.[0-9]\+\.[0-9]\+\)$/\1/p"); do
php=$(curl -fsSL "https://hub.docker.com/v2/repositories/dunglas/frankenphp/tags?page_size=100&name=$candidate-php$phpMajor" \
| jq -r '.results[].name' \
| sed -n "s/^$candidate-php\($phpMajor\.[0-9]\+\.[0-9]\+\)-trixie$/\1/p" \
| sort -V \
| tail -n 1)
if [ -n "$php" ]; then
frankenphp="$candidate"
break
fi
done
if [ -z "$frankenphp" ] || [ -z "$php" ]; then
echo "Unable to resolve a FrankenPHP $frankenphpMajor.x image for PHP $phpMajor.x" >&2
exit 1
fi
node=$(curl -fsSL "https://nodejs.org/dist/index.json" \
| jq -r --arg major "v$nodeMajor." 'map(select(.version | startswith($major))) | .[0].version // ""')
if [ -z "$node" ]; then
echo "Unable to resolve a Node $nodeMajor.x release" >&2
exit 1
fi
echo "frankenphp=$frankenphp" >> "$GITHUB_OUTPUT"
echo "php=$php" >> "$GITHUB_OUTPUT"
echo "node=$node" >> "$GITHUB_OUTPUT"
- name: Update versions
env:
frankenphp: ${{ steps.versions.outputs.frankenphp }}
php: ${{ steps.versions.outputs.php }}
node: ${{ steps.versions.outputs.node }}
run: |
set -euo pipefail
currentFrankenphp=$(sed -n 's/^ARG FRANKENPHP_VERSION="\(.*\)"$/\1/p' Dockerfile)
currentPhp=$(sed -n 's/^ARG PHP_VERSION="\(.*\)"$/\1/p' Dockerfile)
currentNode=$(sed -n 's/^ARG NODE_VERSION="\(.*\)"$/\1/p' Dockerfile)
sed -i -E "s/^ARG WINTER_VERSION=\".*\"$/ARG WINTER_VERSION=\"$version\"/" Dockerfile
sed -i -E "s/^ARG FRANKENPHP_VERSION=\".*\"$/ARG FRANKENPHP_VERSION=\"$frankenphp\"/" Dockerfile
sed -i -E "s/^ARG PHP_VERSION=\".*\"$/ARG PHP_VERSION=\"$php\"/" Dockerfile
sed -i -E "s/^ARG NODE_VERSION=\".*\"$/ARG NODE_VERSION=\"$node\"/" Dockerfile
grep -q "^ARG WINTER_VERSION=\"$version\"$" Dockerfile
grep -q "^ARG FRANKENPHP_VERSION=\"$frankenphp\"$" Dockerfile
grep -q "^ARG PHP_VERSION=\"$php\"$" Dockerfile
grep -q "^ARG NODE_VERSION=\"$node\"$" Dockerfile
# Written outside the checkout so that it is not picked up by the commit
notes="$RUNNER_TEMP/release-notes.md"
echo "- Updated Winter to version $version" > "$notes"
if [ "$currentFrankenphp" != "$frankenphp" ]; then
echo "- Updated FrankenPHP to version $frankenphp" >> "$notes"
fi
if [ "$currentPhp" != "$php" ]; then
echo "- Updated PHP to version $php" >> "$notes"
fi
if [ "$currentNode" != "$node" ]; then
echo "- Updated Node to version $node" >> "$notes"
fi
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update to Winter ${{ env.version }}
file_pattern: Dockerfile
commit_user_name: Winter Bot
commit_user_email: 80384029+WinterCMSBot@users.noreply.github.com
- name: Publish release
env:
GH_TOKEN: ${{ secrets.WINTER_BOT_TOKEN }}
run: |
gh release create "$version" \
--repo wintercms/docker \
--target main \
--title "$version" \
--notes-file "$RUNNER_TEMP/release-notes.md"

69
.github/workflows/manifest.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Manifest
on:
push:
tags:
- "*"
workflow_dispatch:
concurrency:
group: manifest-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
updateManifest:
name: Update manifest
runs-on: ubuntu-latest
env:
phpVersion: "8.4"
extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip
key: winter-cms-cache-develop
steps:
- name: Checkout changes
uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.phpVersion }}
extensions: ${{ env.extensions }}
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-scripts
- name: Download manifest
run: wget -O builds.json https://github.com/wintercms/meta/raw/master/manifest/builds.json
- name: Run manifest
run: php artisan winter:manifest builds.json
- name: Create artifact
uses: actions/upload-artifact@v4
with:
name: winter-manifest
path: builds.json
commitManifest:
name: Commit manifest
runs-on: ubuntu-latest
needs: updateManifest
steps:
- name: Checkout changes
uses: actions/checkout@v4
with:
repository: wintercms/meta
ref: master
token: ${{ secrets.WINTER_BOT_TOKEN }}
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: winter-manifest
path: manifest
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update manifest
commit_user_name: Winter Bot
commit_user_email: 80384029+WinterCMSBot@users.noreply.github.com

32
.github/workflows/subsplit.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Module sub-split
on:
push:
# Branches only. Tag pushes are handled by the `create` event below; without
# this filter they would also fire here and pass the tag name to `split -b`,
# which then fails looking for a branch that doesn't exist.
branches:
- "**"
create:
delete:
jobs:
split:
name: Sub-split
runs-on: ubuntu-latest
container: wintercms/cli:0.3.4
env:
WINTER_CLI_GITHUB_TOKEN: ${{ secrets.WINTER_SPLIT_TOKEN }}
steps:
- name: Create tag
if: github.event_name == 'create' && github.ref_type == 'tag'
run: winter split -a "${{ github.ref_name }}"
- name: Delete branch
if: github.event_name == 'delete' && github.ref_type == 'branch'
run: winter split --remove-branch="${{ github.event.ref }}"
- name: Delete tag
if: github.event_name == 'delete' && github.ref_type == 'tag'
run: winter split --remove-tag="${{ github.event.ref }}"
- name: Push
if: github.event_name == 'push'
run: winter split -b "${{ github.ref_name }}"

175
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,175 @@
name: Tests
on:
push:
branches:
- '1.2'
- develop
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
frontendTests:
strategy:
max-parallel: 2
matrix:
operatingSystem: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.operatingSystem }}
name: ${{ matrix.operatingSystem }} / JavaScript
env:
nodeVersion: 16
phpVersion: '8.2'
extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip
key: winter-cms-cache-develop
steps:
- name: Checkout changes
uses: actions/checkout@v3
- name: Setup extension cache
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ env.phpVersion }}
extensions: ${{ env.extensions }}
key: ${{ env.key }}
- name: Cache extensions
uses: actions/cache@v3
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ env.phpVersion }}
extensions: ${{ env.extensions }}
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: ${{ env.nodeVersion }}
- name: Switch library dependency (develop)
if: github.ref == 'refs/heads/develop' || github.base_ref == 'develop'
run: php ./.github/workflows/utilities/library-switcher "dev-develop as 1.2"
- name: Switch library dependency (1.2)
if: github.head_ref == '1.2' || github.ref == 'refs/heads/1.2' || github.base_ref == '1.2'
run: php ./.github/workflows/utilities/library-switcher "1.2.x-dev as 1.2"
- name: Setup dependency cache
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-scripts
- name: Reset modules
run: |
git reset --hard
git clean -fd
- name: Run post-update Composer scripts
run: php artisan package:discover
- name: Run tests
run: |
php artisan mix:install
php artisan mix:run module-system test
phpUnitTests:
strategy:
max-parallel: 8
matrix:
operatingSystem: [ubuntu-latest, windows-latest]
phpVersion: ['8.1', '8.2', '8.3', '8.4', '8.5']
fail-fast: false
runs-on: ${{ matrix.operatingSystem }}
name: ${{ matrix.operatingSystem }} / PHP ${{ matrix.phpVersion }}
env:
extensions: curl, fileinfo, gd, mbstring, openssl, pdo, pdo_sqlite, sqlite3, xml, zip
key: winter-cms-cache-develop
steps:
- name: Checkout changes
uses: actions/checkout@v3
- name: Setup extension cache
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.phpVersion }}
extensions: ${{ env.extensions }}
key: ${{ env.key }}
- name: Cache extensions
uses: actions/cache@v3
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.phpVersion }}
extensions: ${{ env.extensions }}
- name: Switch library dependency (develop)
if: github.ref == 'refs/heads/develop' || github.base_ref == 'develop'
run: php ./.github/workflows/utilities/library-switcher "dev-develop as 1.2"
- name: Switch library dependency (1.0)
if: github.head_ref == '1.0' || github.ref == 'refs/heads/1.0' || github.base_ref == '1.0'
run: php ./.github/workflows/utilities/library-switcher "1.0.x-dev as 1.0"
- name: Switch library dependency (1.1)
if: github.head_ref == '1.1' || github.ref == 'refs/heads/1.1' || github.base_ref == '1.1'
run: php ./.github/workflows/utilities/library-switcher "1.1.x-dev as 1.1"
- name: Switch library dependency (1.2)
if: github.head_ref == '1.2' || github.ref == 'refs/heads/1.2' || github.base_ref == '1.2'
run: php ./.github/workflows/utilities/library-switcher "1.2.x-dev as 1.2"
- name: Setup dependency cache
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: composer install --no-interaction --no-progress --no-scripts
- name: Reset modules
run: |
git reset --hard
git clean -fd
- name: Run post-update Composer scripts
run: php artisan package:discover
- name: Setup problem matchers for PHPUnit
if: matrix.phpVersion == '8.1'
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Run Linting and Tests
run: |
composer lint
php artisan winter:test -m system -m backend -m cms

18
.github/workflows/utilities/library-switcher vendored Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
/**
* Storm library switch for CI
*
* Switches the version of the Storm library being required through Composer. The only argument is the branch or tag
* to switch to.
*/
if (empty($argv[1])) {
echo 'You must provide a version to switch the library dependency to.';
echo "\n";
exit(1);
}
$composer = json_decode(file_get_contents(getcwd() . '/composer.json'), true);
$composer['require']['winter/storm'] = $argv[1];
file_put_contents(getcwd() . '/composer.json', json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

87
.github/workflows/utilities/phpcs-pr vendored Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env php
<?php
/**
* Run PHPCS tests against a PR.
*
* The only argument is for the PR's base branch, which is then compared to the HEAD of the PR to retrieve the list
* of changed files. The PHPCS tests are only run against these changed files, to speed up the tests.
*/
if (empty($argv[1])) {
fwrite(STDERR, 'You must provide a base branch to check this PR against.');
fwrite(STDERR, "\n");
exit(1);
}
// Get a changelist of files from Git for this PR.
$fileList = shell_exec('git diff --name-only --diff-filter=ACMR origin/' . $argv[1] . ' HEAD');
$files = array_filter(explode("\n", $fileList));
foreach ($files as &$file) {
if (strpos($file, ' ') !== false) {
$file = str_replace(' ', '\\ ', $file);
}
}
// no changes found in diff, early exit
if (!count($files)) {
fwrite(STDOUT, "\e[0;32mFound no changed files.\e[0m");
fwrite(STDOUT, "\n");
exit(0);
}
// Run all changed files through the PHPCS code sniffer and generate a CSV report
$csv = shell_exec('phpcs --colors -nq --report="csv" --extensions="php" ' . implode(' ', $files));
$lines = array_map(function ($row) {
return array_map(function ($column) {
return trim($column, '"');
}, explode(',', $row));
}, array_filter(explode("\n", $csv)));
// Remove header row
array_shift($lines);
if (!count($lines)) {
fwrite(STDOUT, "\e[0;32mFound no issues with code quality.\e[0m");
fwrite(STDOUT, "\n");
exit(0);
}
// Group errors by file
$files = [];
foreach ($lines as $line) {
$filename = str_replace(dirname(dirname(dirname(__DIR__))), '', $line[0]);
if (empty($files[$filename])) {
$files[$filename] = [];
}
$files[$filename][] = [
'warning' => (($line[3] ?? 'err') === 'warning'),
'message' => $line[4] ?? 'unknown',
'line' => $line[1] ?? '0',
];
}
// Render report
fwrite(STDERR, "\e[0;31mFound "
. ((count($lines) === 1)
? '1 issue'
: count($lines) . ' issues')
. " with code quality.\e[0m");
fwrite(STDERR, "\n");
foreach ($files as $file => $errors) {
fwrite(STDERR, "\n");
fwrite(STDERR, "\e[1;37m" . str_replace('"', '', $file) . "\e[0m");
fwrite(STDERR, "\n\n");
foreach ($errors as $error) {
fwrite(STDERR, "\e[2m" . str_pad(' L' . $error['line'], 7) . " | \e[0m");
fwrite(STDERR, $error['warning'] ? "\e[1;33mWARN:\e[0m " : "\e[0;31mERR:\e[0m ");
fwrite(STDERR, $error['message']);
fwrite(STDERR, "\n");
}
}
exit(1);

87
.github/workflows/utilities/phpcs-push vendored Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env php
<?php
/**
* Run PHPCS tests against a push.
*
* The only argument is for the commit, which a list of changed files is retrieved from. The PHPCS tests are only run
* against these changed files, to speed up the tests.
*/
if (empty($argv[1])) {
fwrite(STDERR, 'You must provide a commit SHA to check.');
fwrite(STDERR, "\n");
exit(1);
}
// Get a changelist of files from Git for this push.
$fileList = shell_exec('git show --name-only --pretty="" --diff-filter=ACMR ' . $argv[1]);
$files = array_filter(explode("\n", $fileList));
foreach ($files as &$file) {
if (strpos($file, ' ') !== false) {
$file = str_replace(' ', '\\ ', $file);
}
}
// no changes found in diff, early exit
if (!count($files)) {
fwrite(STDOUT, "\e[0;32mFound no changed files.\e[0m");
fwrite(STDOUT, "\n");
exit(0);
}
// Run all changed files through the PHPCS code sniffer and generate a CSV report
$csv = shell_exec('phpcs --colors -nq --report="csv" --extensions="php" ' . implode(' ', $files));
$lines = array_map(function ($row) {
return array_map(function ($column) {
return trim($column, '"');
}, explode(',', $row));
}, array_filter(explode("\n", $csv)));
// Remove header row
array_shift($lines);
if (!count($lines)) {
fwrite(STDOUT, "\e[0;32mFound no issues with code quality.\e[0m");
fwrite(STDOUT, "\n");
exit(0);
}
// Group errors by file
$files = [];
foreach ($lines as $line) {
$filename = str_replace(dirname(dirname(dirname(__DIR__))), '', $line[0]);
if (empty($files[$filename])) {
$files[$filename] = [];
}
$files[$filename][] = [
'warning' => (($line[3] ?? 'err') === 'warning'),
'message' => $line[4] ?? 'unknown',
'line' => $line[1] ?? '0',
];
}
// Render report
fwrite(STDERR, "\e[0;31mFound "
. ((count($lines) === 1)
? '1 issue'
: count($lines) . ' issues')
. " with code quality.\e[0m");
fwrite(STDERR, "\n");
foreach ($files as $file => $errors) {
fwrite(STDERR, "\n");
fwrite(STDERR, "\e[1;37m" . str_replace('"', '', $file) . "\e[0m");
fwrite(STDERR, "\n\n");
foreach ($errors as $error) {
fwrite(STDERR, "\e[2m" . str_pad(' L' . $error['line'], 7) . " | \e[0m");
fwrite(STDERR, $error['warning'] ? "\e[1;33mWARN:\e[0m " : "\e[0;31mERR:\e[0m ");
fwrite(STDERR, $error['message']);
fwrite(STDERR, "\n");
}
}
exit(1);