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="$(jq -r .tag_name <<<"$latest")"
[[ "$tag" == "v$version" ]] || fail "releases/latest liefert '$tag', erwartet v$version" [[ "$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")" # The address is built like GiteaUpdater builds it: Gitea behind a TLS proxy reports http:// in browser_download_url.
[[ "$zip_url" == "$gitea/$repo/releases/download/"* ]] || fail "Asset-URL fehlt oder liegt woanders: '$zip_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)" work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT trap 'rm -rf "$work"' EXIT
+6 -3
View File
@@ -4,7 +4,7 @@ Tags: mcp, ai, divi, pods, oauth
Requires at least: 6.9 Requires at least: 6.9
Tested up to: 7.1 Tested up to: 7.1
Requires PHP: 8.1 Requires PHP: 8.1
Stable tag: 1.0.1 Stable tag: 1.0.2
License: GPL-2.0-or-later License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html 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 → 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 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- „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 Checkout mit .git-Ordner bekommt deshalb kein Update angeboten. Versionen vor 1.0.2 kennen diesen
Weg nicht: der Wechsel auf 1.0.1 ist einmalig von Hand (ZIP hochladen), danach läuft es über WordPress. Weg nicht: der Wechsel auf 1.0.2 ist einmalig von Hand (ZIP hochladen), danach läuft es über WordPress.
== Divi-Freigabe == == Divi-Freigabe ==
@@ -209,6 +209,9 @@ LIT MCP schreibt nur mit Divi-Versionen, die für diese Plugin-Version geprüft
== Changelog == == 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 = = 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.) * 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 final class Plugin
{ {
public const VERSION = '1.0.1'; public const VERSION = '1.0.2';
private static string $pluginFile = ''; private static string $pluginFile = '';
private static ?SettingsPage $settingsPage = null; 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 * 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. * 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 * 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 * without credentials). Add a signature checked before unpacking (key outside Gitea) before the plugin runs on sites
* you do not control. * you do not control.
@@ -90,7 +90,7 @@ final class GiteaUpdater
} }
$version = $match[1]; $version = $match[1];
$package = self::packageUrl((array) ($body['assets'] ?? []), $version); $package = self::packageUrl((array) ($body['assets'] ?? []), $tag, $version);
if ($package === null) { if ($package === null) {
return self::failed( return self::failed(
sprintf('release %s has no asset %s-%s.zip in the repository', $tag, self::SLUG, $version) 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 * @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) { foreach ($assets as $asset) {
if (! is_array($asset)) { if (is_array($asset) && ($asset['name'] ?? '') === $zipName) {
continue; return self::RELEASE_DOWNLOADS . rawurlencode($tag) . '/' . $zipName;
}
$url = (string) ($asset['browser_download_url'] ?? '');
$isZip = ($asset['name'] ?? '') === self::SLUG . '-' . $version . '.zip';
if ($isZip && str_starts_with($url, self::RELEASE_DOWNLOADS)) {
return $url;
} }
} }
+23 -6
View File
@@ -55,6 +55,28 @@ final class GiteaUpdaterTest extends TestCase
self::assertSame('', $this->log()); 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 public function testOtherPluginsAreLeftAlone(): void
{ {
Functions\expect('wp_remote_get')->never(); Functions\expect('wp_remote_get')->never();
@@ -104,8 +126,6 @@ final class GiteaUpdaterTest extends TestCase
/** @return array<string, array{string}> */ /** @return array<string, array{string}> */
public static function unusableReleases(): array public static function unusableReleases(): array
{ {
$foreign = 'https://evil.example/releases/download/v1.0.0/' . self::ZIP_NAME;
return [ return [
'no JSON' => ['<html>'], 'no JSON' => ['<html>'],
'tag is no version' => [self::releaseBody('latest', self::ZIP_NAME, self::ZIP)], '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)], '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)], 'asset missing' => [self::releaseBody('v1.0.0', 'other.zip', self::ZIP)],
'release without assets' => ['{"tag_name":"v1.0.0"}'], 'release without assets' => ['{"tag_name":"v1.0.0"}'],
'package from a foreign host' => [self::releaseBody('v1.0.0', self::ZIP_NAME, $foreign)], 'asset of another version' => [self::releaseBody('v1.0.0', 'wp-lit-mcp-0.9.0.zip', self::ZIP)],
'package outside the release downloads' => [
self::releaseBody('v1.0.0', self::ZIP_NAME, 'https://' . GiteaUpdater::HOST . '/other/repo/x.zip'),
],
]; ];
} }
+1 -1
View File
@@ -3,7 +3,7 @@
/** /**
* Plugin Name: WP-LIT-MCP * Plugin Name: WP-LIT-MCP
* Description: MCP-Server für WordPress, Divi 5 und Pods – mit OAuth für KI-Assistenten. * 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 * Update URI: https://git.loosen-it.de/janniclas.loosen/WP-LIT-MCP
* Requires at least: 6.9 * Requires at least: 6.9
* Requires PHP: 8.1 * Requires PHP: 8.1