Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ to your unique WordPress setup. With the Playground CLI, you can use the followi
- **`server`**: (Advanced) Starts a local WordPress server with full manual control over configuration.
- **`run-blueprint`**: Executes a Blueprint file without starting a web server.
- **`build-snapshot`**: Builds a ZIP snapshot of a WordPress site based on a Blueprint.
- **`php`**: Runs a PHP script.

The `start` command supports these common optional arguments. Run
`npx @wp-playground/cli@latest start --help` for the full list:
Expand All @@ -291,6 +292,14 @@ The `start` command supports these common optional arguments. Run
- `--reset`: Delete the stored site and start fresh. Defaults to false.
- `--no-auto-mount`: Disable automatic project detection.

The `php` command supports the shared runtime and mount options plus:

- `--cwd=<vfs-path>`: Set the PHP process working directory to a virtual filesystem path.

When `--cwd` is omitted, a single auto-mounted directory becomes the working directory
automatically. With manual mounts or multiple auto-mounts, PHP keeps its default
working directory unless you provide `--cwd`.

The `server` command supports these common optional arguments. Run `npx @wp-playground/cli@latest server --help` for the full list:

- `--port=<port>`: The port number for the server to listen on. Defaults to 9400.
Expand Down
29 changes: 10 additions & 19 deletions packages/docs/site/docs/main/guides/phpunit-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,27 @@ This guide assumes your plugin or theme has PHPUnit installed via Composer (`com

## Running tests

From your plugin or theme directory, run the following command. Replace `themes/THEME_NAME` with the path to your plugin or theme:
From your plugin or theme directory, run the following command:

```bash
npx @wp-playground/cli@latest php \
--auto-mount \
-- \
/wordpress/wp-content/themes/THEME_NAME/vendor/bin/phpunit \
-c /wordpress/wp-content/themes/THEME_NAME/phpunit.xml.dist
vendor/bin/phpunit \
-c phpunit.xml.dist
```

The `--auto-mount` flag detects whether the current directory is a plugin, theme, or WordPress installation and mounts it at the correct path under `/wordpress/wp-content/`. The `--` separates CLI flags from arguments passed to the PHP interpreter, and you can pass any arguments supported by your PHPUnit configuration.
The `--auto-mount` flag detects whether the current directory is a plugin, theme, or WordPress installation, mounts it at the correct path under `/wordpress/wp-content/`, and uses that single mount as the PHP working directory. The same relative command works for both plugins and themes. The `--` separates CLI flags from arguments passed to the PHP interpreter, and you can pass any arguments supported by your PHPUnit configuration.

For a plugin, the path would use `plugins/` instead:

```bash
npx @wp-playground/cli@latest php \
--auto-mount \
-- \
/wordpress/wp-content/plugins/MY_PLUGIN/vendor/bin/phpunit \
-c /wordpress/wp-content/plugins/MY_PLUGIN/phpunit.xml.dist
```

You can also use `--mount` to explicitly map a local directory to a path inside the Playground filesystem:
You can also use `--mount` to explicitly map a local directory to a path inside the Playground filesystem. Set `--cwd` to that virtual path when you want to use relative command arguments:

```bash
npx @wp-playground/cli@latest php \
--mount=.:/wordpress/wp-content/plugins/MY_PLUGIN \
--cwd=/wordpress/wp-content/plugins/MY_PLUGIN \
-- \
/wordpress/wp-content/plugins/MY_PLUGIN/vendor/bin/phpunit \
-c /wordpress/wp-content/plugins/MY_PLUGIN/phpunit.xml.dist
vendor/bin/phpunit \
-c phpunit.xml.dist
```

## Choosing PHP and WordPress versions
Expand All @@ -57,8 +48,8 @@ npx @wp-playground/cli@latest php \
--php=8.1 \
--wp=6.5 \
-- \
/wordpress/wp-content/plugins/MY_PLUGIN/vendor/bin/phpunit \
-c /wordpress/wp-content/plugins/MY_PLUGIN/phpunit.xml.dist
vendor/bin/phpunit \
-c phpunit.xml.dist
```

Supported PHP versions range from 7.4 to 8.5. For WordPress, you can use a specific version number, `latest`, `nightly`, or `beta`.
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/universal/src/lib/php-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export class PHPWorker implements LimitedPHPApi, AsyncDisposable {
/** @inheritDoc @php-wasm/universal!/PHP.cli */
async cli(
argv: string[],
options?: { env?: Record<string, string> }
options?: { env?: Record<string, string>; cwd?: string }
): Promise<StreamedPHPResponse> {
const state = _private.get(this)!;
const primaryPhp = state.php;
Expand Down
16 changes: 16 additions & 0 deletions packages/php-wasm/universal/src/lib/php.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { PHPRuntimeId } from './load-php-runtime';
import { popLoadedRuntime } from './load-php-runtime';
import type { PHPRequestHandler } from './php-request-handler';
import { PHPResponse, StreamedPHPResponse } from './php-response';
import { ErrnoError, getEmscriptenFsError } from './rethrow-file-system-error';
import type {
ChildProcess,
MessageListener,
Expand Down Expand Up @@ -1703,6 +1704,21 @@ export class PHP implements Disposable {
const release = await this.semaphore.acquire();

return await this.#executeWithErrorHandling(() => {
if (options.cwd !== undefined) {
try {
this.chdir(options.cwd);
} catch (error) {
const fileSystemError = getEmscriptenFsError(error);
if (fileSystemError !== undefined) {
throw new ErrnoError(
(error as { errno: number }).errno,
`Could not change the PHP working directory to "${options.cwd}": ${fileSystemError}`,
{ cause: error }
);
}
throw error;
}
}
const env = options.env || {};
for (const [key, value] of Object.entries(env)) {
this.#setEnv(key, value);
Expand Down
8 changes: 8 additions & 0 deletions packages/playground/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ The `start` command supports these common optional arguments. Run `npx @wp-playg
- `--reset`: Delete the stored site directory and start fresh.
- `--no-auto-mount`: Disable automatic project detection.

The `php` command supports the shared runtime and mount options plus:

- `--cwd=<vfs-path>`: Set the PHP process working directory to a virtual filesystem path.

When `--cwd` is omitted, a single auto-mounted directory becomes the working directory
automatically. With manual mounts or multiple auto-mounts, PHP keeps its default
working directory unless you provide `--cwd`.

The `server` command supports these common optional arguments. Run `npx @wp-playground/cli@latest server --help` for the full list:

- `--port=<port>`: The port number for the server to listen on. Defaults to 9400.
Expand Down
34 changes: 32 additions & 2 deletions packages/playground/cli/src/run-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,14 @@ export async function parseOptionsAndRunCLI(
},
};

const phpCommandOnlyOptions: Record<string, YargsOptions> = {
cwd: {
describe:
'Virtual filesystem path to use as the PHP process working directory.',
type: 'string',
},
};

const yargsObject = yargs(argsToParse)
.usage('Usage: wp-playground <command> [options]')
.command(
Expand Down Expand Up @@ -579,7 +587,10 @@ export async function parseOptionsAndRunCLI(
})
)
.command('php', 'Run a PHP script', (yargsInstance: Argv) =>
yargsInstance.options({ ...sharedOptions })
yargsInstance.options({
...sharedOptions,
...phpCommandOnlyOptions,
})
)
.demandCommand(1, 'Please specify a command')
.strictCommands()
Expand Down Expand Up @@ -828,6 +839,7 @@ export interface RunCLIArgs {
| BlueprintV2Declaration
| BlueprintBundle;
command: 'start' | 'server' | 'run-blueprint' | 'build-snapshot' | 'php';
cwd?: string;
debug?: boolean;
login?: boolean;
mount?: Mount[];
Expand Down Expand Up @@ -985,6 +997,22 @@ export async function runCLI(
args = expandAutoMounts(args);
}

let phpCommandCwd: string | undefined;
if (args.command === 'php') {
phpCommandCwd = args.cwd;
if (phpCommandCwd === undefined) {
const mounts = [
...(args['mount-before-install'] || []),
...(args.mount || []),
];
const hasManualMounts = mounts.some((mount) => !mount.autoMounted);
const autoMounts = mounts.filter((mount) => mount.autoMounted);
if (!hasManualMounts && autoMounts.length === 1) {
phpCommandCwd = autoMounts[0].vfsPath;
}
}
}

// Keeping the '--quiet' option to preserve backward compatibility
if (args.quiet) {
args.verbosity = 'quiet';
Expand Down Expand Up @@ -1664,7 +1692,9 @@ export async function runCLI(
'/internal/shared/bin/php',
...(args['_'] || []).slice(1),
];
const response = await playgroundPool.cli(argv);
const response = await playgroundPool.cli(argv, {
cwd: phpCommandCwd,
});
const [exitCode] = await Promise.all([
response.exitCode,
response.stdout.pipeTo(
Expand Down
Loading
Loading