Bisher musste ein Agent, der eine Anbindung aktualisiert, die gesamte Historie lesen - oder er las gar nichts und uebersah eine brechende Aenderung. Beides schlecht. - public/docs/changelog.json ist die einzige Quelle. Je Fassung eine Zusammenfassung, je Aenderung Bereich, ein "breaking"-Kennzeichen und vor allem ein Feld "action" mit dem, was konkret zu tun ist. Steht dort null, ist nichts zu tun - das ist die haeufigste und nuetzlichste Antwort. - GET /api/updateservice/v1/changelog?since=2.2.0 liefert nur die neueren Fassungen, dazu die Anzahl der Punkte mit Handlungsbedarf und der brechenden Aenderungen. count:0 heisst "du bist auf Stand" - dann muss gar nichts gelesen werden. Optional nach Bereich filterbar (?area=packager). - /docs/changelog.php rendert dieselbe Datei fuer Menschen, mit Eingabefeld fuer die eigene Fassung. Bewusst dieselbe Quelle: zwei Fassungen zu pflegen hiesse, sie auseinanderlaufen zu lassen. - DeploymentcenterSdk.Version im SDK ist der Bezugspunkt. Damit muss die Fassung nicht abgetippt werden. - AGENT_PROMPT_TEMPLATE.md verpflichtet dazu, sie in der AGENTS.md des Projekts festzuhalten und vor jeder Aenderung an der Anbindung den Unterschied abzufragen. Auch in der Kurzfassung fuer knappe Prompt-Budgets. Die Historie ist rueckwirkend bis 2.0.0 gefuellt: 6 Fassungen, 25 Punkte mit Handlungsbedarf, 13 brechende Aenderungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
213 lines
7.9 KiB
PHP
213 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Lesbare Ansicht des Changelogs.
|
|
*
|
|
* Quelle ist changelog.json - dieselbe Datei, die
|
|
* GET /api/updateservice/v1/changelog ausliefert. Zwei Fassungen zu pflegen
|
|
* hiesse, sie frueher oder spaeter auseinanderlaufen zu lassen.
|
|
*
|
|
* /docs/changelog.php alles
|
|
* /docs/changelog.php?since=2.2.0 nur was seitdem kam
|
|
* /docs/changelog.php?raw die rohe JSON-Datei
|
|
*/
|
|
|
|
$path = __DIR__ . '/changelog.json';
|
|
$raw = is_file($path) ? (string)file_get_contents($path) : '';
|
|
$raw = preg_replace('/^\xEF\xBB\xBF/', '', $raw) ?? $raw;
|
|
|
|
if (isset($_GET['raw'])) {
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo $raw;
|
|
exit;
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
// Auch bei kaputter Datei ein Array - sonst greift die Ausgabe unten auf null zu.
|
|
$data = is_array($decoded) ? $decoded : [];
|
|
$versions = isset($data['versions']) && is_array($data['versions'])
|
|
? $data['versions']
|
|
: [];
|
|
|
|
$since = isset($_GET['since']) ? trim((string)$_GET['since']) : '';
|
|
|
|
/** Semantischer Vergleich - dieselbe Ordnung wie serverseitig. */
|
|
function cl_core(string $v): array
|
|
{
|
|
$v = ltrim(trim($v), 'vV');
|
|
$plus = strpos($v, '+');
|
|
if ($plus !== false) { $v = substr($v, 0, $plus); }
|
|
$dash = strpos($v, '-');
|
|
if ($dash !== false) { $v = substr($v, 0, $dash); }
|
|
|
|
$out = [];
|
|
foreach (explode('.', $v) as $part) {
|
|
$out[] = (int)preg_replace('/\D/', '', $part);
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function cl_newer(string $candidate, string $than): bool
|
|
{
|
|
$a = cl_core($candidate);
|
|
$b = cl_core($than);
|
|
for ($i = 0; $i < max(count($a), count($b)); $i++) {
|
|
$x = $a[$i] ?? 0;
|
|
$y = $b[$i] ?? 0;
|
|
if ($x !== $y) { return $x > $y; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ($since !== '') {
|
|
$versions = array_values(array_filter(
|
|
$versions,
|
|
static fn(array $v): bool => cl_newer((string)($v['version'] ?? '0'), $since)
|
|
));
|
|
}
|
|
|
|
$actionItems = 0;
|
|
$breaking = 0;
|
|
foreach ($versions as $v) {
|
|
foreach ($v['changes'] ?? [] as $c) {
|
|
if (!empty($c['action'])) { $actionItems++; }
|
|
if (!empty($c['breaking'])) { $breaking++; }
|
|
}
|
|
}
|
|
|
|
function e(?string $s): string
|
|
{
|
|
return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
$areaLabels = [
|
|
'server' => 'Server',
|
|
'sdk' => 'SDK',
|
|
'agent' => 'Update-Agent',
|
|
'packager' => 'Packager',
|
|
'tooling' => 'Werkzeuge',
|
|
'docs' => 'Dokumentation',
|
|
];
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Deploymentcenter — Änderungen</title>
|
|
<style>
|
|
:root {
|
|
--bg: #0b0f19; --card: #141b2d; --accent: #5b9dff;
|
|
--text: #e2e8f0; --muted: #94a3b8; --border: #1f2937;
|
|
--warn: #fbbf24; --break: #f87171; --ok: #4ade80;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body { margin: 0; padding: 2rem 1rem; background: var(--bg); color: var(--text);
|
|
font-family: system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.6; }
|
|
.wrap { max-width: 60rem; margin: 0 auto; }
|
|
h1 { margin: 0 0 .25rem; font-size: 1.75rem; }
|
|
.lead { color: var(--muted); margin: 0 0 1.5rem; }
|
|
code { font-family: "Fira Code", ui-monospace, monospace; font-size: .85em;
|
|
background: rgba(91,157,255,.12); padding: .1rem .35rem; border-radius: 3px; }
|
|
pre code { display: block; padding: .75rem; overflow-x: auto; }
|
|
.box { background: var(--card); border: 1px solid var(--border);
|
|
border-radius: 8px; padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
|
|
.filter { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
|
|
input[type=text] { background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
|
padding: .4rem .6rem; border-radius: 5px; font-family: inherit; }
|
|
button { background: var(--accent); border: 0; color: #041225; font-weight: 600;
|
|
padding: .45rem .9rem; border-radius: 5px; cursor: pointer; font-family: inherit; }
|
|
.ver { border-left: 3px solid var(--accent); padding-left: 1rem; margin: 2rem 0; }
|
|
.ver h2 { margin: 0; font-size: 1.3rem; }
|
|
.date { color: var(--muted); font-size: .85rem; }
|
|
.summary { margin: .4rem 0 1rem; }
|
|
.chg { border-top: 1px solid var(--border); padding: .9rem 0; }
|
|
.chg:first-of-type { border-top: 0; }
|
|
.chg h3 { margin: 0 0 .35rem; font-size: 1rem; }
|
|
.tag { display: inline-block; font-size: .7rem; font-weight: 700; letter-spacing: .03em;
|
|
padding: .1rem .45rem; border-radius: 4px; margin-right: .4rem; vertical-align: middle; }
|
|
.t-area { background: rgba(148,163,184,.18); color: var(--muted); }
|
|
.t-break { background: rgba(248,113,113,.16); color: var(--break); }
|
|
.t-act { background: rgba(251,191,36,.16); color: var(--warn); }
|
|
.action { background: rgba(251,191,36,.08); border-left: 2px solid var(--warn);
|
|
padding: .5rem .75rem; margin-top: .5rem; font-size: .92rem; }
|
|
.action strong { color: var(--warn); }
|
|
.none { color: var(--muted); font-style: italic; }
|
|
a { color: var(--accent); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
|
|
<h1>Änderungen am Deploymentcenter</h1>
|
|
<p class="lead">
|
|
Aktuelle Fassung: <code><?= e($data['current'] ?? '?') ?></code>.
|
|
Trage ein, gegen welche Fassung deine Anbindung gebaut wurde — dann steht hier
|
|
nur, was seitdem dazugekommen ist.
|
|
</p>
|
|
|
|
<div class="box">
|
|
<form method="get" class="filter">
|
|
<label for="since">Meine Fassung:</label>
|
|
<input type="text" id="since" name="since" value="<?= e($since) ?>" placeholder="2.2.0" size="10">
|
|
<button type="submit">Unterschied zeigen</button>
|
|
<?php if ($since !== ''): ?>
|
|
<a href="changelog.php" style="margin-left:.5rem;">alles zeigen</a>
|
|
<?php endif; ?>
|
|
</form>
|
|
<p style="margin:.75rem 0 0; color:var(--muted); font-size:.9rem;">
|
|
Maschinenlesbar: <code>GET /api/updateservice/v1/changelog?since=<?= e($since !== '' ? $since : '2.2.0') ?></code>
|
|
· <a href="changelog.php?raw">rohes JSON</a>
|
|
</p>
|
|
</div>
|
|
|
|
<?php if ($versions === []): ?>
|
|
<p class="none">
|
|
<?= $since !== ''
|
|
? 'Seit ' . e($since) . ' hat sich nichts geändert.'
|
|
: 'Es ist nichts hinterlegt.' ?>
|
|
</p>
|
|
<?php else: ?>
|
|
<div class="box">
|
|
<strong><?= count($versions) ?></strong> Fassung(en)<?= $since !== '' ? ' neuer als ' . e($since) : '' ?>,
|
|
davon <strong style="color:var(--warn);"><?= $actionItems ?></strong> Punkt(e) mit Handlungsbedarf
|
|
und <strong style="color:var(--break);"><?= $breaking ?></strong> mit Bruch.
|
|
</div>
|
|
|
|
<?php foreach ($versions as $v): ?>
|
|
<div class="ver">
|
|
<h2><?= e($v['version'] ?? '?') ?>
|
|
<span class="date">— <?= e($v['date'] ?? '') ?></span>
|
|
</h2>
|
|
<p class="summary"><?= e($v['summary'] ?? '') ?></p>
|
|
|
|
<?php foreach ($v['changes'] ?? [] as $c): ?>
|
|
<div class="chg">
|
|
<h3>
|
|
<span class="tag t-area"><?= e($areaLabels[$c['area'] ?? ''] ?? ($c['area'] ?? '?')) ?></span>
|
|
<?php if (!empty($c['breaking'])): ?><span class="tag t-break">BRUCH</span><?php endif; ?>
|
|
<?php if (!empty($c['action'])): ?><span class="tag t-act">TUN</span><?php endif; ?>
|
|
<?= e($c['title'] ?? '') ?>
|
|
</h3>
|
|
<div><?= e($c['text'] ?? '') ?></div>
|
|
<?php if (!empty($c['action'])): ?>
|
|
<div class="action"><strong>Zu tun:</strong> <?= e($c['action']) ?></div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
|
|
<p style="margin-top:2.5rem; color:var(--muted); font-size:.9rem;">
|
|
<a href="/docs/">Agenten-Handbuch</a> ·
|
|
<a href="/docs/release.md">Veröffentlichen</a> ·
|
|
<a href="/api/openapi.json">OpenAPI</a>
|
|
</p>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|