Skip to content

[Playground CLI] Use Blueprints v2 #2238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a10cc6d
[PHP.wasm CLI] Remove disable_functions from the default php.ini
adamziel Jun 4, 2025
afc650c
React to child processes exiting immediately. Timeout when they don't…
adamziel Jun 5, 2025
54fef91
Mount top-level symlinks, such as /var, in the useHostFilesystem
adamziel Jun 5, 2025
5f87652
Propagate cwd to spawn handler in php-wasm/main.ts
adamziel Jun 5, 2025
0e0c431
Remove a custom spawn handler in php-wasm/main.ts. Use the generic on…
adamziel Jun 5, 2025
94cadb3
Lint
adamziel Jun 5, 2025
c048fac
Blueprints v2: Run in CLI
adamziel Jun 5, 2025
54feaa4
Auto-flush TTY's stdout stream to support auto-refreshing progress ba…
adamziel Jun 6, 2025
0932b64
Add an onBlueprintTargetResolved hook
adamziel Jun 6, 2025
a2e56da
Consistent site options
adamziel Jun 6, 2025
a3523b8
Run Blueprints v2 via run.ts
adamziel Jun 6, 2025
6173c10
Naive first stab at running Blueprints v2 in Playground CLI
adamziel Jun 6, 2025
c390e6e
Basic Blueprints v2 runner working! Now we're stuck on no error infor…
adamziel Jun 6, 2025
648e3b7
Output useful information. Do not log 10 lines of error when the blue…
adamziel Jun 9, 2025
56ee490
Document the difference between logger and output UI in CLI
adamziel Jun 9, 2025
1749761
Code style nitpicks
adamziel Jun 9, 2025
56c40d1
Avoid fetching the Blueprint in TypeScript. Delegate it to PHP.
adamziel Jun 9, 2025
fa90944
Remove a log message
adamziel Jun 9, 2025
8c7be21
Remove downloading and Blueprint resolution logic – it's now handled …
adamziel Jun 9, 2025
e9725ec
Reorganize Playground CLI code to exclude unused code
adamziel Jun 9, 2025
e7a6507
Make post_message_to_js a custom PHP extension to enable it in the CL…
adamziel Jun 10, 2025
f1c5fd9
Stream stdout and stderr bytes from php.cli()
adamziel Jun 10, 2025
a8fab90
Add the missing mounts.ts file
adamziel Jun 10, 2025
4de0176
Add php.runStream() method that provides StreamedPHPResponse and depr…
adamziel Jun 10, 2025
28fce90
Add the missing is-exit-code.ts file
adamziel Jun 10, 2025
908d295
Polling improvement, subprocesses improvements
adamziel Jun 10, 2025
73ef99f
Consider CWD of subprocesses and use .cli() for all sub-spawns
adamziel Jun 11, 2025
615dba3
Browser PHP compatibility
adamziel Jun 11, 2025
a08cb5a
Document the missing progress tracking
adamziel Jun 11, 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
8 changes: 8 additions & 0 deletions packages/meta/src/node-es-module-loader/loader.mts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ export async function load(
};
}

if (urlObj.searchParams.has('url')) {
return {
format: 'module',
shortCircuit: true,
source: `export default ${JSON.stringify(urlObj.pathname)};`,
};
}

if (urlObj.pathname.endsWith('.json')) {
const source = readFileSync(urlObj.pathname, 'utf8');
return {
Expand Down
87 changes: 49 additions & 38 deletions packages/php-wasm/cli/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* A CLI script that runs PHP CLI via the WebAssembly build.
*/
import { writeFileSync, existsSync, mkdtempSync, rmSync, rmdirSync } from 'fs';
import os from 'os';
import { writeFileSync, existsSync, mkdtempSync, chmodSync } from 'fs';
import { rootCertificates } from 'tls';

import {
Expand All @@ -11,8 +12,8 @@ import {
import type { SupportedPHPVersion } from '@php-wasm/universal';

import { PHP } from '@php-wasm/universal';
import { spawn } from 'child_process';
import { loadNodeRuntime, useHostFilesystem } from '@php-wasm/node';
import path from 'path';

let args = process.argv.slice(2);
if (!args.length) {
Expand Down Expand Up @@ -45,60 +46,70 @@ async function run() {
// @see https://github.com/npm/npm/issues/4531
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { TMPDIR, ...envVariables } = process.env;

/**
* Ensure the PHP_BINARY constant is set to the PHP-WASM binary.
*
* ## Rationale
*
* We want any `proc_open()` calls to use the PHP-WASM binary and
* not the system PHP binary.
*
* ## How it works
*
* The code below creates a temporary `php` executable in PATH,
* which covers `proc_open( "php", ... )` calls.
*
* Furthermore, when PHP detects the `php` executable in PATH, it
* sets the PHP_BINARY constant to it.
*/
const tempDir = mkdtempSync(path.join(os.tmpdir(), 'php-wasm-bin'));
writeFileSync(
`${tempDir}/php`,
`#!/bin/sh
${process.argv[0]} ${process.execArgv.join(' ')} ${process.argv[1]}
`
);
chmodSync(`${tempDir}/php`, 0o755);

const sysTempDir = mkdtempSync(path.join(os.tmpdir(), 'php-wasm-sys-tmp'));
const php = new PHP(
await loadNodeRuntime(phpVersion, {
emscriptenOptions: {
ENV: {
...envVariables,
TMPDIR: sysTempDir,
TERM: 'xterm',
PATH: `${tempDir}:${envVariables['PATH']}`,
},
},
})
);

useHostFilesystem(php);
php.setSpawnHandler((command: string) => {
const phpWasmCommand = `${process.argv[0]} ${process.execArgv.join(
' '
)} ${process.argv[1]}`;
// Naively replace the PHP binary with the PHP-WASM command
// @TODO: Don't process the command. Lean on the shell to do it, e.g.
// through a PATH or an alias.
const updatedCommand = command.replace(
/^(?:\\ |[^ ])*php\d?(\s|$)/,
phpWasmCommand + '$1'
);

// Create a shell script in a temporary directory
const tempDir = mkdtempSync('php-wasm-');
const tempScriptPath = `${tempDir}/script.sh`;
writeFileSync(
tempScriptPath,
`#!/bin/sh
${updatedCommand} < /dev/stdin
`
);

try {
return spawn(updatedCommand, [], {
shell: true,
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 5000,
});
} finally {
// Remove the temporary directory
rmSync(tempScriptPath);
rmdirSync(tempDir);
}
});

const hasMinusCOption = args.some((arg) => arg.startsWith('-c'));
if (!hasMinusCOption) {
args.unshift('-c', defaultPhpIniPath);
}

await php
.cli(['php', ...args])
const response = await php.cli(['php', ...args]);
response.stderr.pipeTo(
new WritableStream({
write(chunk) {
process.stderr.write(chunk);
},
})
);
response.stdout.pipeTo(
new WritableStream({
write(chunk) {
process.stdout.write(chunk);
},
})
);

response.exitCode
.catch((result) => {
if (result.name === 'ExitStatus') {
process.exit(result.status === undefined ? 1 : result.status);
Expand Down
1 change: 0 additions & 1 deletion packages/php-wasm/cli/src/php.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ log_errors=1
always_populate_raw_post_data=-1
upload_max_filesize=2000M
post_max_size=2000M
disable_functions=proc_open,popen,curl_exec,curl_multi_exec
allow_url_fopen=On
session.save_path=/home/web_user
implicit_flush=1
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/compile/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ const platformDefaults = {
WITH_INTL: 'yes',
WITH_OPENSSL: 'yes',
WITH_WS_NETWORKING_PROXY: 'yes',
WITH_CLI_SAPI: 'yes',
},
web: {},
node: {
WITH_CLI_SAPI: 'yes',
WITH_NODEFS: 'yes',
WITH_MYSQL: 'yes',
},
Expand Down
8 changes: 8 additions & 0 deletions packages/php-wasm/compile/php-post-message-to-js/config.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dnl config.m4 for extension post_message_to_js

PHP_ARG_ENABLE(post_message_to_js, whether to enable post_message_to_js support,
[ --enable-post_message_to_js Enable post_message_to_js support])

if test "$PHP_POST_MESSAGE_TO_JS" != "no"; then
PHP_NEW_EXTENSION(post_message_to_js, post_message_to_js.c, $ext_shared)
fi
5 changes: 5 additions & 0 deletions packages/php-wasm/compile/php-post-message-to-js/config.w32
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ARG_ENABLE("post_message_to_js", "Enable post_message_to_js support", "no");

if (PHP_POST_MESSAGE_TO_JS == "yes") {
EXTENSION("post_message_to_js", "post_message_to_js.c");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "php.h"
#include "post_message_to_js.h"
#include "ext/standard/info.h"
#include <emscripten.h>

/**
* Provided by php_wasm.c:
*/
extern size_t js_module_onMessage(const char *data, char **response_buffer);

/* {{{ PHP_FUNCTION */
PHP_FUNCTION(post_message_to_js)
{
char *data;
int data_len;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE) {
return;
}

char *response;
size_t response_len = js_module_onMessage(data, &response);
if (response_len != -1) {
zend_string *return_string = zend_string_init(response, response_len, 0);
free(response);
RETURN_NEW_STR(return_string);
} else {
RETURN_NULL();
}
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(post_message_to_js)
{
php_info_print_table_start();
php_info_print_table_row(2, "post_message_to_js support", "enabled");
php_info_print_table_end();
}
/* }}} */

/* {{{ post_message_to_js_functions[] */
const zend_function_entry post_message_to_js_functions[] = {
PHP_FE(post_message_to_js, arginfo_post_message_to_js)
PHP_FE_END
};
/* }}} */

/* {{{ post_message_to_js_module_entry */
zend_module_entry post_message_to_js_module_entry = {
STANDARD_MODULE_HEADER,
"post_message_to_js", /* Extension name */
post_message_to_js_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
NULL, /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(post_message_to_js), /* PHP_MINFO - Module info */
PHP_POST_MESSAGE_TO_JS_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_POST_MESSAGE_TO_JS
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(post_message_to_js)
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef PHP_POST_MESSAGE_TO_JS_H
#define PHP_POST_MESSAGE_TO_JS_H

extern zend_module_entry post_message_to_js_module_entry;
#define phpext_post_message_to_js_ptr &post_message_to_js_module_entry

ZEND_BEGIN_ARG_INFO_EX(arginfo_post_message_to_js, 0, 1, 1)
ZEND_ARG_INFO(0, data)
ZEND_END_ARG_INFO()

PHP_FUNCTION(post_message_to_js);

#define PHP_POST_MESSAGE_TO_JS_VERSION "1.0.0"

#endif // PHP_POST_MESSAGE_TO_JS_H
Loading
Loading