From d280b14b7cab1a30eb7d582d9ae90b6bc7257098 Mon Sep 17 00:00:00 2001 From: Daniel Hobi Date: Thu, 28 May 2026 12:50:44 +0200 Subject: [PATCH] * - Add versions.php to web app which shows available library versions - Eat your own dog food by using Relay to cache packages.json --- composer.json | 5 + web/compile.sh | 7 ++ web/header.phtml | 4 + web/versions.php | 244 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 web/compile.sh create mode 100644 web/versions.php diff --git a/composer.json b/composer.json index ebf1c88..06c2e97 100644 --- a/composer.json +++ b/composer.json @@ -13,5 +13,10 @@ }, "require-dev": { "heroku/heroku-buildpack-php": "dev-main" + }, + "scripts": { + "compile": [ + "./web/compile.sh" + ] } } diff --git a/web/compile.sh b/web/compile.sh new file mode 100644 index 0000000..74d233d --- /dev/null +++ b/web/compile.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +RELAY_INI=`find $(php-config --ini-dir) -name "*-relay.ini"` + +sed -i 's/^;\? \?relay.maxmemory =.*/relay.maxmemory = 100M/' $RELAY_INI +sed -i 's/^;\? \?relay.eviction_policy =.*/relay.eviction_policy = lru/' $RELAY_INI \ No newline at end of file diff --git a/web/header.phtml b/web/header.phtml index 02c8cc6..9206813 100644 --- a/web/header.phtml +++ b/web/header.phtml @@ -35,6 +35,10 @@ +
+ Home + See all versions +
diff --git a/web/versions.php b/web/versions.php new file mode 100644 index 0000000..536775e --- /dev/null +++ b/web/versions.php @@ -0,0 +1,244 @@ +connect(getenv('UPSTASH_REDIS_REST_URL'), 6379); +$relay->auth(['default', getenv('UPSTASH_REDIS_REST_TOKEN')]); + +$table = new Relay\Table('packages'); + +function fetch_json(string $url, $table): ?array +{ + if($table->exists($url)) { + $cached = $table->get($url); + if ($cached) { + return json_decode($cached, true); + } + } + $opts = [ + 'http' => [ + 'method' => 'GET', + 'header' => "User-Agent: PHP\r\n", + 'timeout' => 10, + ], + ]; + $context = stream_context_create($opts); + $json = @file_get_contents($url, false, $context); + if ($json === false) { + error_log("Failed to fetch: $url"); + return null; + } + $table->set($url, $json); + $data = json_decode($json, true); + if (json_last_error() !== JSON_ERROR_NONE) { + error_log("JSON decode error for $url: " . json_last_error_msg()); + return null; + } + return $data; +} + +function parse_packages(array $packages_data): array +{ + $stacks = []; + if (!isset($packages_data['packages'])) { + return $stacks; + } + + $packages = $packages_data['packages']; + $package_list = []; + + // If associative array (package-name => [versions]) + $is_assoc = array_keys($packages) !== range(0, count($packages) - 1); + if ($is_assoc) { + foreach ($packages as $package_name => $versions) { + if (is_array($versions)) { + foreach ($versions as $version_info) { + $package_list[] = $version_info; + } + } + } + } else { + // Flatten lists + foreach ($packages as $item) { + if (is_array($item) && array_keys($item) === range(0, count($item) - 1)) { + foreach ($item as $sub) { + $package_list[] = $sub; + } + } else { + $package_list[] = $item; + } + } + } + + foreach ($package_list as $version_info) { + if (!is_array($version_info)) { + continue; + } + $package_name = $version_info['name'] ?? null; + if (!$package_name || strpos($package_name, 'heroku-sys/ext-') !== 0) { + continue; + } + $ext_name = substr($package_name, strlen('heroku-sys/ext-')); + $ext_version = $version_info['version'] ?? null; + if (!$ext_version) { + continue; + } + $require = $version_info['require'] ?? []; + $php_req = $require['heroku-sys/php'] ?? null; + $stack_req = $require['heroku-sys/heroku'] ?? null; + if (!$php_req || !$stack_req) { + continue; + } + + // Parse stack (e.g., ^22.0.0 -> 22 -> heroku-22) + if (preg_match('/\^?(\d+)\./', $stack_req, $m)) { + $stack = 'heroku-' . $m[1]; + } else { + continue; + } + + // Parse PHP version like 7.3.* -> 7.3 + if (preg_match('/(\d+\.\d+)\.\*/', $php_req, $m2)) { + $php_version = $m2[1]; + } else { + continue; + } + + $stacks[$stack][$php_version][$ext_name][] = $ext_version; + } + + // Deduplicate and sort versions + foreach ($stacks as $stack_name => $phps) { + foreach ($phps as $php => $exts) { + foreach ($exts as $ext => $versions) { + $unique = array_values(array_unique($versions)); + usort($unique, function ($a, $b) { + return version_compare($a, $b); + }); + $stacks[$stack_name][$php][$ext] = $unique; + } + } + } + + return $stacks; +} + +// Merge stacks from multiple manifests +$all_stacks = []; +foreach ($urls as $url) { + $data = fetch_json($url, $table); + if ($data === null) { + continue; + } + $st = parse_packages($data); + // Merge into all_stacks + foreach ($st as $stack => $phps) { + foreach ($phps as $php => $exts) { + foreach ($exts as $ext => $versions) { + if (!isset($all_stacks[$stack][$php][$ext])) { + $all_stacks[$stack][$php][$ext] = []; + } + $all_stacks[$stack][$php][$ext] = array_merge($all_stacks[$stack][$php][$ext], $versions); + } + } + } +} + +// Final dedupe and sort after merge +foreach ($all_stacks as $stack_name => $phps) { + foreach ($phps as $php => $exts) { + foreach ($exts as $ext => $versions) { + $unique = array_values(array_unique($versions)); + usort($unique, function ($a, $b) { + return version_compare($a, $b); + }); + $all_stacks[$stack_name][$php][$ext] = $unique; + } + } +} + +// Helper to sort php versions like 7.3, 8.0 numerically +function sort_php_versions(array $versions): array +{ + usort($versions, function ($a, $b) { + $pa = array_map('intval', explode('.', $a)); + $pb = array_map('intval', explode('.', $b)); + for ($i = 0; $i < max(count($pa), count($pb)); $i++) { + $va = $pa[$i] ?? 0; + $vb = $pb[$i] ?? 0; + if ($va === $vb) continue; + return $va <=> $vb; + } + return 0; + }); + return $versions; +} + +// Render HTML using site header/footer and Tailwind styling +ini_set('display_errors', 1); +error_reporting(E_ALL); + +require __DIR__ . '/header.phtml'; + +if (empty($all_stacks)) { + echo "

No data available.

\n"; + require __DIR__ . '/footer.phtml'; + exit; +} + +foreach ($all_stacks as $stack => $phps) { + // gather all extensions and php versions + $all_extensions = []; + $all_php_versions = []; + foreach ($phps as $php => $exts) { + $all_php_versions[] = $php; + foreach ($exts as $ext => $_) { + $all_extensions[$ext] = true; + } + } + $all_extensions = array_keys($all_extensions); + sort($all_extensions, SORT_NATURAL | SORT_FLAG_CASE); + $all_php_versions = array_values(array_unique($all_php_versions)); + $all_php_versions = sort_php_versions($all_php_versions); + + echo "
\n"; + echo "

" . htmlspecialchars($stack) . "

\n"; + echo "
\n"; + echo " \n"; + echo " "; + foreach ($all_extensions as $ext) { + echo ""; + } + echo "\n"; + echo " \n"; + + foreach ($all_php_versions as $php_ver) { + echo " \n"; + echo " "; + foreach ($all_extensions as $ext) { + $versions = $all_stacks[$stack][$php_ver][$ext] ?? []; + $cells = []; + foreach ($versions as $v) { + $cells[] = '' . htmlspecialchars($v) . ''; + } + echo ""; + } + echo "\n"; + } + + echo " \n"; + echo "
PHP" . htmlspecialchars($ext) . "
" . htmlspecialchars($php_ver) . "" . implode(' ', $cells) . "
\n"; + echo "
\n"; + echo "
\n"; +} + +require __DIR__ . '/footer.phtml';