#!/usr/bin/env bash # # Production release packaging -- docs/design/production-distribution-and-updates.md # sections 1-3, 16, 18. Builds the public download ZIP from a clean git # checkout (git archive -- never the working tree, so uncommitted local # files can never leak into a release), strips comments/whitespace # (not obfuscation -- see strip-comments.php's own header), and audits # the result against an explicit allowlist before packaging, failing # closed on anything unexpected rather than silently shipping it. # # Usage: bin/build-release.sh [git-ref] (defaults to HEAD) # set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REF="${1:-HEAD}" BUILD_DIR="$(mktemp -d)" STAGE_DIR="${BUILD_DIR}/argus-wordpress-defence" OUT_DIR="${REPO_ROOT}/dist" cleanup() { rm -rf "${BUILD_DIR}"; } trap cleanup EXIT echo "==> Packaging ${REF} from a clean checkout (git archive, not the working tree)" mkdir -p "${STAGE_DIR}" git -C "${REPO_ROOT}" archive "${REF}" | tar -x -C "${STAGE_DIR}" echo "==> Removing dev-only paths not meant for the production artifact" rm -rf \ "${STAGE_DIR}/docs" \ "${STAGE_DIR}/tests" \ "${STAGE_DIR}/README.md" \ "${STAGE_DIR}/ARGUS_WORDPRESS_SECURITY_ARCHITECTURE.md" \ "${STAGE_DIR}/.gitignore" \ "${STAGE_DIR}/bin" echo "==> Allowlist audit -- fail closed on anything unexpected" UNEXPECTED=0 while IFS= read -r -d '' item; do rel="${item#"${STAGE_DIR}"/}" case "$rel" in argus-wordpress-defence.php|uninstall.php|readme.txt) ;; admin|admin/*|includes|includes/*|mu-loader|mu-loader/*|assets|assets/*|languages|languages/*) ;; *) if [ -f "$item" ]; then echo " UNEXPECTED FILE: ${rel}" UNEXPECTED=1 fi ;; esac done < <(find "${STAGE_DIR}" -print0) # Reject known-forbidden patterns explicitly, even inside allowlisted dirs. if find "${STAGE_DIR}" \( -name "*.key" -o -name "*.pem" -o -name ".env*" -o -name "*.map" \) | grep -q .; then echo " FORBIDDEN FILE TYPE found (key/pem/.env/source map)" UNEXPECTED=1 fi if [ "${UNEXPECTED}" -ne 0 ]; then echo "==> BUILD FAILED: allowlist audit found unexpected content. Nothing was packaged." exit 1 fi echo " clean -- only allowlisted paths present" # Read the version BEFORE stripping -- it lives inside the plugin # header's own DocBlock comment, which php_strip_whitespace() below # would otherwise remove before this ever got a chance to read it. VERSION="$(grep -oP '(?<=Version:)\s*\K\S+' "${STAGE_DIR}/argus-wordpress-defence.php" | head -1)" if [ -z "${VERSION}" ]; then echo "==> BUILD FAILED: could not read plugin version from argus-wordpress-defence.php" exit 1 fi echo "==> Packaging version ${VERSION}" echo "==> Stripping comments/whitespace (not obfuscation -- see bin/strip-comments.php)" php "${REPO_ROOT}/bin/strip-comments.php" "${STAGE_DIR}" mkdir -p "${OUT_DIR}" ZIP_PATH="${OUT_DIR}/argus-wordpress-defence-${VERSION}.zip" rm -f "${ZIP_PATH}" # Prefer the `zip` CLI when present (most CI runners have it); fall # back to PHP's ZipArchive (bin/zip-directory.php) for a host that # only has the PHP extension -- either is a real, complete archive, no # feature difference between the two paths. if command -v zip >/dev/null 2>&1; then ( cd "${BUILD_DIR}" && zip -rq "${ZIP_PATH}" "argus-wordpress-defence" ) else php "${REPO_ROOT}/bin/zip-directory.php" "${STAGE_DIR}" "${ZIP_PATH}" "argus-wordpress-defence" fi SHA256="$(sha256sum "${ZIP_PATH}" | cut -d' ' -f1)" echo "${SHA256} $(basename "${ZIP_PATH}")" > "${ZIP_PATH}.sha256" echo "==> Done" echo " ${ZIP_PATH}" echo " SHA-256: ${SHA256}" echo " Size: $(du -h "${ZIP_PATH}" | cut -f1)"