WP Plugin Starter 1.0.0: a fresh start
The parts every wpallstars plugin needs: settings screen with search and an empty General tab, Read Me tab, features as classes, replaced plugins, the shared GitHub updater, release and check scripts, CI and the shared rules. scripts/rename-plugin.sh starts a new plugin; scripts/sync-core.sh keeps plugins' core files in step with this starter. Built with aidevops (https://aidevops.sh).
This commit is contained in:
commit
2f91c3e6af
62 files changed
+10929
No files matched your search
Executable
+265
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run Plugin Check (the WordPress.org review tool) on the release zips, in a
|
||||
# disposable WordPress in Docker. Nothing is kept: the containers, volume and
|
||||
# network are removed when it ends, and no other site is touched.
|
||||
#
|
||||
# Usage: scripts/plugin-check.sh [--ref REF] [--zip FILE]... [--keep-output DIR]
|
||||
# --ref REF Build both zips from REF (default: HEAD) and check them.
|
||||
# --zip FILE Check this zip instead (repeatable); it must hold one
|
||||
# {slug}/ folder.
|
||||
# --keep-output DIR Save each full report as JSON in DIR.
|
||||
#
|
||||
# Exit status: 0 when no zip has Plugin Check errors. Warnings are listed;
|
||||
# review them before a WordPress.org submission. Updater findings in the
|
||||
# GitHub zip's updater files (.distignore-wporg) are expected and not counted.
|
||||
# Needs Docker and internet access (WordPress and Plugin Check are downloaded).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly SCRIPT_DIR
|
||||
# shellcheck source=scripts/lib/plugin.sh disable=SC1091 # followed only with -x
|
||||
. "$SCRIPT_DIR/lib/plugin.sh"
|
||||
# Set in main() from the plugin's main file.
|
||||
SLUG=""
|
||||
UPDATER_FILES=""
|
||||
CLI_IMAGE=""
|
||||
DB_IMAGE=""
|
||||
readonly DB_PASSWORD="plugincheck"
|
||||
|
||||
NAME=""
|
||||
TMP_DIR=""
|
||||
STARTED=0
|
||||
|
||||
die() {
|
||||
local message="$1"
|
||||
printf 'plugin-check: %s\n' "$message" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
usage() {
|
||||
sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [ "$STARTED" -eq 1 ]; then
|
||||
local attempt id
|
||||
# wp-cli containers still stopping (after Ctrl-C) keep the volume and
|
||||
# network in use, so remove them first and retry.
|
||||
for attempt in 1 2 3; do
|
||||
docker ps -aq --filter "volume=$NAME-wp" | while IFS= read -r id; do
|
||||
docker rm -f "$id" >/dev/null 2>&1 || true
|
||||
done
|
||||
docker rm -f "$NAME-db" >/dev/null 2>&1 || true
|
||||
docker volume rm "$NAME-wp" >/dev/null 2>&1 || true
|
||||
docker network rm "$NAME" >/dev/null 2>&1 || true
|
||||
if ! docker volume inspect "$NAME-wp" >/dev/null 2>&1 && ! docker network inspect "$NAME" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
[ "$attempt" -eq 3 ] && printf 'plugin-check: could not remove %s-wp or network %s\n' "$NAME" "$NAME" >&2
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then
|
||||
rm -rf "$TMP_DIR"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# wp-cli in the disposable site; zips are mounted at /zips. The image's
|
||||
# 128 MB PHP memory limit is too small to unpack WordPress.
|
||||
wp_cli() {
|
||||
docker run --rm --network "$NAME" -v "$NAME-wp:/var/www/html" -v "$TMP_DIR/zips:/zips:ro" \
|
||||
--user 33:33 "$CLI_IMAGE" php -d memory_limit=1G /usr/local/bin/wp "$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
start_site() {
|
||||
printf 'Starting a disposable WordPress (%s)...\n' "$NAME"
|
||||
STARTED=1
|
||||
docker network create "$NAME" >/dev/null
|
||||
docker volume create "$NAME-wp" >/dev/null
|
||||
docker run -d --name "$NAME-db" --network "$NAME" \
|
||||
-e MARIADB_ROOT_PASSWORD="$DB_PASSWORD" -e MARIADB_DATABASE=wordpress "$DB_IMAGE" >/dev/null
|
||||
# The volume starts owned by root; let www-data (33) write to it.
|
||||
docker run --rm -v "$NAME-wp:/var/www/html" --user 0:0 "$CLI_IMAGE" chown 33:33 /var/www/html
|
||||
|
||||
# Ping over TCP: the image's first, socket-only server answers on the
|
||||
# socket before the real server is listening.
|
||||
local waited=0
|
||||
until docker exec "$NAME-db" mariadb-admin ping -h127.0.0.1 -uroot -p"$DB_PASSWORD" --silent >/dev/null 2>&1; do
|
||||
[ "$waited" -lt 90 ] || die "the database did not start"
|
||||
sleep 2
|
||||
waited=$((waited + 2))
|
||||
done
|
||||
|
||||
wp_cli core download --quiet
|
||||
wp_cli config create --dbname=wordpress --dbuser=root --dbpass="$DB_PASSWORD" --dbhost="$NAME-db" --skip-check --quiet
|
||||
wp_cli core install --url=http://localhost --title=PluginCheck --admin_user=admin --admin_password="$DB_PASSWORD" \
|
||||
--admin_email=admin@example.com --skip-email --quiet
|
||||
wp_cli plugin install plugin-check --activate --quiet
|
||||
printf 'WordPress %s, Plugin Check %s\n' "$(wp_cli core version)" "$(wp_cli plugin get plugin-check --field=version)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check one zip; returns 1 when Plugin Check reports errors.
|
||||
check_zip() {
|
||||
local zip_name="$1"
|
||||
local keep="$2"
|
||||
local report errors warnings
|
||||
printf '\n== %s ==\n' "$zip_name"
|
||||
if ! wp_cli plugin install "/zips/$zip_name" --force --quiet; then
|
||||
printf 'Could not install %s.\n' "$zip_name"
|
||||
return 1
|
||||
fi
|
||||
report="$(wp_cli plugin check "$SLUG" --format=json 2>&1 || true)"
|
||||
if [ -n "$keep" ]; then
|
||||
printf '%s\n' "$report" >"$keep/${zip_name%.zip}-plugin-check.json"
|
||||
fi
|
||||
if ! printf '%s\n' "$report" | grep -Eq '^(FILE: |Success: )'; then
|
||||
# Neither findings nor the success line: Plugin Check did not run.
|
||||
printf 'Plugin Check did not run:\n%s\n' "$report"
|
||||
wp_cli plugin delete "$SLUG" --quiet || true
|
||||
return 1
|
||||
fi
|
||||
errors="$( (printf '%s\n' "$report" | grep -o '"type":"ERROR"' || true) | wc -l | tr -d ' ')"
|
||||
warnings="$( (printf '%s\n' "$report" | grep -o '"type":"WARNING"' || true) | wc -l | tr -d ' ')"
|
||||
# The GitHub zip carries Updates from GitHub on purpose; Plugin Check
|
||||
# reports it as an updater. Those findings are expected there (and only
|
||||
# in those files); in the WordPress.org zip they stay errors. So are
|
||||
# prefix warnings there: the shared updater's names (wpallstars_) are the
|
||||
# same in every plugin, so that one copy can stand in for the others.
|
||||
local expected=0
|
||||
local expected_warnings=0
|
||||
local counts
|
||||
case "$zip_name" in
|
||||
"$SLUG"-*)
|
||||
# The file list goes through the environment: awk -v cannot hold newlines.
|
||||
counts="$(printf '%s\n' "$report" | FILES="$UPDATER_FILES" awk '
|
||||
BEGIN { nfiles = split(ENVIRON["FILES"], list, "\n") }
|
||||
# A listed file, or a file inside a listed folder.
|
||||
function updater(path, i) {
|
||||
for (i = 1; i <= nfiles; i++) {
|
||||
if (list[i] != "" && (path == list[i] || index(path, list[i] "/") == 1)) { return 1 }
|
||||
}
|
||||
return 0
|
||||
}
|
||||
/^FILE: / { current = substr($0, 7); next }
|
||||
/^\[/ && updater(current) {
|
||||
n = split($0, items, "},{")
|
||||
for (i = 1; i <= n; i++) {
|
||||
if (items[i] ~ /"type":"ERROR"/ && items[i] ~ /"code":"(plugin_updater_detected|update_modification_detected|PluginCheck\.CodeAnalysis\.Offloading\.OffloadedContent)"/) { count++ }
|
||||
if (items[i] ~ /"type":"WARNING"/ && items[i] ~ /"code":"WordPress\.NamingConventions\.PrefixAllGlobals\./) { prefix++ }
|
||||
}
|
||||
}
|
||||
END { print count + 0, prefix + 0 }')"
|
||||
expected="${counts% *}"
|
||||
expected_warnings="${counts#* }"
|
||||
;;
|
||||
esac
|
||||
if [ "$expected" -gt 0 ] || [ "$expected_warnings" -gt 0 ]; then
|
||||
printf '%s updater error(s) and %s prefix warning(s) in %s are expected in the GitHub zip.\n' "$expected" "$expected_warnings" "$(printf '%s' "$UPDATER_FILES" | tr '\n' ' ')"
|
||||
errors=$((errors - expected))
|
||||
warnings=$((warnings - expected_warnings))
|
||||
fi
|
||||
# Readable summary: one line per finding (type, code, file:line).
|
||||
printf '%s\n' "$report" | awk '
|
||||
/^FILE: / { file = substr($0, 7); next }
|
||||
/^\[/ {
|
||||
n = split($0, items, "},{")
|
||||
for (i = 1; i <= n; i++) {
|
||||
t = items[i]; c = items[i]; l = items[i]
|
||||
sub(/.*"type":"/, "", t); sub(/".*/, "", t)
|
||||
sub(/.*"code":"/, "", c); sub(/".*/, "", c)
|
||||
sub(/.*"line":/, "", l); sub(/[^0-9].*/, "", l)
|
||||
printf " %-7s %s %s:%s\n", t, c, file, l
|
||||
}
|
||||
}'
|
||||
printf '%s error(s), %s warning(s)\n' "$errors" "$warnings"
|
||||
wp_cli plugin delete "$SLUG" --quiet || true
|
||||
[ "$errors" -eq 0 ] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
main() {
|
||||
local ref="HEAD"
|
||||
local keep=""
|
||||
local zips=""
|
||||
local arg
|
||||
while [ $# -gt 0 ]; do
|
||||
arg="$1"
|
||||
case "$arg" in
|
||||
--ref)
|
||||
[ $# -ge 2 ] || die "--ref needs a value"
|
||||
ref="$2"
|
||||
shift
|
||||
;;
|
||||
--zip)
|
||||
[ $# -ge 2 ] || die "--zip needs a file"
|
||||
[ -f "$2" ] || die "no such zip: $2"
|
||||
zips="$zips
|
||||
$(cd "$(dirname "$2")" && pwd)/$(basename "$2")"
|
||||
shift
|
||||
;;
|
||||
--keep-output)
|
||||
[ $# -ge 2 ] || die "--keep-output needs a folder"
|
||||
mkdir -p "$2"
|
||||
keep="$(cd "$2" && pwd)"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
return 0
|
||||
;;
|
||||
*) die "unknown argument: $arg" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
command -v docker >/dev/null 2>&1 || die "needs Docker"
|
||||
docker info >/dev/null 2>&1 || die "Docker is not running"
|
||||
local root
|
||||
root="$(git rev-parse --show-toplevel)" || die "run this inside a checkout of the plugin"
|
||||
cd "$root"
|
||||
plugin_identity "$ref" || die "cannot tell which plugin this is at $ref"
|
||||
SLUG="$PLUGIN_SLUG"
|
||||
UPDATER_FILES="$(plugin_wporg_only "$ref")"
|
||||
CLI_IMAGE="$(plugin_env CLI_IMAGE wordpress:cli-php8.3)"
|
||||
DB_IMAGE="$(plugin_env DB_IMAGE mariadb:10.6)"
|
||||
NAME="$SLUG-plugincheck-$$"
|
||||
|
||||
trap cleanup EXIT
|
||||
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/$SLUG-plugincheck.XXXXXX")"
|
||||
mkdir -p "$TMP_DIR/zips"
|
||||
chmod 755 "$TMP_DIR" "$TMP_DIR/zips"
|
||||
|
||||
local zip_path
|
||||
if [ -z "$zips" ]; then
|
||||
"$root/scripts/build-release.sh" --ref "$ref" --out "$TMP_DIR/zips" --quiet >/dev/null || die "build failed"
|
||||
else
|
||||
while IFS= read -r zip_path; do
|
||||
[ -n "$zip_path" ] && cp "$zip_path" "$TMP_DIR/zips/"
|
||||
done <<EOF
|
||||
$zips
|
||||
EOF
|
||||
fi
|
||||
chmod 644 "$TMP_DIR"/zips/*.zip
|
||||
|
||||
start_site
|
||||
local failed=0 zip_name
|
||||
for zip_path in "$TMP_DIR"/zips/*.zip; do
|
||||
zip_name="$(basename "$zip_path")"
|
||||
check_zip "$zip_name" "$keep" || failed=1
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
if [ "$failed" -eq 1 ]; then
|
||||
printf 'Plugin Check found errors.\n'
|
||||
return 1
|
||||
fi
|
||||
printf 'Plugin Check: no errors.\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user