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).
587 lines
22 KiB
Bash
Executable File
587 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Check a version of the plugin before it is released on GitHub or
|
|
# submitted to WordPress.org. Changes nothing: it builds both zips into a
|
|
# temporary folder (scripts/build-release.sh) and reads them and the Git ref.
|
|
#
|
|
# Errors stop a release. Warnings need a look and a decision; most matter only
|
|
# for WordPress.org. Notes are for information.
|
|
#
|
|
# Usage: scripts/preflight-release.sh [--ref REF] [--offline] [--strict] [--no-docker]
|
|
# --ref REF Commit, branch or tag to check (default: HEAD).
|
|
# --offline Skip checks that ask WordPress.org (latest WordPress version,
|
|
# slug, contributor profiles).
|
|
# --strict Fail on warnings too (use before a WordPress.org submission).
|
|
# --no-docker Lint PHP with the local php instead of PHP 7.4 in Docker.
|
|
#
|
|
# Exit status: 0 when there are no errors (and, with --strict, no warnings).
|
|
# Plugin Check runs separately: scripts/plugin-check.sh.
|
|
|
|
set -euo pipefail
|
|
|
|
readonly UPDATER_HEADERS='GitHub Plugin URI|Primary Branch|Release Asset'
|
|
# Development files that must never be in a release zip (paths inside the slug folder).
|
|
readonly DEV_FILES='^[^/]+/(\.git|\.agents|\.wordpress-org|\.distignore|\.distignore-wporg|\.gitattributes|\.gitignore|\.woodpecker\.yml|\.github|\.editorconfig|\.gitleaks\.toml|\.aidevops\.json|composer\.(json|lock)|phpcs\.xml(\.dist)?|phpstan(-baseline|-plugin)?\.neon(\.dist)?|vendor|AGENTS\.md|CONTRIBUTING\.md|DEVELOPMENT\.md|LAUNCH\.md|SECURITY\.md|STANDARDS\.md|RELEASING\.md|ROADMAP\.md|STABILITY\.md|TESTING\.md|scripts|dist|node_modules|reference-plugins|project-documents)(/|$)|(^|/)(\.DS_Store|__MACOSX|Thumbs\.db)(/|$)|\.(bak|log|orig|swp)$'
|
|
readonly README_MAX_BYTES=10240
|
|
readonly SHORT_DESC_MAX=150
|
|
readonly MAX_TAGS=5
|
|
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 from the main file at the ref (plugin_identity).
|
|
SLUG=""
|
|
MAIN_FILE=""
|
|
VERSION_CONSTANT=""
|
|
# Paths only in the GitHub build (.distignore-wporg): the GitHub updater.
|
|
UPDATER_FILES=""
|
|
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
TMP_DIR=""
|
|
OFFLINE=0
|
|
USE_DOCKER=1
|
|
CHANGELOG_TXT=""
|
|
|
|
die() {
|
|
local message="$1"
|
|
printf 'preflight: %s\n' "$message" >&2
|
|
exit 2
|
|
}
|
|
|
|
usage() {
|
|
sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
|
|
return 0
|
|
}
|
|
|
|
cleanup() {
|
|
if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then
|
|
rm -rf "$TMP_DIR"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
ok() {
|
|
local message="$1"
|
|
printf ' ok %s\n' "$message"
|
|
return 0
|
|
}
|
|
|
|
err() {
|
|
local message="$1"
|
|
printf ' ERROR %s\n' "$message"
|
|
ERRORS=$((ERRORS + 1))
|
|
return 0
|
|
}
|
|
|
|
warn() {
|
|
local message="$1"
|
|
printf ' warn %s\n' "$message"
|
|
WARNINGS=$((WARNINGS + 1))
|
|
return 0
|
|
}
|
|
|
|
note() {
|
|
local message="$1"
|
|
printf ' note %s\n' "$message"
|
|
return 0
|
|
}
|
|
|
|
section() {
|
|
local title="$1"
|
|
printf '\n%s\n' "$title"
|
|
return 0
|
|
}
|
|
|
|
# Value of "Key: value" in a header (plugin file comment or readme), case-insensitive.
|
|
field() {
|
|
local text="$1"
|
|
local key="$2"
|
|
printf '%s\n' "$text" | awk -v k="$key" '
|
|
BEGIN { k = tolower(k) }
|
|
{
|
|
line = $0
|
|
sub(/^[ \t*]*/, "", line)
|
|
i = index(line, ":")
|
|
if (i > 0 && tolower(substr(line, 1, i - 1)) == k) {
|
|
v = substr(line, i + 1)
|
|
gsub(/^[ \t]+|[ \t\r]+$/, "", v)
|
|
print v
|
|
exit
|
|
}
|
|
}'
|
|
return 0
|
|
}
|
|
|
|
# 1 if version a < b (numeric parts), else 0.
|
|
version_lt() {
|
|
local a="$1"
|
|
local b="$2"
|
|
awk -v a="$a" -v b="$b" 'BEGIN {
|
|
na = split(a, x, "."); nb = split(b, y, "."); n = (na > nb) ? na : nb
|
|
for (i = 1; i <= n; i++) { if ((x[i] + 0) < (y[i] + 0)) { print 1; exit } if ((x[i] + 0) > (y[i] + 0)) { print 0; exit } }
|
|
print 0 }'
|
|
return 0
|
|
}
|
|
|
|
# HTTP status of a URL (000 when offline or unreachable).
|
|
http_status() {
|
|
local url="$1"
|
|
local code
|
|
# curl prints 000 itself when it cannot connect, and exits non-zero.
|
|
code="$(curl -sL -o /dev/null -m 20 -w '%{http_code}' "$url" || true)"
|
|
printf '%s' "${code:-000}"
|
|
return 0
|
|
}
|
|
|
|
check_versions() {
|
|
local plugin_header="$1"
|
|
local readme="$2"
|
|
local main_php="$3"
|
|
section "Versions and headers"
|
|
|
|
local version constant stable
|
|
version="$(field "$plugin_header" "Version")"
|
|
constant="$(printf '%s\n' "$main_php" | sed -nE "/define\([[:space:]]*['\"]${VERSION_CONSTANT}['\"]/{s/.*,[[:space:]]*['\"]([^'\"]+)['\"].*/\1/p;q;}")"
|
|
stable="$(field "$readme" "Stable tag")"
|
|
|
|
if printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
|
|
ok "Version: $version (numbers only, so GitHub updates and WordPress.org treat it as stable)"
|
|
else
|
|
err "Version: '$version' is not X.Y.Z; pre-release versions on main are offered to sites still using Git Updater"
|
|
fi
|
|
if [ "$constant" = "$version" ]; then ok "version constant matches ($constant)"; else err "version constant is '$constant', Version: is '$version'"; fi
|
|
if [ "$stable" = "$version" ]; then ok "readme.txt Stable tag matches"; else err "readme.txt Stable tag is '$stable', Version: is '$version'"; fi
|
|
if [ "$(printf '%s' "$stable" | tr '[:upper:]' '[:lower:]')" = "trunk" ]; then err "Stable tag: trunk is not allowed for new plugins"; fi
|
|
|
|
local key header_value readme_value
|
|
for key in "Requires at least" "Requires PHP"; do
|
|
header_value="$(field "$plugin_header" "$key")"
|
|
readme_value="$(field "$readme" "$key")"
|
|
if [ -z "$header_value" ]; then
|
|
err "$MAIN_FILE has no '$key' header (WordPress reads it from the plugin file)"
|
|
elif [ -n "$readme_value" ] && [ "$header_value" != "$readme_value" ]; then
|
|
err "$key: $header_value in $MAIN_FILE, $readme_value in readme.txt"
|
|
else
|
|
ok "$key: $header_value"
|
|
fi
|
|
done
|
|
|
|
if printf '%s\n' "$main_php" | grep -Eiq '^[[:space:]*]*Update URI:'; then
|
|
err "Update URI header found: WordPress.org rejects it and Plugin Check reports an updater"
|
|
else
|
|
ok "no Update URI header"
|
|
fi
|
|
|
|
local domain license plugin_uri author_uri
|
|
domain="$(field "$plugin_header" "Text Domain")"
|
|
if [ "$domain" = "$SLUG" ]; then ok "Text Domain: $domain"; else err "Text Domain is '$domain'; it must be the slug ($SLUG) for language packs"; fi
|
|
license="$(field "$plugin_header" "License")"
|
|
if printf '%s' "$license" | grep -Eiq 'GPL'; then ok "License: $license"; else err "License '$license' is not GPL-compatible as written"; fi
|
|
plugin_uri="$(field "$plugin_header" "Plugin URI")"
|
|
author_uri="$(field "$plugin_header" "Author URI")"
|
|
if [ -n "$plugin_uri" ] && [ "$plugin_uri" = "$author_uri" ]; then
|
|
warn "Plugin URI and Author URI are the same ($plugin_uri); WordPress.org asks for a Plugin URI unique to the plugin, or none"
|
|
fi
|
|
VERSION="$version"
|
|
return 0
|
|
}
|
|
|
|
# Changelog text (a readme section or changelog.txt): an entry for the version,
|
|
# and no Unreleased section left.
|
|
check_changelog() {
|
|
local label="$1"
|
|
local text="$2"
|
|
local version="$3"
|
|
if printf '%s\n' "$text" | grep -Eq "^= *v?$version *="; then
|
|
ok "$label changelog has $version"
|
|
else
|
|
warn "$label changelog has no '= $version =' entry"
|
|
fi
|
|
if printf '%s\n' "$text" | grep -Eiq '^= *unreleased *='; then
|
|
warn "$label changelog still has an Unreleased section; name it $version when releasing"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
check_readme() {
|
|
local readme="$1"
|
|
local plugin_header="$2"
|
|
local version="$3"
|
|
section "readme.txt"
|
|
|
|
local bytes
|
|
bytes="$(printf '%s' "$readme" | wc -c | tr -d ' ')"
|
|
if [ "$bytes" -le "$README_MAX_BYTES" ]; then
|
|
ok "size ${bytes} bytes ($((README_MAX_BYTES - bytes)) left)"
|
|
else
|
|
warn "size ${bytes} bytes; WordPress.org says over 10 KB may cause errors (keep the current changelog, move older entries to changelog.txt, trim the description)"
|
|
fi
|
|
|
|
local name readme_name
|
|
name="$(field "$plugin_header" "Plugin Name")"
|
|
readme_name="$(printf '%s\n' "$readme" | sed -n '1s/^===[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*===.*/\1/p')"
|
|
if [ "$readme_name" = "$name" ]; then ok "name matches the plugin header ($name)"; else warn "readme name '$readme_name' differs from Plugin Name '$name'"; fi
|
|
|
|
local short
|
|
short="$(printf '%s\n' "$readme" | awk 'NR == 1 { next } !h && /^[ \t]*$/ { h = 1; next } h && /^==/ { exit } h && !/^[ \t]*$/ { print; exit }')"
|
|
if [ -z "$short" ]; then
|
|
err "no short description (the line after the header)"
|
|
elif [ "${#short}" -le "$SHORT_DESC_MAX" ]; then
|
|
ok "short description ${#short} characters"
|
|
else
|
|
warn "short description ${#short} characters; WordPress.org cuts it at $SHORT_DESC_MAX"
|
|
fi
|
|
|
|
local tags tag_count
|
|
tags="$(field "$readme" "Tags")"
|
|
tag_count="$(printf '%s\n' "$tags" | awk -F',' '{ print ($0 == "") ? 0 : NF }')"
|
|
if [ "$tag_count" -ge 1 ] && [ "$tag_count" -le "$MAX_TAGS" ]; then ok "$tag_count tags"; else warn "$tag_count tags; WordPress.org shows at most $MAX_TAGS"; fi
|
|
|
|
local tested
|
|
tested="$(field "$readme" "Tested up to")"
|
|
if printf '%s' "$tested" | grep -Eq '^[0-9]+\.[0-9]+$'; then
|
|
ok "Tested up to: $tested"
|
|
else
|
|
err "Tested up to '$tested' should be a major version such as 6.8"
|
|
fi
|
|
if [ "$OFFLINE" -eq 0 ]; then
|
|
local latest latest_major
|
|
# The first offer is the latest release; later ones are older branches.
|
|
latest="$(curl -s -m 20 'https://api.wordpress.org/core/version-check/1.7/' | grep -o '"current":"[0-9.]*"' | head -n 1 | cut -d'"' -f4 || true)"
|
|
latest_major="$(printf '%s' "$latest" | cut -d. -f1-2)"
|
|
if [ -z "$latest_major" ]; then
|
|
note "could not read the latest WordPress version"
|
|
elif [ "$(version_lt "$tested" "$latest_major")" = "1" ]; then
|
|
warn "Tested up to $tested, latest WordPress is $latest; test and raise it"
|
|
else
|
|
ok "Tested up to is the latest WordPress ($latest)"
|
|
fi
|
|
fi
|
|
|
|
check_changelog "readme.txt" "$(printf '%s\n' "$readme" | awk '/^== *[Cc]hangelog *==/ { c = 1; next } c && /^== / { exit } c')" "$version"
|
|
if [ -n "$CHANGELOG_TXT" ]; then
|
|
check_changelog "changelog.txt" "$CHANGELOG_TXT" "$version"
|
|
fi
|
|
if printf '%s\n' "$readme" | grep -Eiq '^== *upgrade notice *=='; then
|
|
if printf '%s\n' "$readme" | awk '/^== *[Uu]pgrade [Nn]otice *==/ { u = 1; next } u && /^== / { exit } u' | grep -Eq "^= *v?$version *="; then
|
|
ok "upgrade notice has $version"
|
|
else
|
|
note "no upgrade notice for $version (optional)"
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
check_wporg() {
|
|
local readme="$1"
|
|
local plugin_header="$2"
|
|
section "WordPress.org listing"
|
|
|
|
local name derived
|
|
name="$(field "$plugin_header" "Plugin Name")"
|
|
derived="$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g')"
|
|
if [ "$derived" = "$SLUG" ]; then
|
|
ok "the slug WordPress.org makes from '$name' is $SLUG"
|
|
else
|
|
warn "WordPress.org makes the slug from Plugin Name: '$name' becomes '$derived', not '$SLUG'. Ask for '$SLUG' in the submission notes (it can change only before approval), or the text domain will not match"
|
|
fi
|
|
if printf '%s' "$name" | grep -Eiq '^(wordpress|wp|woocommerce|woo|gutenberg)([^a-z]|$)'; then
|
|
warn "Plugin Name starts with a trademark ('$name'); WordPress.org does not allow that"
|
|
fi
|
|
|
|
if [ "$OFFLINE" -eq 1 ]; then
|
|
note "offline: slug and contributor profiles not checked"
|
|
return 0
|
|
fi
|
|
local info
|
|
info="$(curl -s -m 20 "https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&request%5Bslug%5D=$SLUG&request%5Bfields%5D%5Bsections%5D=0" || true)"
|
|
if printf '%s' "$info" | grep -q '"error"'; then
|
|
note "slug $SLUG is not listed on WordPress.org yet"
|
|
elif [ -n "$info" ]; then
|
|
note "slug $SLUG is already listed on WordPress.org; check it is this plugin"
|
|
fi
|
|
|
|
local contributors user status
|
|
contributors="$(field "$readme" "Contributors")"
|
|
if [ -z "$contributors" ]; then
|
|
err "readme.txt has no Contributors"
|
|
fi
|
|
for user in $(printf '%s' "$contributors" | tr ',' ' '); do
|
|
status="$(http_status "https://profiles.wordpress.org/$user/")"
|
|
case "$status" in
|
|
200) ok "contributor $user has a WordPress.org profile" ;;
|
|
000) note "could not check contributor $user" ;;
|
|
*) warn "contributor '$user' has no WordPress.org profile (HTTP $status); Contributors must be WordPress.org usernames, case-sensitive" ;;
|
|
esac
|
|
done
|
|
return 0
|
|
}
|
|
|
|
# Lint PHP (PHP 7.4 in Docker when possible) and JS in an unpacked build.
|
|
check_syntax() {
|
|
local dir="$1"
|
|
local label="$2"
|
|
local out php_label
|
|
local expected
|
|
expected="$(find "$dir" -name '*.php' | wc -l | tr -d ' ')"
|
|
if [ "$USE_DOCKER" -eq 1 ] && command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
local image="php:7.4-cli"
|
|
if docker image inspect wordpress:php7.4-apache >/dev/null 2>&1; then image="wordpress:php7.4-apache"; fi
|
|
out="$(docker run --rm -v "$dir:/src:ro" "$image" sh -c 'find /src -name "*.php" -exec php -l {} \;' 2>&1 || true)"
|
|
php_label="PHP 7.4 syntax ($image)"
|
|
elif command -v php >/dev/null 2>&1; then
|
|
out="$(find "$dir" -name '*.php' -exec php -l {} \; 2>&1 || true)"
|
|
php_label="PHP syntax ($(php -r 'echo PHP_VERSION;'); not 7.4)"
|
|
else
|
|
out=""
|
|
php_label=""
|
|
warn "$label: no PHP to lint with"
|
|
fi
|
|
if [ -n "$php_label" ]; then
|
|
# Count the files php -l passed, so a lint that never ran cannot pass.
|
|
local passed problems
|
|
passed="$(printf '%s\n' "$out" | grep -c '^No syntax errors' || true)"
|
|
problems="$(printf '%s\n' "$out" | grep -v '^No syntax errors' | grep -v '^[[:space:]]*$' || true)"
|
|
if [ -z "$problems" ] && [ "$passed" -eq "$expected" ] && [ "$expected" -gt 0 ]; then
|
|
ok "$label: $php_label, $passed files"
|
|
elif [ -z "$problems" ]; then
|
|
err "$label: PHP lint checked $passed of $expected files"
|
|
else
|
|
err "$label: $php_label errors:"
|
|
printf '%s\n' "$problems" | sed 's/^/ /'
|
|
fi
|
|
fi
|
|
if command -v node >/dev/null 2>&1; then
|
|
out="$(find "$dir" -name '*.js' -exec node --check {} \; 2>&1 || true)"
|
|
if [ -z "$out" ]; then ok "$label: JS syntax"; else err "$label: JS syntax errors:"; printf '%s\n' "$out" | sed 's/^/ /'; fi
|
|
else
|
|
warn "$label: node not found, JS not checked"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Shared checks for a release zip; prints its unpacked folder in UNPACKED.
|
|
check_zip() {
|
|
local zip_path="$1"
|
|
local label="$2"
|
|
local dir="$TMP_DIR/unpacked-$label"
|
|
local list tops dev size
|
|
list="$(unzip -Z1 "$zip_path")"
|
|
tops="$(printf '%s\n' "$list" | cut -d/ -f1 | sort -u | tr '\n' ' ')"
|
|
if [ "$tops" = "$SLUG " ]; then ok "$label: one $SLUG/ folder"; else err "$label: top level is '$tops', must be only $SLUG/"; fi
|
|
if printf '%s\n' "$list" | grep -qx "$SLUG/$MAIN_FILE"; then ok "$label: $SLUG/$MAIN_FILE present"; else err "$label: $SLUG/$MAIN_FILE missing"; fi
|
|
dev="$(printf '%s\n' "$list" | grep -E "$DEV_FILES" || true)"
|
|
if [ -z "$dev" ]; then ok "$label: no development files"; else err "$label: development files: $(printf '%s' "$dev" | tr '\n' ' ')"; fi
|
|
size="$(wc -c <"$zip_path" | tr -d ' ')"
|
|
note "$label: $(basename "$zip_path"), $((size / 1024)) KB, $(printf '%s\n' "$list" | grep -cv '/$') files"
|
|
mkdir -p "$dir"
|
|
unzip -q "$zip_path" -d "$dir"
|
|
check_syntax "$dir" "$label"
|
|
UNPACKED="$dir/$SLUG"
|
|
return 0
|
|
}
|
|
|
|
check_builds() {
|
|
local github_zip="$1"
|
|
local wporg_zip="$2"
|
|
local version="$3"
|
|
section "Release zips"
|
|
|
|
if [ "$(basename "$github_zip")" = "$SLUG-$version.zip" ]; then
|
|
ok "GitHub asset name $SLUG-$version.zip (sites pick assets starting with $SLUG)"
|
|
else
|
|
err "GitHub asset is $(basename "$github_zip"), expected $SLUG-$version.zip"
|
|
fi
|
|
case "$(basename "$wporg_zip")" in
|
|
"$SLUG"*) err "WordPress.org zip name starts with $SLUG; sites could install it from GitHub" ;;
|
|
*) ok "WordPress.org zip name does not start with $SLUG" ;;
|
|
esac
|
|
|
|
UNPACKED=""
|
|
check_zip "$github_zip" "github"
|
|
local github_dir="$UNPACKED" path
|
|
if [ -z "$UPDATER_FILES" ]; then
|
|
note "no .distignore-wporg: both builds hold the same files"
|
|
fi
|
|
for path in $UPDATER_FILES; do
|
|
if [ -e "$github_dir/$path" ]; then ok "github: has $path"; else err "github: $path missing (listed in .distignore-wporg)"; fi
|
|
done
|
|
if grep -Eq "^[[:space:]*]*GitHub Plugin URI:" "$github_dir/$MAIN_FILE"; then ok "github: has the GitHub Plugin URI header"; else err "github: no GitHub Plugin URI header, sites cannot update it from GitHub"; fi
|
|
|
|
check_zip "$wporg_zip" "wporg"
|
|
local wporg_dir="$UNPACKED"
|
|
for path in $UPDATER_FILES; do
|
|
if [ -e "$wporg_dir/$path" ]; then err "wporg: $path must not be in the WordPress.org build"; else ok "wporg: no $path"; fi
|
|
done
|
|
if grep -Eq "^[[:space:]*]*($UPDATER_HEADERS):" "$wporg_dir/$MAIN_FILE"; then err "wporg: GitHub updater header lines still in $MAIN_FILE"; else ok "wporg: no GitHub updater header lines"; fi
|
|
local hits
|
|
hits="$(grep -rEl --include='*.php' --include='*.js' 'gu_override_dot_org|api\.github\.com/repos|Plugin_Upgrader|Theme_Upgrader|site_transient_update_plugins|auto_update_(plugin|theme)' "$wporg_dir" 2>/dev/null | sed "s|^$wporg_dir/||" || true)"
|
|
if [ -z "$hits" ]; then
|
|
ok "wporg: no code that installs or updates plugins from elsewhere"
|
|
else
|
|
warn "wporg: check these install or update code (guideline 8 allows only WordPress.org sources): $(printf '%s' "$hits" | tr '\n' ' ')"
|
|
fi
|
|
|
|
section "Remote assets and services (WordPress.org build)"
|
|
hits="$(grep -rEn "(wp_(enqueue|register)_(script|style)|<script|<link)[^;]*['\"](https?:)?//" "$wporg_dir" --include='*.php' 2>/dev/null | sed "s|^$wporg_dir/||" | cut -c1-160 || true)"
|
|
if [ -z "$hits" ]; then ok "no scripts or styles loaded from other sites"; else warn "scripts or styles from other sites (guideline 8: ship them in the plugin unless they are part of a service):"; printf '%s\n' "$hits" | sed 's/^/ /'; fi
|
|
local readme_text hosts host missing=""
|
|
readme_text="$(cat "$wporg_dir/readme.txt")"
|
|
hosts="$(grep -rEoh 'https?://[A-Za-z0-9.-]+\.[a-z]{2,}' "$wporg_dir/includes" "$wporg_dir/blocks" 2>/dev/null | sed -E 's|https?://||; s|^www\.||' | sort -u || true)"
|
|
for host in $hosts; do
|
|
case "$host" in
|
|
w3.org | gnu.org | wordpress.org | *.wordpress.org | w.org | *.w.org | wp.org | *.wp.org | example.com | *.example.com) continue ;;
|
|
esac
|
|
if ! printf '%s' "$readme_text" | grep -Fqi "$host"; then missing="$missing $host"; fi
|
|
done
|
|
if [ -z "$missing" ]; then
|
|
ok "every host in includes/ and blocks/ is named in readme.txt"
|
|
else
|
|
note "hosts in code not named in readme.txt (fine if they are only links; services the plugin contacts need an External services entry):$missing"
|
|
fi
|
|
|
|
# Checks for parts only some plugins have; each runs when its files exist.
|
|
# A plugin's own checks: scripts/preflight-plugin.sh defines plugin_preflight,
|
|
# which gets the unpacked GitHub build and can use section, ok, warn and err.
|
|
if [ -f "$SCRIPT_DIR/preflight-plugin.sh" ]; then
|
|
# shellcheck source=/dev/null
|
|
. "$SCRIPT_DIR/preflight-plugin.sh"
|
|
plugin_preflight "$github_dir"
|
|
fi
|
|
if [ -f "$SCRIPT_DIR/replaced-plugins.php" ]; then
|
|
check_replaced_count "$github_dir"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# README.md's "<Plugin Name> replaces **N plugins**" line matches the
|
|
# 'replaces' entries in the code (scripts/replaced-plugins.php).
|
|
check_replaced_count() {
|
|
local dir="$1"
|
|
section "Replaced plugins"
|
|
if ! command -v php >/dev/null 2>&1; then
|
|
warn "php not found, the replaced plugins count in README.md was not checked"
|
|
return 0
|
|
fi
|
|
local out
|
|
if out="$(php "$SCRIPT_DIR/replaced-plugins.php" --check "$dir" 2>&1)"; then
|
|
ok "$out"
|
|
else
|
|
err "$out"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Core files match the starter's (scripts/sync-core.sh --check), when a
|
|
# checkout of the starter is at hand. A warning, not an error: the starter's
|
|
# checkout may be behind, and only a person can tell which side is right.
|
|
check_core_files() {
|
|
section "Core files"
|
|
local out status=0
|
|
out="$(bash "$SCRIPT_DIR/sync-core.sh" --check 2>&1)" || status=$?
|
|
case "$status" in
|
|
0) ok "$(printf '%s\n' "$out" | tail -n 1)" ;;
|
|
1) warn "$(printf '%s\n' "$out" | sed -n '2,$p' | tr '\n' ' ')" ;;
|
|
*) note "not compared: ${out#sync-core: }" ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
check_git() {
|
|
local ref="$1"
|
|
local sha="$2"
|
|
local version="$3"
|
|
section "Git"
|
|
local tag_sha
|
|
if tag_sha="$(git rev-parse --verify --quiet "refs/tags/v$version^{commit}")"; then
|
|
if [ "$tag_sha" = "$sha" ]; then ok "tag v$version is this commit"; else warn "tag v$version exists on another commit (${tag_sha:0:12}); bump the version"; fi
|
|
else
|
|
note "no tag v$version yet"
|
|
fi
|
|
local main_sha
|
|
if main_sha="$(git rev-parse --verify --quiet "refs/remotes/origin/main^{commit}")"; then
|
|
if git merge-base --is-ancestor "$sha" "$main_sha"; then ok "$ref is on origin/main"; else note "$ref is not on origin/main yet; release only from main"; fi
|
|
fi
|
|
if [ "$ref" = "HEAD" ] && [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
|
note "uncommitted changes are not checked (the build comes from HEAD)"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
main() {
|
|
local ref="HEAD"
|
|
local strict=0
|
|
local arg
|
|
while [ $# -gt 0 ]; do
|
|
arg="$1"
|
|
case "$arg" in
|
|
--ref)
|
|
[ $# -ge 2 ] || die "--ref needs a value"
|
|
ref="$2"
|
|
shift
|
|
;;
|
|
--offline) OFFLINE=1 ;;
|
|
--strict) strict=1 ;;
|
|
--no-docker) USE_DOCKER=0 ;;
|
|
-h | --help)
|
|
usage
|
|
return 0
|
|
;;
|
|
*) die "unknown argument: $arg" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
local root sha
|
|
root="$(git rev-parse --show-toplevel)" || die "run this inside a checkout of the plugin"
|
|
cd "$root"
|
|
sha="$(git rev-parse --verify --quiet "$ref^{commit}")" || die "not a commit: $ref"
|
|
plugin_identity "$sha" || die "cannot tell which plugin this is at $ref"
|
|
SLUG="$PLUGIN_SLUG"
|
|
MAIN_FILE="$PLUGIN_MAIN_FILE"
|
|
VERSION_CONSTANT="${PLUGIN_CONST}_VERSION"
|
|
UPDATER_FILES="$(plugin_wporg_only "$sha")"
|
|
|
|
trap cleanup EXIT
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/$SLUG-preflight.XXXXXX")"
|
|
|
|
local main_php readme plugin_header
|
|
main_php="$(git show "$sha:$MAIN_FILE")"
|
|
readme="$(git show "$sha:readme.txt")" || die "readme.txt missing at $ref"
|
|
if git cat-file -e "$sha:changelog.txt" 2>/dev/null; then
|
|
CHANGELOG_TXT="$(git show "$sha:changelog.txt")"
|
|
fi
|
|
plugin_header="$(printf '%s\n' "$main_php" | sed -n '1,/\*\//p')"
|
|
|
|
printf '%s preflight: %s (%s)\n' "$PLUGIN_NAME" "$ref" "${sha:0:12}"
|
|
VERSION=""
|
|
check_versions "$plugin_header" "$readme" "$main_php"
|
|
check_readme "$readme" "$plugin_header" "$VERSION"
|
|
check_wporg "$readme" "$plugin_header"
|
|
|
|
local zips github_zip wporg_zip
|
|
zips="$("$root/scripts/build-release.sh" --ref "$sha" --out "$TMP_DIR/dist" --quiet)" || die "build failed"
|
|
github_zip="$(printf '%s\n' "$zips" | sed -n 1p)"
|
|
wporg_zip="$(printf '%s\n' "$zips" | sed -n 2p)"
|
|
check_builds "$github_zip" "$wporg_zip" "$VERSION"
|
|
if [ -f "$SCRIPT_DIR/sync-core.sh" ]; then
|
|
check_core_files
|
|
fi
|
|
check_git "$ref" "$sha" "$VERSION"
|
|
|
|
printf '\n%s error(s), %s warning(s).\n' "$ERRORS" "$WARNINGS"
|
|
if [ "$ERRORS" -gt 0 ]; then
|
|
printf 'Not ready: fix the errors.\n'
|
|
return 1
|
|
fi
|
|
if [ "$strict" -eq 1 ] && [ "$WARNINGS" -gt 0 ]; then
|
|
printf 'Not ready (--strict): resolve or accept the warnings.\n'
|
|
return 1
|
|
fi
|
|
printf 'Next: scripts/plugin-check.sh (Plugin Check on both zips).\n'
|
|
return 0
|
|
}
|
|
|
|
main "$@"
|