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
4 changes: 3 additions & 1 deletion docs/recipe-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ the mounted plugin slug.
Supported `loadAs` values:

- `plugin`: mount below `/wordpress/wp-content/plugins/<slug>` and activate when
`activate` is not `false`.
`activate` is not `false`. Composer autoloaders are preloaded only for these
active plugin inputs; an inactive plugin is mounted without executing its
`vendor/autoload.php`.
- `mu-plugin`: mount below
`/wordpress/wp-content/mu-plugins/wp-codebox-runtime/<slug>` and load through
WP Codebox's generated MU-plugin loader. Use this for sandbox runtime
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
"test:composer-package-overlay-revision": "tsx scripts/composer-backed-source-hydration-smoke.ts",
"test:composer-package-overlay-autoload-layout": "tsx scripts/composer-package-overlay-autoload-layout-smoke.ts",
"test:composer-installed-versions-loader-order": "tsx scripts/composer-installed-versions-loader-order-smoke.ts",
"test:recipe-extra-plugin-composer-autoloaders": "tsx tests/recipe-extra-plugin-composer-autoloaders.test.ts",
"test:runtime-preset-registry": "tsx tests/runtime-preset-registry.test.ts",
"test:generic-ability-runtime-run": "tsx tests/generic-ability-runtime-run.test.ts",
"test:provider-runtime-contracts": "tsx tests/provider-runtime-contracts.test.ts",
Expand Down
13 changes: 9 additions & 4 deletions packages/cli/src/recipe-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1796,14 +1796,15 @@ echo wp_json_encode(array('command' => 'install-mu-plugins', 'plugins' => $plugi

export function installPluginComposerAutoloadersCode(extraPlugins: PreparedExtraPlugin[]): string | null {
const plugins = extraPlugins
.filter((plugin) => plugin.loadAs === "plugin")
.filter((plugin) => plugin.loadAs === "plugin" && plugin.activate)
.map((plugin) => plugin.pluginFile)

if (plugins.length === 0) {
return null
}

return `$plugins = ${JSON.stringify(plugins)};
$autoloaders = array();
if (!is_dir(WPMU_PLUGIN_DIR) && !mkdir(WPMU_PLUGIN_DIR, 0777, true) && !is_dir(WPMU_PLUGIN_DIR)) {
throw new RuntimeException('Could not create mu-plugins directory.');
}
Expand All @@ -1828,16 +1829,20 @@ foreach ($plugins as $plugin) {
}
$package_autoload = WP_PLUGIN_DIR . '/' . $plugin_dir . '/vendor/autoload_packages.php';
if (is_file($package_autoload)) {
$lines[] = "require_once WP_PLUGIN_DIR . '/" . str_replace("'", "\\'", $plugin_dir) . "/vendor/autoload_packages.php';";
$autoload_path = $plugin_dir . '/vendor/autoload_packages.php';
$autoloaders[] = $autoload_path;
$lines[] = "require_once WP_PLUGIN_DIR . '/" . str_replace("'", "\\'", $autoload_path) . "';";
continue;
}
$autoload = WP_PLUGIN_DIR . '/' . $plugin_dir . '/vendor/autoload.php';
if (is_file($autoload)) {
$lines[] = "require_once WP_PLUGIN_DIR . '/" . str_replace("'", "\\'", $plugin_dir) . "/vendor/autoload.php';";
$autoload_path = $plugin_dir . '/vendor/autoload.php';
$autoloaders[] = $autoload_path;
$lines[] = "require_once WP_PLUGIN_DIR . '/" . str_replace("'", "\\'", $autoload_path) . "';";
}
}
if (false === file_put_contents($loader, implode("\n", $lines) . "\n")) {
throw new RuntimeException('Could not write WP Codebox Composer autoloader loader.');
}
echo wp_json_encode(array('command' => 'install-composer-autoloaders', 'plugins' => $plugins, 'loader' => $loader), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);`
echo wp_json_encode(array('command' => 'install-composer-autoloaders', 'plugins' => $plugins, 'autoloaders' => $autoloaders, 'loader' => $loader), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);`
}
78 changes: 78 additions & 0 deletions tests/recipe-extra-plugin-composer-autoloaders.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import assert from "node:assert/strict"
import { execFile as execFileCallback } from "node:child_process"
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { promisify } from "node:util"

import { installPluginComposerAutoloadersCode, type PreparedExtraPlugin } from "../packages/cli/src/recipe-sources.js"

const execFile = promisify(execFileCallback)
const root = await mkdtemp(join(tmpdir(), "wp-codebox-extra-plugin-autoloaders-"))
const pluginsDir = join(root, "plugins")
const muPluginsDir = join(root, "mu-plugins")

function plugin(slug: string, activate: boolean): PreparedExtraPlugin {
return {
source: join(pluginsDir, slug),
slug,
target: `/wordpress/wp-content/plugins/${slug}`,
pluginFile: `${slug}/${slug}.php`,
activate,
loadAs: "plugin",
cleanupPaths: [],
provenance: { kind: "local", original: join(pluginsDir, slug) },
}
}

try {
await mkdir(join(pluginsDir, "active-provider", "vendor"), { recursive: true })
await mkdir(join(pluginsDir, "inactive-plugin", "vendor"), { recursive: true })
await mkdir(join(pluginsDir, "inactive-plugin", "tests"), { recursive: true })
await mkdir(muPluginsDir, { recursive: true })

await writeFile(join(pluginsDir, "active-provider", "vendor", "autoload.php"), "<?php function wp_codebox_active_provider_loaded() { return true; }\n")
await writeFile(join(pluginsDir, "inactive-plugin", "composer.json"), JSON.stringify({
autoload: { "psr-4": { "InactivePlugin\\\\": "src/" } },
"autoload-dev": { files: ["tests/autoload-dev.php"] },
}))
await writeFile(join(pluginsDir, "inactive-plugin", "tests", "autoload-dev.php"), "<?php function wp_codebox_inactive_plugin_dev_sentinel() { return true; }\n")
await writeFile(join(pluginsDir, "inactive-plugin", "vendor", "autoload.php"), "<?php require_once dirname(__DIR__) . '/tests/autoload-dev.php';\n")

const installCode = installPluginComposerAutoloadersCode([
plugin("active-provider", true),
plugin("inactive-plugin", false),
])
assert.ok(installCode)
assert.doesNotMatch(installCode, /inactive-plugin/, "inactive plugin inputs are absent from the generated preload contract")

const php = `
function wp_json_encode($value, $options = 0) { return json_encode($value, $options); }
define('ABSPATH', ${JSON.stringify(`${root}/`)});
define('WP_PLUGIN_DIR', ${JSON.stringify(pluginsDir)});
define('WPMU_PLUGIN_DIR', ${JSON.stringify(muPluginsDir)});
${installCode}
echo "\n---RESULT---\n";
require WPMU_PLUGIN_DIR . '/wp-codebox-composer-autoloaders.php';
echo json_encode(array(
'active_provider_loaded' => function_exists('wp_codebox_active_provider_loaded'),
'inactive_dev_sentinel_loaded' => function_exists('wp_codebox_inactive_plugin_dev_sentinel'),
));
`
const { stdout } = await execFile("php", ["-r", php])
const [evidenceJson, resultJson] = stdout.split("\n---RESULT---\n")
assert.deepEqual(JSON.parse(evidenceJson), {
command: "install-composer-autoloaders",
plugins: ["active-provider/active-provider.php"],
autoloaders: ["active-provider/vendor/autoload.php"],
loader: join(muPluginsDir, "wp-codebox-composer-autoloaders.php"),
}, "setup evidence names each intentionally preloaded autoloader")
assert.deepEqual(JSON.parse(resultJson), {
active_provider_loaded: true,
inactive_dev_sentinel_loaded: false,
})

console.log("recipe extra plugin composer autoloaders ok")
} finally {
await rm(root, { recursive: true, force: true })
}
Loading