Uploads werden weiterhin nach WebP umgewandelt, jetzt in vier statt drei Breiten (480/960/1600/2400) und ohne Hochskalieren. Hintergrundbilder waehlen ihre Variante je Geraet, weil CSS kein srcset kennt. Dabei kam ein Fehler heraus, den erst der Browsertest zeigte: der Dokumentpruefer erlaubte drei sources je Bild, also war mit der vierten Variante jedes Dokument mit einem Bild ungueltig - ohne sichtbare Meldung. Die Grenze haengt jetzt an MediaLibrary::WIDTHS. Geteiltes Hosting hat meistens keinen Composer. scripts/build-release.php baut deshalb auf einem Build-Rechner ein Archiv mit fertigem vendor/ ohne Entwicklungspakete, samt SHA-256. Der Updater bricht ab, wenn ein Update composer.lock aendert und Composer auf dem Server nicht aufrufbar ist, statt mit einem unpassenden vendor/ weiterzulaufen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
5.4 KiB
PHP
141 lines
5.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Builds a release archive that runs on hosting without Composer.
|
|
*
|
|
* Umut is right: shared hosting usually has no Composer. A release therefore has to carry
|
|
* `vendor/` with it, and that folder has to be built by somebody - here, on a machine that does
|
|
* have Composer. The archive is what `install.php` downloads and what a host without Composer
|
|
* unpacks; the Git repository stays free of `vendor/`.
|
|
*
|
|
* Run: php scripts/build-release.php [--out=var/releases]
|
|
*
|
|
* The script refuses to build rather than produce something misleading: without Composer there is
|
|
* no way to get a dependency tree without development packages, and shipping the development one
|
|
* would mean an archive with PHPUnit in it.
|
|
*/
|
|
$root = dirname(__DIR__);
|
|
$options = getopt('', ['out::', 'keep-dev']);
|
|
$outDirectory = $root.'/'.trim($options['out'] ?? 'var/releases', '/');
|
|
$version = trim((string) file_get_contents($root.'/VERSION'));
|
|
if (1 !== preg_match('/^\d+\.\d+\.\d+$/D', $version)) {
|
|
fwrite(STDERR, "VERSION enthält keine Fassung der Form 1.2.3.\n");
|
|
exit(1);
|
|
}
|
|
|
|
/** Runs a command and returns [ok, output]; the shell is used because this is a build tool. */
|
|
$run = static function (string $command) use ($root): array {
|
|
$output = [];
|
|
$status = 0;
|
|
exec('cd '.escapeshellarg($root).' && '.$command.' 2>&1', $output, $status);
|
|
|
|
return [0 === $status, implode("\n", $output)];
|
|
};
|
|
|
|
// Composer, in the order that works on a developer machine.
|
|
$composer = null;
|
|
foreach (['php '.escapeshellarg($root.'/composer.phar'), 'composer'] as $candidate) {
|
|
[$ok] = $run($candidate.' --version');
|
|
if ($ok) {
|
|
$composer = $candidate;
|
|
break;
|
|
}
|
|
}
|
|
if (null === $composer) {
|
|
fwrite(STDERR, "Composer ist auf diesem Rechner nicht aufrufbar.\n");
|
|
fwrite(STDERR, "Ein Release wird auf einem Build-Rechner gebaut, nicht auf dem Hosting - genau deshalb gibt es dieses Skript.\n");
|
|
exit(1);
|
|
}
|
|
|
|
$staging = $outDirectory.'/kantsites-'.$version;
|
|
$archive = $outDirectory.'/kantsites-'.$version.'.zip';
|
|
if (is_dir($staging) || is_file($archive)) {
|
|
fwrite(STDERR, sprintf("Für Fassung %s gibt es schon ein Release. Erst entfernen oder VERSION erhöhen.\n", $version));
|
|
exit(1);
|
|
}
|
|
if (!is_dir($outDirectory) && !mkdir($outDirectory, 0o775, true)) {
|
|
fwrite(STDERR, sprintf("%s ließ sich nicht anlegen.\n", $outDirectory));
|
|
exit(1);
|
|
}
|
|
|
|
echo "Fassung {$version}\n";
|
|
|
|
// What belongs into a release: the application, and nothing that only a developer needs.
|
|
$include = ['bin', 'config', 'migrations', 'public', 'src', 'templates', 'composer.json', 'composer.lock', 'VERSION', 'README.md', 'LICENSE', '.env.example'];
|
|
$skip = ['.git', '.github', 'node_modules', 'var', 'tests', 'scripts', 'reports', 'phpunit.xml.dist', '.php-cs-fixer.dist.php', 'package.json', 'package-lock.json'];
|
|
|
|
$copy = static function (string $from, string $to) use (&$copy, $skip): int {
|
|
$count = 0;
|
|
if (is_file($from)) {
|
|
@mkdir(dirname($to), 0o775, true);
|
|
|
|
return @copy($from, $to) ? 1 : 0;
|
|
}
|
|
foreach (scandir($from) ?: [] as $entry) {
|
|
if ('.' === $entry || '..' === $entry || in_array($entry, $skip, true)) {
|
|
continue;
|
|
}
|
|
$count += $copy($from.'/'.$entry, $to.'/'.$entry);
|
|
}
|
|
|
|
return $count;
|
|
};
|
|
|
|
$files = 0;
|
|
foreach ($include as $entry) {
|
|
if (file_exists($root.'/'.$entry)) {
|
|
$files += $copy($root.'/'.$entry, $staging.'/'.$entry);
|
|
}
|
|
}
|
|
echo "Quelldateien: {$files}\n";
|
|
|
|
// The dependencies, without the development ones - that is the whole point of building here.
|
|
$flags = isset($options['keep-dev']) ? '' : ' --no-dev';
|
|
[$ok, $output] = $run(sprintf(
|
|
'%s install%s --optimize-autoloader --no-interaction --no-progress --working-dir=%s',
|
|
$composer,
|
|
$flags,
|
|
escapeshellarg($staging),
|
|
));
|
|
if (!$ok) {
|
|
fwrite(STDERR, "composer install ist gescheitert:\n".$output."\n");
|
|
exit(1);
|
|
}
|
|
echo "vendor/ gebaut".($flags ? ' ohne Entwicklungspakete' : ' MIT Entwicklungspaketen')."\n";
|
|
|
|
// The environment file is not part of a release: it holds the secret of one installation.
|
|
foreach (['.env.local', '.env.prod.local'] as $secret) {
|
|
if (is_file($staging.'/'.$secret)) {
|
|
unlink($staging.'/'.$secret);
|
|
}
|
|
}
|
|
|
|
if (!class_exists(ZipArchive::class)) {
|
|
fwrite(STDERR, "Die PHP-Erweiterung zip fehlt; ohne sie lässt sich kein Archiv schreiben.\n");
|
|
exit(1);
|
|
}
|
|
$zip = new ZipArchive();
|
|
if (true !== $zip->open($archive, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
|
|
fwrite(STDERR, "Archiv ließ sich nicht anlegen.\n");
|
|
exit(1);
|
|
}
|
|
$entries = 0;
|
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($staging, FilesystemIterator::SKIP_DOTS));
|
|
foreach ($iterator as $file) {
|
|
if ($file->isFile()) {
|
|
// Paths inside the archive start with the version folder, so unpacking never scatters
|
|
// files across an existing installation.
|
|
$zip->addFile($file->getPathname(), 'kantsites-'.$version.'/'.str_replace('\\', '/', substr($file->getPathname(), strlen($staging) + 1)));
|
|
++$entries;
|
|
}
|
|
}
|
|
$zip->close();
|
|
|
|
$hash = hash_file('sha256', $archive);
|
|
file_put_contents($archive.'.sha256', $hash.' '.basename($archive)."\n");
|
|
|
|
printf("\nArchiv: %s\n", $archive);
|
|
printf("Dateien: %d, Größe: %.1f MB\n", $entries, filesize($archive) / 1048576);
|
|
printf("SHA-256: %s\n", $hash);
|
|
echo "\nDie Prüfsumme gehört in install.php, damit ein manipuliertes Archiv nicht entpackt wird.\n";
|