Description of the bug
Summary
Rsync_Installation::is_within_wordpress_install() decides whether the framework is
already running inside an installed WordPress by string-matching its own vendored path:
// src/mantle/testing/concerns/trait-rsync-installation.php
public function is_within_wordpress_install(): bool {
return false !== strpos( __DIR__, '/wp-content/' );
}
__DIR__ here is the framework's own install location
(.../wp-content/.../vendor/alleyinteractive/mantle-framework/...). In any project laid
out with the repository root named or nested under wp-content — common in WordPress
VIP / wp-content-rooted monorepos — this string is always present, so the method returns
true even when there is no WordPress core above the checkout.
Because this method guards several builder calls, they all become silent no-ops.
Affected version
v1.19.3 (ref d37a354fde52bc27395e86f47295a9b721f8e782). The check is unchanged on
current main at the time of writing.
Guarded call sites (all early-return $this with no error/log)
maybe_rsync()
with_vip_mu_plugins()
with_object_cache()
with_sqlite()
Example:
public function with_sqlite( bool $install = true ): static {
if ( $this->is_within_wordpress_install() ) {
return $this; // <-- silently does nothing for wp-content-rooted repos
}
putenv( 'MANTLE_USE_SQLITE=' . ( $install ? '1' : '0' ) );
// ...
}
Impact
For wp-content-rooted checkouts, ->maybe_rsync_wp_content()->with_sqlite() in a test
bootstrap does nothing — the suite silently runs against the in-place MySQL DB instead
of an rsynced SQLite install. There is:
- No error and no log line indicating the calls were skipped, and
- No public override to force the "not within an install" branch.
So the documented "Using SQLite for the database" flow
(https://mantle.alley.com/docs/testing/installation-manager#using-sqlite-for-the-database)
is unreachable in these layouts without editing vendored code.
Reproduction
- Check out any project whose path contains
/wp-content/ above the vendor dir
(e.g. repo root is wp-content/, Mantle at
wp-content/.../vendor/alleyinteractive/mantle-framework), with no wp-load.php
above wp-content.
- Test bootstrap:
use function Mantle\Testing\manager;
manager()->maybe_rsync_wp_content()->with_sqlite()->install();
- Run PHPUnit on the host. Expected: rsync to a fresh install + SQLite drop-in.
Actual: both calls are no-ops; the suite runs in place against MySQL. No indication
that rsync/SQLite were skipped.
Root cause
strpos( __DIR__, '/wp-content/' ) is a proxy for "am I inside a real WP install," but it
matches on a directory name rather than the presence of WordPress itself, so it collides
with wp-content-rooted repo layouts.
Proposed fix (suggestion)
Keep the fast string check as a cheap negative, then confirm an actual WP core exists above
wp-content. This also preserves the method's second role as the rsync recursion guard
— the rsynced scratch copy does have wp-load.php above it, so it still reads as "within
an install" and won't re-rsync:
public function is_within_wordpress_install(): bool {
if ( false === strpos( __DIR__, '/wp-content/' ) ) {
return false;
}
$parent = preg_replace( '/\/wp-content\/.*$/', '', __DIR__ );
return is_file( $parent . '/wp-load.php' );
}
Alternatives worth considering (maintainer's call):
- Walk up from
__DIR__ looking for wp-load.php rather than assuming the wp-content
segment is the boundary (handles non-standard core locations).
- Honor an explicit env/override (e.g.
MANTLE_WITHIN_WP_INSTALL=0) so callers can force
the rsync path deterministically.
- At minimum,
log()/emit a notice when a guarded call is skipped because the gate is
true, so the no-op is not silent.
Steps To Reproduce
See above.
Additional Information
No response
Description of the bug
Summary
Rsync_Installation::is_within_wordpress_install()decides whether the framework isalready running inside an installed WordPress by string-matching its own vendored path:
__DIR__here is the framework's own install location(
.../wp-content/.../vendor/alleyinteractive/mantle-framework/...). In any project laidout with the repository root named or nested under
wp-content— common in WordPressVIP / wp-content-rooted monorepos — this string is always present, so the method returns
trueeven when there is no WordPress core above the checkout.Because this method guards several builder calls, they all become silent no-ops.
Affected version
v1.19.3(refd37a354fde52bc27395e86f47295a9b721f8e782). The check is unchanged oncurrent
mainat the time of writing.Guarded call sites (all early-return
$thiswith no error/log)maybe_rsync()with_vip_mu_plugins()with_object_cache()with_sqlite()Example:
Impact
For wp-content-rooted checkouts,
->maybe_rsync_wp_content()->with_sqlite()in a testbootstrap does nothing — the suite silently runs against the in-place MySQL DB instead
of an rsynced SQLite install. There is:
So the documented "Using SQLite for the database" flow
(https://mantle.alley.com/docs/testing/installation-manager#using-sqlite-for-the-database)
is unreachable in these layouts without editing vendored code.
Reproduction
/wp-content/above the vendor dir(e.g. repo root is
wp-content/, Mantle atwp-content/.../vendor/alleyinteractive/mantle-framework), with nowp-load.phpabove
wp-content.Actual: both calls are no-ops; the suite runs in place against MySQL. No indication
that rsync/SQLite were skipped.
Root cause
strpos( __DIR__, '/wp-content/' )is a proxy for "am I inside a real WP install," but itmatches on a directory name rather than the presence of WordPress itself, so it collides
with wp-content-rooted repo layouts.
Proposed fix (suggestion)
Keep the fast string check as a cheap negative, then confirm an actual WP core exists above
wp-content. This also preserves the method's second role as the rsync recursion guard— the rsynced scratch copy does have
wp-load.phpabove it, so it still reads as "withinan install" and won't re-rsync:
Alternatives worth considering (maintainer's call):
__DIR__looking forwp-load.phprather than assuming thewp-contentsegment is the boundary (handles non-standard core locations).
MANTLE_WITHIN_WP_INSTALL=0) so callers can forcethe rsync path deterministically.
log()/emit a notice when a guarded call is skipped because the gate istrue, so the no-op is not silent.Steps To Reproduce
See above.
Additional Information
No response