diff --git a/tools/docker/mu-plugins/fix-monorepo-plugins-url.php b/tools/docker/mu-plugins/fix-monorepo-plugins-url.php index afc7285de3b4..44bd0f613888 100644 --- a/tools/docker/mu-plugins/fix-monorepo-plugins-url.php +++ b/tools/docker/mu-plugins/fix-monorepo-plugins-url.php @@ -35,9 +35,20 @@ function jetpack_docker_plugins_url( $url, $path, $plugin ) { $packages = ( new Monorepo() )->get( 'packages' ); if ( strpos( $url, $packages ) !== false && strpos( $plugin, $packages ) === 0 ) { - // Look through available monorepo plugins until we find one with the plugin symlink. - $suffix1 = '/jetpack_vendor/automattic/jetpack-' . substr( $plugin, strlen( $packages ) ); - $suffix2 = '/vendor/automattic/jetpack-' . substr( $plugin, strlen( $packages ) ); + // The vendor symlink follows the package's composer name, which usually — + // but not always — matches the directory name (e.g., packages/scan/ has + // composer name automattic/jetpack-scan-page). Read composer.json to find + // the actual name; fall back to the directory-name convention when the + // composer.json can't be read. + $relative = substr( $plugin, strlen( $packages ) ); + $slash_pos = strpos( $relative, '/' ); + $package_dir = false === $slash_pos ? $relative : substr( $relative, 0, $slash_pos ); + $sub_path = false === $slash_pos ? '' : substr( $relative, $slash_pos ); + $composer_name = jetpack_docker_resolve_composer_name( $packages . $package_dir ) + ?? 'automattic/jetpack-' . $package_dir; + + $suffix1 = '/jetpack_vendor/' . $composer_name . $sub_path; + $suffix2 = '/vendor/' . $composer_name . $sub_path; $real_plugin = realpath( $plugin ); if ( false !== $real_plugin ) { foreach ( $wp_plugin_paths as $dir ) { @@ -54,3 +65,32 @@ function jetpack_docker_plugins_url( $url, $path, $plugin ) { return $url; } add_filter( 'plugins_url', __NAMESPACE__ . '\jetpack_docker_plugins_url', 1, 3 ); + +/** + * Resolve a monorepo package's composer name from its composer.json. + * + * Cached per-request so a single page load doesn't re-read the same composer.json + * for every URL the package generates. + * + * @param string $package_dir Absolute path to the package directory. + * @return string|null The composer name (e.g., "automattic/jetpack-scan-page"), + * or null if composer.json is missing/unreadable/malformed. + */ +function jetpack_docker_resolve_composer_name( $package_dir ) { + static $cache = array(); + if ( array_key_exists( $package_dir, $cache ) ) { + return $cache[ $package_dir ]; + } + + $name = null; + $composer_path = $package_dir . '/composer.json'; + if ( is_readable( $composer_path ) ) { + $contents = json_decode( file_get_contents( $composer_path ), true ); + if ( ! empty( $contents['name'] ) && is_string( $contents['name'] ) ) { + $name = $contents['name']; + } + } + + $cache[ $package_dir ] = $name; + return $name; +}