Initial commit

This commit is contained in:
gustavooth
2026-07-23 22:56:30 -03:00
commit 22a1006598
1816 changed files with 410742 additions and 0 deletions
@@ -0,0 +1,37 @@
# Emogrifier Scoped Dependency Build
This directory is the source of truth for rebuilding the scoped `symfony/css-selector` bundle used by:
- `app/Services/Libs/Emogrifier/scoped-vendor/symfony/css-selector`
## Why this exists
A direct vendor snapshot can drift to PHP-8-only packages. This build workspace pins a PHP-7.4-compatible dependency set and regenerates the scoped bundle deterministically.
## Rebuild command
Run from repository root:
```bash
bash app/Services/Libs/Emogrifier/build/rebuild_scoped_css_selector.sh
```
The rebuild script will:
1. Install locked dependencies from `build/composer.lock`.
2. Copy `symfony/css-selector` into `scoped-vendor/symfony/css-selector`.
3. Prefix namespaces to `FluentEmogrifier\\Vendor\\Symfony\\Component\\CssSelector`.
4. Replace `str_contains()` usage with a local compatibility helper.
5. Update scoped composer metadata (`installed.json`, `installed.php`, `platform_check.php`).
## Updating css-selector
1. Edit `build/composer.json` version constraints.
2. Run:
```bash
composer update symfony/css-selector --working-dir app/Services/Libs/Emogrifier/build
bash app/Services/Libs/Emogrifier/build/rebuild_scoped_css_selector.sh
```
3. Verify with PHP 7.4 and 8.x before release.
@@ -0,0 +1,16 @@
{
"name": "fluentcrm/emogrifier-build",
"type": "project",
"description": "Build workspace for FluentCRM Emogrifier scoped dependencies",
"require": {
"symfony/css-selector": "5.4.45"
},
"config": {
"platform": {
"php": "7.4.33"
},
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
composer install --no-dev --prefer-dist --no-interaction --working-dir "$SCRIPT_DIR"
php "$SCRIPT_DIR/sync_css_selector.php"
@@ -0,0 +1,284 @@
<?php
declare(strict_types=1);
const CSS_SELECTOR_PACKAGE = 'symfony/css-selector';
$buildDir = __DIR__;
$emogrifierDir = dirname(__DIR__);
$scopedVendorDir = $emogrifierDir . '/scoped-vendor';
$sourceCssSelectorDir = $buildDir . '/vendor/symfony/css-selector';
$targetCssSelectorDir = $scopedVendorDir . '/symfony/css-selector';
$installedJsonPath = $scopedVendorDir . '/composer/installed.json';
$installedPhpPath = $scopedVendorDir . '/composer/installed.php';
$platformCheckPath = $scopedVendorDir . '/composer/platform_check.php';
$buildLockPath = $buildDir . '/composer.lock';
if (!is_dir($sourceCssSelectorDir)) {
fwrite(STDERR, "Missing {$sourceCssSelectorDir}. Run composer install in build directory first.\n");
exit(1);
}
if (!file_exists($buildLockPath)) {
fwrite(STDERR, "Missing composer.lock in build directory.\n");
exit(1);
}
$lock = json_decode((string) file_get_contents($buildLockPath), true);
if (!is_array($lock)) {
fwrite(STDERR, "Could not parse build composer.lock.\n");
exit(1);
}
$package = null;
foreach (($lock['packages'] ?? []) as $candidate) {
if (($candidate['name'] ?? '') === CSS_SELECTOR_PACKAGE) {
$package = $candidate;
break;
}
}
if (!$package) {
fwrite(STDERR, "Could not find " . CSS_SELECTOR_PACKAGE . " in build composer.lock.\n");
exit(1);
}
$version = (string) ($package['version'] ?? '');
$reference = (string) ($package['source']['reference'] ?? '');
$requirePhp = (string) ($package['require']['php'] ?? '>=7.3.0');
$supportSource = (string) ($package['support']['source'] ?? '');
$distUrl = (string) ($package['dist']['url'] ?? '');
if (!$version || !$reference) {
fwrite(STDERR, "Missing version/reference in build lock for " . CSS_SELECTOR_PACKAGE . ".\n");
exit(1);
}
$normalizedVersion = preg_replace('/^v/', '', $version) . '.0';
if (!$normalizedVersion) {
fwrite(STDERR, "Failed to compute normalized version for {$version}.\n");
exit(1);
}
rrmdir($targetCssSelectorDir);
mkdirOrFail(dirname($targetCssSelectorDir));
copyDir($sourceCssSelectorDir, $targetCssSelectorDir);
removeIfExists($targetCssSelectorDir . '/README.md');
removeIfExists($targetCssSelectorDir . '/CHANGELOG.md');
removeIfExists($targetCssSelectorDir . '/LICENSE');
removeIfExists($targetCssSelectorDir . '/composer.json');
$phpFiles = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($targetCssSelectorDir, FilesystemIterator::SKIP_DOTS)
);
foreach ($phpFiles as $file) {
/** @var SplFileInfo $file */
if ($file->getExtension() !== 'php') {
continue;
}
$path = $file->getPathname();
$content = (string) file_get_contents($path);
$content = str_replace(
'namespace Symfony\\Component\\CssSelector',
'namespace FluentEmogrifier\\Vendor\\Symfony\\Component\\CssSelector',
$content
);
$content = str_replace(
'use Symfony\\Component\\CssSelector\\',
'use FluentEmogrifier\\Vendor\\Symfony\\Component\\CssSelector\\',
$content
);
$content = preg_replace(
'/(?<![A-Za-z0-9_\\\\])str_contains\s*\(/',
'\\FluentEmogrifier\\Vendor\\Symfony\\Component\\CssSelector\\Util\\Php74Compat::strContains(',
$content
);
file_put_contents($path, (string) $content);
}
$compatDir = $targetCssSelectorDir . '/Util';
mkdirOrFail($compatDir);
file_put_contents($compatDir . '/Php74Compat.php', php74CompatClass());
updateInstalledJson($installedJsonPath, $version, $normalizedVersion, $reference, $requirePhp, $supportSource, $distUrl);
updateInstalledPhp($installedPhpPath, $version, $normalizedVersion, $reference);
updatePlatformCheck($platformCheckPath);
echo "Synced css-selector {$version} ({$reference}) into scoped-vendor.\n";
function mkdirOrFail(string $path): void
{
if (is_dir($path)) {
return;
}
if (!mkdir($path, 0777, true) && !is_dir($path)) {
throw new RuntimeException("Failed to create directory: {$path}");
}
}
function rrmdir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
/** @var SplFileInfo $item */
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}
rmdir($dir);
}
function copyDir(string $source, string $target): void
{
mkdirOrFail($target);
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($items as $item) {
/** @var SplFileInfo $item */
$relative = substr($item->getPathname(), strlen($source) + 1);
$destination = $target . '/' . $relative;
if ($item->isDir()) {
mkdirOrFail($destination);
continue;
}
mkdirOrFail(dirname($destination));
copy($item->getPathname(), $destination);
}
}
function removeIfExists(string $path): void
{
if (file_exists($path)) {
unlink($path);
}
}
function php74CompatClass(): string
{
return <<<'PHP'
<?php
namespace FluentEmogrifier\Vendor\Symfony\Component\CssSelector\Util;
/**
* Minimal compatibility helpers for keeping scoped css-selector PHP 7.4-safe.
*/
final class Php74Compat
{
public static function strContains(string $haystack, string $needle): bool
{
if ($needle === '') {
return true;
}
return strpos($haystack, $needle) !== false;
}
}
PHP;
}
function updateInstalledJson(
string $path,
string $version,
string $normalizedVersion,
string $reference,
string $requirePhp,
string $supportSource,
string $distUrl
): void {
$json = json_decode((string) file_get_contents($path), true);
if (!is_array($json) || !isset($json['packages']) || !is_array($json['packages'])) {
throw new RuntimeException("Could not parse installed.json: {$path}");
}
foreach ($json['packages'] as &$package) {
if (($package['name'] ?? '') !== CSS_SELECTOR_PACKAGE) {
continue;
}
$package['version'] = $version;
$package['version_normalized'] = $normalizedVersion;
if (isset($package['source']) && is_array($package['source'])) {
$package['source']['reference'] = $reference;
}
if (isset($package['dist']) && is_array($package['dist'])) {
$package['dist']['reference'] = $reference;
if ($distUrl) {
$package['dist']['url'] = $distUrl;
}
}
if (!isset($package['require']) || !is_array($package['require'])) {
$package['require'] = [];
}
$package['require']['php'] = $requirePhp;
if ($supportSource) {
if (!isset($package['support']) || !is_array($package['support'])) {
$package['support'] = [];
}
$package['support']['source'] = $supportSource;
}
break;
}
unset($package);
file_put_contents(
$path,
json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL
);
}
function updateInstalledPhp(string $path, string $version, string $normalizedVersion, string $reference): void
{
$content = (string) file_get_contents($path);
$content = preg_replace_callback(
"/'symfony\\/css-selector' => array\\([\\s\\S]*?\\n\\s*\\),/",
function (array $matches) use ($version, $normalizedVersion, $reference): string {
$block = $matches[0];
$block = preg_replace("/'pretty_version' => '[^']+'/", "'pretty_version' => '{$version}'", $block, 1);
$block = preg_replace("/'version' => '[^']+'/", "'version' => '{$normalizedVersion}'", $block, 1);
$block = preg_replace("/'reference' => '[^']+'/", "'reference' => '{$reference}'", $block, 1);
return (string) $block;
},
$content,
1
);
file_put_contents($path, $content);
}
function updatePlatformCheck(string $path): void
{
$content = (string) file_get_contents($path);
$content = preg_replace('/PHP_VERSION_ID >= \d+/', 'PHP_VERSION_ID >= 70300', $content);
$content = preg_replace('/>= [0-9]+\.[0-9]+\.[0-9]+/', '>= 7.3.0', $content);
file_put_contents($path, $content);
}