fix(updater): Paket-Adresse aus Tag und Dateiname bauen, Version 1.0.2

Gitea meldet browser_download_url hinter dem TLS-Proxy mit http://, der Updater lehnte die Adresse ab
und fand kein Update. Die Adresse wird jetzt aus dem geprüften Tag und dem Dateinamen gebaut; release-check.sh
prüft genauso.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-04 14:50:43 +01:00
co-authored by Claude Sonnet 5.5
parent b2759fba73
commit c8648d5263
6 changed files with 44 additions and 25 deletions
+4 -2
View File
@@ -25,8 +25,10 @@ latest="$(curl -fsS "$gitea/api/v1/repos/$repo/releases/latest")" \
tag="$(jq -r .tag_name <<<"$latest")"
[[ "$tag" == "v$version" ]] || fail "releases/latest liefert '$tag', erwartet v$version"
zip_url="$(jq -r --arg n "$name" '.assets[] | select(.name == $n) | .browser_download_url' <<<"$latest")"
[[ "$zip_url" == "$gitea/$repo/releases/download/"* ]] || fail "Asset-URL fehlt oder liegt woanders: '$zip_url'"
# The address is built like GiteaUpdater builds it: Gitea behind a TLS proxy reports http:// in browser_download_url.
[[ "$(jq -r --arg n "$name" '[.assets[] | select(.name == $n)] | length' <<<"$latest")" == "1" ]] \
|| fail "Das Release hat kein Asset $name"
zip_url="$gitea/$repo/releases/download/$tag/$name"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
+6 -3
View File
@@ -4,7 +4,7 @@ Tags: mcp, ai, divi, pods, oauth
Requires at least: 6.9
Tested up to: 7.1
Requires PHP: 8.1
Stable tag: 1.0.1
Stable tag: 1.0.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -200,8 +200,8 @@ Bibliothekseintrags.
Neue Versionen erscheinen als Release auf git.loosen-it.de; WordPress zeigt sie unter Plugins und Dashboard →
Aktualisierungen an wie jedes andere Plugin. Automatisch einspielen lässt sich ein Update mit dem Schalter
„Automatische Updates aktivieren“ in der Plugin-Liste. Ein Update ersetzt den Plugin-Ordner; ein Entwicklungs-
Checkout mit .git-Ordner bekommt deshalb kein Update angeboten. Versionen vor 1.0.1 kennen diesen
Weg nicht: der Wechsel auf 1.0.1 ist einmalig von Hand (ZIP hochladen), danach läuft es über WordPress.
Checkout mit .git-Ordner bekommt deshalb kein Update angeboten. Versionen vor 1.0.2 kennen diesen
Weg nicht: der Wechsel auf 1.0.2 ist einmalig von Hand (ZIP hochladen), danach läuft es über WordPress.
== Divi-Freigabe ==
@@ -209,6 +209,9 @@ LIT MCP schreibt nur mit Divi-Versionen, die für diese Plugin-Version geprüft
== Changelog ==
= 1.0.2 =
* Behebt: 1.0.1 fand keine Updates, weil Gitea die Download-Adresse mit http:// meldet. Die Adresse des Pakets wird jetzt selbst aus Tag und Dateiname gebaut. Der Wechsel auf 1.0.2 ist einmalig von Hand.
= 1.0.1 =
* Updates über WordPress: Das neueste Release von git.loosen-it.de erscheint als Plugin-Update (Update URI); automatisches Einspielen über den Schalter in der Plugin-Liste. Der Wechsel auf 1.0.1 ist einmalig von Hand. (Version 1.0.0 wurde nie ausgeliefert.)
+1 -1
View File
@@ -46,7 +46,7 @@ defined('ABSPATH') || exit;
*/
final class Plugin
{
public const VERSION = '1.0.1';
public const VERSION = '1.0.2';
private static string $pluginFile = '';
private static ?SettingsPage $settingsPage = null;
+9 -12
View File
@@ -13,7 +13,7 @@ defined('ABSPATH') || exit;
* Gitea host, so WordPress asks the filter update_plugins_<host> (about every 12 hours, which is also the only
* caching needed). The release carries the ZIP as asset wp-lit-mcp-<version>.zip.
*
* shortcut: no signature – a package is trusted as far as the repository's release downloads and TLS are, so whoever
* shortcut: no signature – the package is trusted as far as the repository's release downloads and TLS are, so whoever
* can publish a release there ships code to every site that updates. Public release assets only (WordPress downloads
* without credentials). Add a signature checked before unpacking (key outside Gitea) before the plugin runs on sites
* you do not control.
@@ -90,7 +90,7 @@ final class GiteaUpdater
}
$version = $match[1];
$package = self::packageUrl((array) ($body['assets'] ?? []), $version);
$package = self::packageUrl((array) ($body['assets'] ?? []), $tag, $version);
if ($package === null) {
return self::failed(
sprintf('release %s has no asset %s-%s.zip in the repository', $tag, self::SLUG, $version)
@@ -101,21 +101,18 @@ final class GiteaUpdater
}
/**
* The ZIP asset of this version, only from the repository's release downloads: the trust boundary of the update.
* The ZIP of this version – its address is built here from the checked tag and the file name, not taken from the
* answer: Gitea behind a TLS proxy reports http:// (its ROOT_URL), and a URL from the answer would be a second
* trust boundary. The asset only has to exist.
*
* @param array<array-key, mixed> $assets
*/
private static function packageUrl(array $assets, string $version): ?string
private static function packageUrl(array $assets, string $tag, string $version): ?string
{
$zipName = self::SLUG . '-' . $version . '.zip';
foreach ($assets as $asset) {
if (! is_array($asset)) {
continue;
}
$url = (string) ($asset['browser_download_url'] ?? '');
$isZip = ($asset['name'] ?? '') === self::SLUG . '-' . $version . '.zip';
if ($isZip && str_starts_with($url, self::RELEASE_DOWNLOADS)) {
return $url;
if (is_array($asset) && ($asset['name'] ?? '') === $zipName) {
return self::RELEASE_DOWNLOADS . rawurlencode($tag) . '/' . $zipName;
}
}
+23 -6
View File
@@ -55,6 +55,28 @@ final class GiteaUpdaterTest extends TestCase
self::assertSame('', $this->log());
}
/** @dataProvider reportedAddresses */
public function testPackageAddressIsBuiltHereNotTakenFromTheAnswer(string $reported): void
{
$this->givenRelease(200, self::releaseBody('v1.0.0', self::ZIP_NAME, $reported));
$update = $this->updater()->offer(false, [], self::BASENAME);
self::assertSame(self::ZIP, $update['package']);
}
/** @return array<string, array{string}> */
public static function reportedAddresses(): array
{
return [
'http, as Gitea behind a TLS proxy reports it' => [
'http://' . GiteaUpdater::HOST . '/janniclas.loosen/WP-LIT-MCP/releases/download/v1.0.0/'
. self::ZIP_NAME,
],
'another host' => ['https://evil.example/' . self::ZIP_NAME],
];
}
public function testOtherPluginsAreLeftAlone(): void
{
Functions\expect('wp_remote_get')->never();
@@ -104,8 +126,6 @@ final class GiteaUpdaterTest extends TestCase
/** @return array<string, array{string}> */
public static function unusableReleases(): array
{
$foreign = 'https://evil.example/releases/download/v1.0.0/' . self::ZIP_NAME;
return [
'no JSON' => ['<html>'],
'tag is no version' => [self::releaseBody('latest', self::ZIP_NAME, self::ZIP)],
@@ -113,10 +133,7 @@ final class GiteaUpdaterTest extends TestCase
'tag with trailing newline' => [self::releaseBody("v1.0.0\n", self::ZIP_NAME, self::ZIP)],
'asset missing' => [self::releaseBody('v1.0.0', 'other.zip', self::ZIP)],
'release without assets' => ['{"tag_name":"v1.0.0"}'],
'package from a foreign host' => [self::releaseBody('v1.0.0', self::ZIP_NAME, $foreign)],
'package outside the release downloads' => [
self::releaseBody('v1.0.0', self::ZIP_NAME, 'https://' . GiteaUpdater::HOST . '/other/repo/x.zip'),
],
'asset of another version' => [self::releaseBody('v1.0.0', 'wp-lit-mcp-0.9.0.zip', self::ZIP)],
];
}
+1 -1
View File
@@ -3,7 +3,7 @@
/**
* Plugin Name: WP-LIT-MCP
* Description: MCP-Server für WordPress, Divi 5 und Pods – mit OAuth für KI-Assistenten.
* Version: 1.0.1
* Version: 1.0.2
* Update URI: https://git.loosen-it.de/janniclas.loosen/WP-LIT-MCP
* Requires at least: 6.9
* Requires PHP: 8.1