Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .claude/hooks/session-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# Synchronous and instant: only writes static session environment variables.
# The slow dependency installs live in session-start.sh, which runs async.
set -euo pipefail

if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ] || [ -z "${CLAUDE_ENV_FILE:-}" ]; then
exit 0
fi

cd "${CLAUDE_PROJECT_DIR:-$(pwd)}"

# Database settings read by tests/php/wp-tests-config.php (must match session-start.sh).
{
echo 'export WP_TESTS_DB_HOST="127.0.0.1:3306"'
echo 'export WP_TESTS_DB_USER="root"'
echo 'export WP_TESTS_DB_PASSWORD="root"'
echo 'export WP_TESTS_DB_NAME="wordpress_test"'
echo "export PATH=\"$PWD/vendor/bin:\$PATH\""
} >> "$CLAUDE_ENV_FILE"
125 changes: 89 additions & 36 deletions .claude/hooks/session-start.sh
Original file line number Diff line number Diff line change
@@ -1,59 +1,112 @@
#!/bin/bash
# Prepares a Claude Code cloud session so linters and tests can run:
# Composer + npm dependencies, plus a local MariaDB for the WordPress PHPUnit suite.
set -euo pipefail
#
# Runs ASYNC: the session's first turn starts immediately while this installs in
# the background. A cold run takes ~10 minutes, almost all of it Composer: the
# session's GitHub proxy refuses archive (zipball/codeload) downloads for repos
# that aren't attached to the session, so every package is git-cloned instead.
# Progress goes to $LOG; $DONE is written when everything finished successfully.
set -uo pipefail

if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi

cd "${CLAUDE_PROJECT_DIR:-$(pwd)}"
# Tell Claude Code to run the rest of this hook in the background (30 min cap).
echo '{"async": true, "asyncTimeout": 1800000}'

# Database settings read by tests/php/wp-tests-config.php.
cd "${CLAUDE_PROJECT_DIR:-$(pwd)}" || exit 1

LOG=/tmp/rfsd-session-setup.log
DONE=/tmp/rfsd-session-setup.done
rm -f "$DONE"
exec >>"$LOG" 2>&1
echo "=== session setup started $(date -u +%FT%TZ)"

# Database settings read by tests/php/wp-tests-config.php (must match session-env.sh).
DB_NAME="wordpress_test"
DB_USER="root"
DB_PASS="root"

# --- PHP dependencies (phpcs, phpunit, WordPress test suite) ---
# Cloud sessions run as root; without this Composer disables plugins, so the
# phpcs installer plugin never registers the WordPress coding standard.
export COMPOSER_ALLOW_SUPERUSER=1
composer install --no-interaction --no-progress --prefer-dist
failed=()

# --- JS dependencies (jest) ---
npm install --no-audit --no-fund
# Runs a step, logging its duration; a failed step is recorded and the rest continue.
step() {
local name=$1
shift
local start=$SECONDS
echo "--- $name"
if "$@"; then
echo "--- $name ok ($((SECONDS - start))s)"
else
echo "--- $name FAILED ($((SECONDS - start))s)"
failed+=("$name")
fi
}

# --- MariaDB for the PHPUnit integration tests ---
if ! command -v mariadbd >/dev/null 2>&1 && ! command -v mysqld >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq mariadb-server
fi
# Skips an install when the files that decide it are unchanged since it last succeeded.
# The hash file lives in the git-ignored directory the install fills.
up_to_date() {
sha256sum --check --status "$1" 2>/dev/null
}

if ! mysqladmin ping --silent >/dev/null 2>&1; then
service mariadb start 2>/dev/null || service mysql start
for _ in $(seq 1 30); do
mysqladmin ping --silent >/dev/null 2>&1 && break
sleep 1
done
fi
composer_install() {
local hashes=vendor/.session-start.sha256
up_to_date "$hashes" && { echo "vendor/ up to date"; return 0; }
rm -f "$hashes"
# Cloud sessions run as root; without this Composer disables plugins, so the
# phpcs installer plugin never registers the WordPress coding standard.
# --prefer-source: dist archives from GitHub are always refused by the session
# proxy (403), so skip straight to git clones instead of failing 43 downloads.
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-interaction --no-progress --prefer-source &&
sha256sum composer.json composer.lock .claude/hooks/session-start.sh > "$hashes"
}

npm_install() {
local hashes=node_modules/.session-start.sha256
up_to_date "$hashes" && { echo "node_modules/ up to date"; return 0; }
rm -f "$hashes"
npm install --no-audit --no-fund &&
sha256sum package.json package-lock.json > "$hashes"
}

# Idempotent: password-authenticated root over TCP and a fresh test database.
if ! mysql -u"$DB_USER" -p"$DB_PASS" -e "CREATE DATABASE IF NOT EXISTS $DB_NAME" >/dev/null 2>&1; then
mariadb_setup() {
if ! command -v mariadbd >/dev/null 2>&1 && ! command -v mysqld >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get update -qq &&
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq mariadb-server || return 1
fi

if ! mysqladmin ping --silent >/dev/null 2>&1; then
service mariadb start 2>/dev/null || service mysql start || return 1
for _ in $(seq 1 30); do
mysqladmin ping --silent >/dev/null 2>&1 && break
sleep 1
done
fi

# Idempotent: password-authenticated root over TCP and a fresh test database.
# The check must use TCP (-h 127.0.0.1) like PHPUnit does: over the Unix socket,
# MariaDB's unix_socket auth lets OS root in with any password, so the check
# would pass without the password ever being set.
mysql -h127.0.0.1 -P3306 -u"$DB_USER" -p"$DB_PASS" -e "CREATE DATABASE IF NOT EXISTS $DB_NAME" >/dev/null 2>&1 && return 0
mysql -uroot <<SQL
ALTER USER '$DB_USER'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('$DB_PASS');
ALTER USER '$DB_USER'@'localhost' IDENTIFIED VIA unix_socket OR mysql_native_password USING PASSWORD('$DB_PASS');
CREATE DATABASE IF NOT EXISTS $DB_NAME;
FLUSH PRIVILEGES;
SQL
fi
}

# Quick steps first (seconds each), so Jest and the database are ready long
# before the slow Composer step finishes.
step "npm install" npm_install
step "mariadb" mariadb_setup
step "composer install" composer_install

# Persist DB settings for the session so `vendor/bin/phpunit` works as-is.
if [ -n "${CLAUDE_ENV_FILE:-}" ]; then
{
echo 'export WP_TESTS_DB_HOST="127.0.0.1:3306"'
echo "export WP_TESTS_DB_USER=\"$DB_USER\""
echo "export WP_TESTS_DB_PASSWORD=\"$DB_PASS\""
echo "export WP_TESTS_DB_NAME=\"$DB_NAME\""
echo "export PATH=\"$PWD/vendor/bin:\$PATH\""
} >> "$CLAUDE_ENV_FILE"
if [ ${#failed[@]} -eq 0 ]; then
date -u +%FT%TZ > "$DONE"
echo "=== session setup finished OK $(date -u +%FT%TZ)"
else
echo "=== session setup finished with failures: ${failed[*]} $(date -u +%FT%TZ)"
exit 1
fi
7 changes: 6 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-env.sh",
"timeout": 30
},
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh",
"timeout": 1800
"timeout": 60
}
]
}
Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ composer build-zip && composer check-zip # release zip from `git ar
composer bump-patch # or bump-minor / bump-major
```

Cloud sessions install dependencies in the background:

- `.claude/hooks/session-start.sh` installs Composer, npm and MariaDB. A cold run takes about 10 minutes.
- Before linting or testing, check that `/tmp/rfsd-session-setup.done` exists.
- If it doesn't, follow `/tmp/rfsd-session-setup.log` and wait. Don't run `composer install` yourself.

PHPUnit runs against a real MySQL/MariaDB database that the suite **wipes on every run**. Settings come from `.env.testing` (copy `.env.testing.example`); shell env vars win. Tests are loaded by `tests/php/bootstrap.php`, which loads the plugin on `muplugins_loaded` and calls `rfsd_activate()` on `init`. Extend `RFSD_TestCase` (`tests/php/class-rfsd-testcase.php`, has `create_faq()` helpers) or `RFSD_Multisite_TestCase`. Tag network-only tests `@group ms-required` and single-site-only tests `@group ms-excluded`; each config excludes the other group. PHPUnit converts notices, warnings and deprecations to failures. WP-CLI is stubbed in `tests/php/stubs/class-wp-cli.php`.

## Architecture
Expand Down
Loading