wp-admin builds "View version details" as
plugin-install.php?tab=plugin-information&plugin=<slug>, which calls
plugins_api() — which asks wordpress.org. This plugin is not there, so the link
opened a modal reading "Plugin not found." on the same screen that was telling
the shop owner an update was ready. Seen on the live shop, not inferred.
The filter is answered here now: name, version from the feed, author, links,
compatibility, and the release notes as the changelog — escaped rather than
rendered, because a release body is not a template language. Another plugin's
slug falls through untouched, with an assertion on that: answering for somebody
else would replace their modal with ours.
Two checks in ci/update-smoke.sh could not fail, both found by the subscriptions
port, both sitting under a comment explaining exactly what they were for:
- the plugin directories were counted into a variable that was never read. The
comment above it describes a zip installing a SECOND copy beside the first;
nothing tested it. Now: directories starting with the slug, exactly one.
- the last step ran `wp eval 'echo WC_EDM_VERSION;'`, hard-coding this plugin's
constant into a script that is copied between plugins. Elsewhere it printed
an empty value and carried on. The name is read out of the plugin header now
and the value is compared against the version that was installed, because
files can be replaced while the running code still reports the old version.
Both verified failing before being believed: copies=2 and copies=0 fail,
running != expected fails, and a plugin with no *_VERSION constant takes the
branch that says so instead of comparing against an empty string.
CORRECTION to the note added with the release-id fix. It said those four
releases published correctly "because this instance's author id is -1". That is
wrong in a way that invites relaxing: gitea.nolimit.dk is one instance and both
plugins publish to it. The difference is WHO publishes. A release created by the
Actions job token has the ghost user as its author ("id":-1) and the minus sign
hides it from [0-9]+; a release created by a real user — a cross-repo PAT, or
one published by hand — has "id":1 and the greedy match lands on it. Verified:
this repo's latest release reports {'id': -1, 'login': 'Ghost'}. Corrected in
CLAUDE.md and docs/REUSE.md.
docs/REUSE.md gains "Make a check fail once before you believe it", listing all
three dead checks found today. A green build is exactly what a broken check
looks like.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
133 lines
5.9 KiB
Bash
Executable File
133 lines
5.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Install the PREVIOUS version into a throwaway shop and let WordPress update
|
|
# it from the published feed, on its own.
|
|
#
|
|
# This is the only check that proves the whole chain: header, filter, feed,
|
|
# download address, zip layout and the folder WordPress unpacks into. Every
|
|
# part of it can be individually correct and the update still not arrive —
|
|
# a package URL on an address the shop cannot reach, a zip whose top-level
|
|
# directory is not the plugin slug, a version WordPress does not consider
|
|
# newer. Run it after a release.
|
|
#
|
|
# FROM_VERSION=1.3.1 ci/update-smoke.sh
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
. ci/lib.sh
|
|
ci_load_conf
|
|
|
|
FROM_VERSION="${FROM_VERSION:-0.0.1}"
|
|
expected=$(ci_plugin_version)
|
|
|
|
id="wpup-$$"
|
|
net="$id-net"
|
|
cleanup() {
|
|
docker rm -f "$id-db" "$id-cli" >/dev/null 2>&1 || true
|
|
docker volume rm -f "$id-wp" >/dev/null 2>&1 || true
|
|
docker network rm "$net" >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "Installing $PLUGIN_SLUG $FROM_VERSION, then asking WordPress to update it. Expecting $expected."
|
|
|
|
docker network create "$net" >/dev/null
|
|
docker volume create "$id-wp" >/dev/null
|
|
docker run -d --name "$id-db" --network "$net" \
|
|
-e MARIADB_ROOT_PASSWORD=root -e MARIADB_DATABASE=wordpress \
|
|
-e MARIADB_USER=wordpress -e MARIADB_PASSWORD=wordpress mariadb:lts >/dev/null
|
|
docker run --rm -v "$id-wp:/var/www/html" alpine chown 33:33 /var/www/html
|
|
docker run -d --name "$id-cli" --network "$net" -v "$id-wp:/var/www/html" \
|
|
-e WORDPRESS_DB_HOST="$id-db" wordpress:cli sleep infinity >/dev/null
|
|
docker exec -u root "$id-cli" sh -c 'echo memory_limit=512M > /usr/local/etc/php/conf.d/zz-memory.ini'
|
|
wp() { docker exec "$id-cli" wp "$@"; }
|
|
|
|
for i in $(seq 1 60); do
|
|
docker exec "$id-db" mariadb -uwordpress -pwordpress -e 'select 1' wordpress >/dev/null 2>&1 && break
|
|
[ "$i" = 60 ] && { echo "MariaDB never came up." >&2; exit 1; }
|
|
sleep 2
|
|
done
|
|
|
|
ci_step "a shop running the previous version"
|
|
wp core download --quiet
|
|
wp config create --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --dbhost="$id-db" --quiet
|
|
wp core install --url=http://plugin.test --title="update smoke" --admin_user=admin \
|
|
--admin_password=admin --admin_email=[email protected] --skip-email --quiet
|
|
[ "$NEEDS_WOOCOMMERCE" = yes ] && wp plugin install woocommerce --activate --quiet
|
|
|
|
# The working tree, with the version wound back. WordPress compares the feed
|
|
# against this header and nothing else, so this is what makes it an "older"
|
|
# install as far as the update check is concerned.
|
|
./ci/dist-files.sh | tr '\n' '\0' | tar --null -T - -cf - \
|
|
| docker exec -i "$id-cli" sh -c \
|
|
"mkdir -p /var/www/html/wp-content/plugins/$PLUGIN_SLUG \
|
|
&& tar -xf - -C /var/www/html/wp-content/plugins/$PLUGIN_SLUG"
|
|
docker exec "$id-cli" sed -i -E \
|
|
"s/^([[:space:]]*\*[[:space:]]*Version:[[:space:]]*).*/\1$FROM_VERSION/" \
|
|
"/var/www/html/wp-content/plugins/$PLUGIN_SLUG/$PLUGIN_MAIN"
|
|
wp plugin activate "$PLUGIN_SLUG" --quiet
|
|
installed=$(wp plugin get "$PLUGIN_SLUG" --field=version)
|
|
echo " installed $installed"
|
|
[ "$installed" = "$FROM_VERSION" ] || { echo "Winding the version back did not take." >&2; exit 1; }
|
|
|
|
ci_step "what WordPress thinks, unprompted"
|
|
# Not the plugin's own code — WordPress's update check, the same one that runs
|
|
# on a schedule in a live shop.
|
|
wp plugin update --all --dry-run 2>&1 | sed 's/^/ /' || true
|
|
available=$(wp plugin list --name="$PLUGIN_SLUG" --field=update)
|
|
echo " update status: $available"
|
|
[ "$available" = available ] || {
|
|
echo "WordPress was not offered an update. The header, the filter or the feed is wrong." >&2
|
|
exit 1
|
|
}
|
|
|
|
ci_step "letting WordPress install it"
|
|
wp plugin update "$PLUGIN_SLUG" 2>&1 | sed 's/^/ /'
|
|
|
|
now=$(wp plugin get "$PLUGIN_SLUG" --field=version)
|
|
status=$(wp plugin list --name="$PLUGIN_SLUG" --field=status)
|
|
echo
|
|
echo " version after update: $now (was $FROM_VERSION)"
|
|
echo " status: $status"
|
|
|
|
[ "$now" = "$expected" ] || { echo "Expected $expected after the update." >&2; exit 1; }
|
|
[ "$status" = active ] || { echo "The plugin is no longer active after updating itself." >&2; exit 1; }
|
|
|
|
ci_step "one copy of the plugin, not two"
|
|
# A zip whose top-level directory is not the slug installs a SECOND copy beside
|
|
# the first instead of replacing it, and the old one stays active. This used to
|
|
# count the directories into a variable and then never look at it — a check
|
|
# that could not fail, sitting under a comment explaining exactly what it was
|
|
# for.
|
|
docker exec "$id-cli" ls /var/www/html/wp-content/plugins | sed 's/^/ /'
|
|
copies=$(docker exec "$id-cli" sh -c \
|
|
"ls -d /var/www/html/wp-content/plugins/$PLUGIN_SLUG* 2>/dev/null | wc -l" | tr -d '[:space:]')
|
|
echo " directories starting with $PLUGIN_SLUG: $copies"
|
|
[ "$copies" = 1 ] || {
|
|
echo "Expected exactly one, got $copies. The zip unpacked to a directory that is not the slug." >&2
|
|
exit 1
|
|
}
|
|
|
|
ci_step "the code that is now running reports the new version"
|
|
# Files can be replaced while the running code still reports the old version —
|
|
# an opcache that was not invalidated, or a constant defined somewhere else.
|
|
# The constant name is read out of the plugin header rather than hard-coded:
|
|
# this script is copied between plugins, and a name that does not exist over
|
|
# there prints an empty value and carries on cheerfully.
|
|
const=$(sed -n -E "s/^[[:space:]]*define\([[:space:]]*'([A-Z_]*VERSION)'.*/\1/p" "$PLUGIN_MAIN" | head -1)
|
|
if [ -n "$const" ]; then
|
|
running=$(wp eval "echo defined( '$const' ) ? $const : '';" | tr -d '[:space:]')
|
|
echo " $const = ${running:-<undefined>}"
|
|
[ "$running" = "$expected" ] || {
|
|
echo "The files say $expected but the running code says '${running:-undefined}'." >&2
|
|
exit 1
|
|
}
|
|
else
|
|
echo " $PLUGIN_MAIN defines no *_VERSION constant — nothing to cross-check."
|
|
fi
|
|
|
|
wp plugin list --name="$PLUGIN_SLUG" --field=update | grep -qx none \
|
|
&& echo " WordPress no longer offers an update — the header and the feed agree."
|
|
|
|
echo
|
|
echo "WordPress found, downloaded and installed $expected from the published feed on its own."
|