Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8af3121
Add hidden `mode` option for Blueprints v2
brandonpayton Aug 15, 2025
7f84ddb
Set Blueprints v2 execution mode for expanded auto-mount
brandonpayton Aug 15, 2025
b2552a9
Support providing a path to auto-mount option
brandonpayton Aug 15, 2025
2745d51
Default auto-mount path within runCLI so we can test the default
brandonpayton Aug 16, 2025
dc0ec11
Move unrelated test out of the autoMount tests
brandonpayton Aug 16, 2025
1bec019
Add broken WIP run-cli tests for Blueprints v2
brandonpayton Aug 16, 2025
bb7dd05
Fix auto-mounting themes while using the Blueprints v2 runner
brandonpayton Aug 16, 2025
06781f2
Fix experimental-multi-worker option description
brandonpayton Aug 16, 2025
9af22e4
Fix run-cli tests that depend on default WordPress Site title
brandonpayton Aug 16, 2025
d173786
Revert "Default auto-mount path within runCLI so we can test the defa…
brandonpayton Aug 18, 2025
83e072d
Revert "Support providing a path to auto-mount option"
brandonpayton Aug 18, 2025
305d91d
Restore boolean autoMount arg in tests
brandonpayton Aug 18, 2025
40e05b7
Import proper types for CLI tests
brandonpayton Aug 19, 2025
8eef03f
Skip broken run-cli tests for Blueprints v2
brandonpayton Aug 19, 2025
fcceb0b
Prevent --mode from being used with --auto-mount
brandonpayton Aug 19, 2025
c677778
Add another validation warning about the --mode option
brandonpayton Aug 19, 2025
ff374b2
Add --mode tests
brandonpayton Aug 19, 2025
080f5f9
Recognize --skip-wordpress-setup and --skip-sqlite-setup options
adamziel Aug 26, 2025
2169d8e
Resolve merge conflicts in packages/playground/cli/tests/run-cli.spec.ts
adamziel Aug 26, 2025
1a90cc6
Resolve conflicts in the unit tests
adamziel Aug 26, 2025
096b25f
Merge branch 'trunk' into playground-cli-auto-select-blueprint-v2-model
adamziel Aug 26, 2025
27e5cb6
Adjust unit tests setup to pass verbosity test with Blueprints v2
adamziel Aug 26, 2025
63ef1e3
Skip the failing verbosity test
adamziel Aug 26, 2025
2fa0f84
Add a --site-url CLI option to separate setting the site URL from bin…
adamziel Aug 26, 2025
6fcd3df
Merge branch 'trunk' into playground-cli-site-url
adamziel Aug 26, 2025
2c7b1a3
Dispose cliServer after each test case
adamziel Aug 26, 2025
734a9d2
Ignore dispose-related errors
adamziel Aug 26, 2025
2eda90a
Lint
adamziel Aug 26, 2025
5b1e0e5
Improve siteUrl-related messaging
adamziel Aug 26, 2025
16db78e
[PHP] Unbind rotatePHPRuntime() handler when PHP instance is destroyed
adamziel Aug 26, 2025
c59bb16
Move PHP instance rotation inside the PHP class
adamziel Aug 27, 2025
246638e
Rotate PHP instance around cli() execution to support multiple calls
adamziel Aug 27, 2025
98744e3
Test for multiple .cli() and .runStream() calls
adamziel Aug 27, 2025
0921653
Merge branch 'trunk' into unbind-hotswap
adamziel Aug 27, 2025
33b4d0b
Remove the concept of destroying a PHP instance
adamziel Aug 27, 2025
6c46cd9
Replace uses of rotatePHPRuntime() with php.enableRuntimeRotation
adamziel Aug 27, 2025
cb2f595
Remove private access modifier from hotSwapPHPRuntime
adamziel Aug 28, 2025
a66d25f
Change php.exit() to php.exit(0) in process manager
adamziel Aug 28, 2025
d9ff790
Update rotate-php-runtime.spec.ts
adamziel Aug 28, 2025
cb63325
Enhance error handling in php.ts
adamziel Aug 28, 2025
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: 2 additions & 2 deletions packages/php-wasm/fs-journal/src/lib/fs-journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ export function journalFSEvents(
php[__private__dont__use].journal.unbind();
delete php[__private__dont__use].journal;
}
php.addEventListener('runtime.beforedestroy', unbindFromOldRuntime);
php.addEventListener('runtime.beforeExit', unbindFromOldRuntime);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the PHP instance can still be reinitialized with another PHP runtime so it's not destroyed


return function unbind() {
php.removeEventListener('runtime.initialized', bindToCurrentRuntime);
php.removeEventListener('runtime.beforedestroy', unbindFromOldRuntime);
php.removeEventListener('runtime.beforeExit', unbindFromOldRuntime);
return php[__private__dont__use].journal.unbind();
};
}
Expand Down
16 changes: 16 additions & 0 deletions packages/php-wasm/node/src/test/php.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,22 @@ phpLoaderOptions.forEach((options) => {
'/internal/shared/bin/php'
);
});

it('should support multiple calls to php.cli() and php.runStream() when runtime rotation is enabled', async () => {
php.enableRuntimeRotation({
maxRequests: 1,
recreateRuntime: () =>
loadNodeRuntime(phpVersion as any, options),
});
const response = await php.cli(['php', '-r', 'echo "Hello";']);
expect(await response.stdoutText).toBe('Hello');
const response2 = await php.runStream({
code: `<?php echo "Hello";`,
});
expect(await response2.stdoutText).toBe('Hello');
const response3 = await php.cli(['php', '-r', 'echo "Hello";']);
expect(await response3.stdoutText).toBe('Hello');
});
});

describe('Response parsing', { skip: options.withXdebug }, () => {
Expand Down
94 changes: 21 additions & 73 deletions packages/php-wasm/node/src/test/rotate-php-runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,20 @@ import {
LatestSupportedPHPVersion,
PHP,
__private__dont__use,
rotatePHPRuntime,
} from '@php-wasm/universal';
import { loadNodeRuntime } from '../lib';
import { createNodeFsMountHandler } from '../lib/node-fs-mount';

const recreateRuntime = async (version: any = LatestSupportedPHPVersion) =>
await loadNodeRuntime(version);

describe('rotatePHPRuntime()', () => {
describe('php.enableRuntimeRotation()', () => {
it('Preserves the /internal directory through PHP runtime recreation', async () => {
// Rotate the PHP runtime
const recreateRuntimeSpy = vitest.fn(recreateRuntime);

const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 10,
Expand Down Expand Up @@ -55,8 +53,7 @@ describe('rotatePHPRuntime()', () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);

const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 10,
Expand Down Expand Up @@ -89,8 +86,7 @@ describe('rotatePHPRuntime()', () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);

const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/wordpress',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 10,
Expand Down Expand Up @@ -192,8 +188,7 @@ describe('rotatePHPRuntime()', () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
// Rotate the PHP runtime
const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1000,
Expand Down Expand Up @@ -222,62 +217,39 @@ describe('rotatePHPRuntime()', () => {
it('Should recreate the PHP runtime after maxRequests', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1,
});
// Rotate the PHP runtime
await php.run({ code: `` });
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should not rotate after the cleanup handler is called, even if max requests is reached', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
const cleanup = rotatePHPRuntime({
php,
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1,
});
// Rotate the PHP runtime
await php.run({ code: `` });
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);

cleanup();

// No further rotation should happen
await php.run({ code: `` });
await php.run({ code: `` });

expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should recreate the PHP runtime after a PHP runtime crash', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1234,
});
// Cause a PHP runtime rotation due to error
await php.dispatchEvent({
php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
source: 'php-wasm',
});
await php.run({ code: `` });
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should not recreate the PHP runtime after a PHP fatal', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1234,
Expand All @@ -296,31 +268,6 @@ describe('rotatePHPRuntime()', () => {
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(1);
}, 30_000);

it('Should not rotate after the cleanup handler is called, even if there is a PHP runtime error', async () => {
const recreateRuntimeSpy = vitest.fn(recreateRuntime);
const php = new PHP(await recreateRuntimeSpy());
const cleanup = rotatePHPRuntime({
php,
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1,
});
// Rotate the PHP runtime
await php.run({ code: `` });
expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);

cleanup();

// No further rotation should happen
php.dispatchEvent({
type: 'request.error',
error: new Error('mock error'),
source: 'php-wasm',
});

expect(recreateRuntimeSpy).toHaveBeenCalledTimes(2);
}, 30_000);

it('Should hotswap the PHP runtime from 8.2 to 8.3', async () => {
let nbCalls = 0;
const recreateRuntimeSpy = vitest.fn(() => {
Expand All @@ -331,8 +278,7 @@ describe('rotatePHPRuntime()', () => {
return recreateRuntime('8.3');
});
const php = new PHP(await recreateRuntimeSpy());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime: recreateRuntimeSpy,
maxRequests: 1,
Expand All @@ -353,8 +299,7 @@ describe('rotatePHPRuntime()', () => {

it('Should preserve the custom SAPI name', async () => {
const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime,
maxRequests: 1,
Expand All @@ -371,8 +316,7 @@ describe('rotatePHPRuntime()', () => {

it('Should preserve the MEMFS files', async () => {
const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime,
maxRequests: 1,
Expand All @@ -395,15 +339,15 @@ describe('rotatePHPRuntime()', () => {

it('Should not overwrite the NODEFS files', async () => {
const php = new PHP(await recreateRuntime());
rotatePHPRuntime({
php,
php.enableRuntimeRotation({
cwd: '/test-root',
recreateRuntime,
maxRequests: 1,
});

// Rotate the PHP runtime
await php.run({ code: `` });
const result = await php.run({ code: `` });
await result.text;

php.mkdir('/test-root');
php.writeFile('/test-root/index.php', 'test');
Expand All @@ -415,7 +359,10 @@ describe('rotatePHPRuntime()', () => {
date.setFullYear(date.getFullYear() - 1);
fs.utimesSync(tempFile, date, date);
try {
php.mount('/test-root/nodefs', createNodeFsMountHandler(tempDir));
await php.mount(
'/test-root/nodefs',
createNodeFsMountHandler(tempDir)
);

// Rotate the PHP runtime
await php.run({ code: `` });
Expand All @@ -429,6 +376,7 @@ describe('rotatePHPRuntime()', () => {
} finally {
fs.rmSync(tempFile);
fs.rmdirSync(tempDir);
php.exit();
}
}, 30_000);
});
Loading