Skip to content

[php-wasm] Add support for rm command in sandboxed spawn handler#3010

Open
Utsav-Ladani wants to merge 6 commits intoWordPress:trunkfrom
Utsav-Ladani:add/rm-command-support
Open

[php-wasm] Add support for rm command in sandboxed spawn handler#3010
Utsav-Ladani wants to merge 6 commits intoWordPress:trunkfrom
Utsav-Ladani:add/rm-command-support

Conversation

@Utsav-Ladani
Copy link
Copy Markdown
Contributor

@Utsav-Ladani Utsav-Ladani commented Dec 10, 2025

Motivation for the change

The wp plugin uninstall <plugin-name> command uses rm -rf to delete the plugin files, and the current sandboxed spawn handler doesn't have support for it, so I am just adding it.

Closes: #2653

Implementation details

Adding support for rm in the sandboxed spawn handler.

Testing Instructions (or ideally a Blueprint)

The following blueprint should run without any error, and the Hello Dolly plugin should be deleted:

{
  "login": true,
  "landingPage": "/wp-admin/plugins.php",
  "steps": [
    {
      "step": "wp-cli",
      "command": "wp plugin uninstall hello"
    }
  ]
}

@Utsav-Ladani Utsav-Ladani force-pushed the add/rm-command-support branch from 3903795 to cf0f654 Compare December 10, 2025 11:19
@Utsav-Ladani Utsav-Ladani requested a review from zaerl December 10, 2025 11:30
@Utsav-Ladani Utsav-Ladani force-pushed the add/rm-command-support branch 2 times, most recently from 2ecba1b to 89d198e Compare December 12, 2025 08:39
}
case 'rm': {
const target = args[args.length - 1];
const flags = args.slice(1, -1).join(' ');
Copy link
Copy Markdown
Collaborator

@adamziel adamziel Dec 15, 2025

Choose a reason for hiding this comment

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

It may be more involved than that. Here's a few examples that would be really useful to cover with a unit test to make sure this command can handle them without failing:

rm file1 file2 file3
rm -r -f directory
rm file1 -f file2 -r
# This one means "remove a file called "-rf"
rm -- -rf
# This one means "recursively remove a file or a directory called "-rf"
rm -rf -- -rf

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@adamziel @zaerl What do you think about using the yargs-parser npm package to parse the args instead of writing a custom parser from scratch?

Copy link
Copy Markdown
Collaborator

@adamziel adamziel Dec 16, 2025

Choose a reason for hiding this comment

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

@Utsav-Ladani potentially! Let's give it a try and see what would this code look like with yargs-parser in place. It seems to be larger than we need, but still fairly small so perhaps? Note this package (@php-wasm/universal) can be imported in both node.js and web browser code, which yargs-parser seems to support. Also, one potentially relevant difference is that yargs-parser seems to accept a string, and in here we already have access to a pre-parsed args array where string syntax were already processed (e.g. "my string" becomes my string) – hopefully there's a way to work from there without re-encoding that and re-escaping quotes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@adamziel I have updated the PR to try yargs-parser and it works for all cases you mentioned above, but we have an issue with the import.

import yargsParser from 'yargs-parser/browser'; is throwing process is not defined error as we are running it in the browser and import yargsParser from 'yargs-parser/browser'; is throwing type error.

@Utsav-Ladani Utsav-Ladani force-pushed the add/rm-command-support branch from 89d198e to 96129a6 Compare March 3, 2026 09:28
@Utsav-Ladani Utsav-Ladani requested review from a team, bgrgicak and Copilot March 3, 2026 10:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds rm support to the sandboxed spawn handler so WP-CLI commands like wp plugin uninstall (which uses rm -rf) can run successfully in the sandbox.

Changes:

  • Allow rm as a supported binary in the sandboxed spawn handler.
  • Implement an rm handler (with -r/-f parsing) to delete files/directories.
  • Refactor repeated setTimeout delay logic into a shared wait() helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +181 to +194
if (await php.isDir(target)) {
if (isRecursive) {
await php.rmdir(target, { recursive: true });
} else {
errorMessages.push(
`rm: ${target}: is a directory`
);
}
} else if (await php.isFile(target)) {
await php.unlink(target);
} else if (!isForce) {
errorMessages.push(
`rm: ${target}: No such file or directory`
);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

Any exception thrown by php.isDir, php.isFile, php.rmdir, or php.unlink will bubble to the outer catch, which is documented as meaning 'the PHP runtime has crashed'. That will incorrectly treat routine filesystem errors (permission issues, transient FS errors, unexpected path formats) as a runtime crash. Handle expected per-target FS failures inside the rm case (e.g., wrap operations per target in try/catch, emit an rm:-style message to stderr, and set a non-zero exit code) so the handler reports command errors instead of misclassifying them as runtime crashes.

Suggested change
if (await php.isDir(target)) {
if (isRecursive) {
await php.rmdir(target, { recursive: true });
} else {
errorMessages.push(
`rm: ${target}: is a directory`
);
}
} else if (await php.isFile(target)) {
await php.unlink(target);
} else if (!isForce) {
errorMessages.push(
`rm: ${target}: No such file or directory`
);
try {
if (await php.isDir(target)) {
if (isRecursive) {
await php.rmdir(target, { recursive: true });
} else {
errorMessages.push(
`rm: ${target}: is a directory`
);
}
} else if (await php.isFile(target)) {
await php.unlink(target);
} else if (!isForce) {
errorMessages.push(
`rm: ${target}: No such file or directory`
);
}
} catch (e) {
const message =
e instanceof Error ? e.message : String(e);
errorMessages.push(`rm: ${target}: ${message}`);

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +170
const parsedArgs = yargsParser(args.slice(1), {
alias: {
recursive: ['r'],
force: ['f'],
},
boolean: ['recursive', 'force'],
}) as any;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

Avoid as any here—it's easy to accidentally access missing properties (e.g., parsedArgs._) without type checking and makes future refactors riskier. Prefer a small local type/interface for the subset you rely on (e.g., { _: string[]; recursive?: boolean; force?: boolean }), or use a typed return shape so targets, isRecursive, and isForce are derived from well-defined fields.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The plugin uninstall wp-cli command generate an error

4 participants