Skip to content
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

feat: Invoke wp-cli from terminal sessions originating from Studio #213

Merged
merged 32 commits into from
Jun 18, 2024

Conversation

dcalhoun
Copy link
Member

@dcalhoun dcalhoun commented Jun 6, 2024

Relates to https://github.com/Automattic/dotcom-forge/issues/7327.

Proposed Changes

Expose the bundled wp-cli for terminal sessions originating from Studio.

Testing Instructions

1. Generic commands succeed

  1. Check out the proposed changes.
  2. npm run package
  3. Launch the app found in the out directory.
  4. Click "Open in Terminal" for a given site.
  5. In the terminal session, wp help or wp --version.
  6. Verify the executed wp-cli subcommand outputs the expected information.

2. Site-specific commands succeed

  1. Check out the proposed changes.
  2. npm run package
  3. Launch the app found in the out directory.
  4. Click "Open in Terminal" for a given site.
  5. In the terminal session, wp user list.
  6. Verify the executed wp-cli subcommand outputs the expected information for
    the targeted site.

3. Studio's bundled wp-cli is preferred over global wp-cli install

  1. Install wp-cli on your system.
  2. Check out the proposed changes.
  3. npm run package
  4. Launch the app found in the out directory.
  5. Click "Open in Terminal" for a given site.
  6. In the terminal session, which wp (macOS) or where wp (Windows).
  7. Verify the outputted location is Studio's bundled wp-cli.
  8. Open a new terminal session outside of Studio, which wp (macOS) or where wp (Windows).
  9. Verify the logged location is not Studio's bundled wp-cli, but the globally installed wp-cli instead.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@dcalhoun dcalhoun added the [Type] Enhancement Improvement upon an existing feature label Jun 6, 2024
@dcalhoun dcalhoun self-assigned this Jun 6, 2024
src/ipc-handlers.ts Outdated Show resolved Hide resolved
src/lib/cli.ts Show resolved Hide resolved
@dcalhoun dcalhoun force-pushed the feat/execute-studio-bundled-wp-cli-electron branch 2 times, most recently from b55df25 to dd6d29f Compare June 7, 2024 17:46
@dcalhoun dcalhoun changed the title feat: Invoke wp-cli from shell sessions originating from Studio feat: Invoke wp-cli from terminal sessions originating from Studio Jun 7, 2024
@@ -66,7 +66,7 @@ export async function executeWPCli ( projectPath: string, args: string[] ): Prom

return { stdout: result.text.replace('#!/usr/bin/env php', '').trim(), stderr: result.errors };
} catch (error) {
const errorContent = php.readFileAsText(stderrPath);
const errorContent = php.readFileAsText(stderrPath).replace('PHP.run() output was: #!/usr/bin/env php', '').trim();
Copy link
Member Author

Choose a reason for hiding this comment

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

Updated to match the stdout changes above and avoid unnecessary noise in the logs.

bin/wp.bat Outdated Show resolved Hide resolved
src/lib/cli.ts Show resolved Hide resolved
Copy link
Member Author

Choose a reason for hiding this comment

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

Most of this logic was inspired by a previous exploration in https://github.com/Automattic/local-environment/pull/174. Some of the logic was removed to simplify the source for the current feature scope.

@@ -21,7 +21,7 @@ import type { ForgeConfig } from '@electron-forge/shared-types';
const config: ForgeConfig = {
packagerConfig: {
asar: true,
extraResource: [ './wp-files', './assets' ],
extraResource: [ './wp-files', './assets', './bin' ],
Copy link
Member Author

Choose a reason for hiding this comment

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

This copies both bin/wp (macOS) and bin/wp.bat (Windows). Ideally, we copy only the relevant file for the given platform.

@fluiddot
Copy link
Contributor

When I was testing https://github.com/Automattic/local-environment/pull/174, I recall that I managed to use the development server by doing the following steps:

  • Run npm start to launch the first instance of the app.
  • In the Electron Forge configuration, change the ports used for the second instance:
    • Change the value of the development port to 3457.
    • Add the loggerPort property with the value 9001 (9000 is the default value).
  • Run npm start -- -- --cli"=<CLI-COMMAND>" in another terminal instance.

@dcalhoun dcalhoun marked this pull request as ready for review June 10, 2024 21:40
@dcalhoun dcalhoun requested review from a team June 10, 2024 21:41
Comment on lines +21 to +26
const php: NodePHP = await NodePHP.load(options.phpVersion,{
requestHandler: {
documentRoot: options.documentRoot,
absoluteUrl: options.absoluteUrl,
}
});
const [, php] = phpInstances;
php.mount(projectPath, options.documentRoot);
Copy link
Member Author

Choose a reason for hiding this comment

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

Improve wp-cli performance by avoiding starting the site. In theory, we only need a PHP instance.

Copy link
Member

Choose a reason for hiding this comment

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

Nice! Love it!

dcalhoun and others added 18 commits June 11, 2024 08:06
We cannot modify the search path with environment variables alone, we
must execute an arbitrary script.
For shell sessions originating from Studio, the bundled `wp-cli` should
take precedence over a user's existing `wp-cli` installation.
Previously, apparent windows from previous Terminal sessions would
linger and appear when launching a new Terminal.
The preceding `C:` was duplicated in the CLI path.
The script must contain the `.bat` file ending in order to run the
script on Windows.
The internals now return a result rather than logging the result.
Match the approach for `stdout` and avoid displaying unnecessary noise.
The `executeWpCli` command relies upon `console.warn`, which resulted
in unnecessary noise when invoking the `wp-cli` in the console.
This logic is likely unnecessary for the current usage with `wp-cli`.
@dcalhoun dcalhoun force-pushed the feat/execute-studio-bundled-wp-cli-electron branch from 9e12aaf to 4320082 Compare June 11, 2024 12:10
@fluiddot fluiddot self-requested a review June 12, 2024 14:46
Copy link
Contributor

@fluiddot fluiddot left a comment

Choose a reason for hiding this comment

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

Testing Commands

I tested different commands on macOS and found that some don't work as expected:

  • wp cli info: The execution freezes and doesn't respond.
  • wp cron test: Only works when the site is running.
  • wp db create: The execution freezes and doesn't respond.
  • wp db check: The execution freezes and doesn't respond.
  • wp db cli: The execution freezes and doesn't respond.
  • wp db query < debug.sql: Produces error env: mysql: No such file or directory.
  • wp eval 'echo WP_CONTENT_DIR;': Produces error: Error: Too many positional arguments: WP_CONTENT_DIR;.
  • wp eval-file eval.php: Produces error: Error: 'eval.php' does not exist.
    • It works when pointing to the server path: wp eval-file /var/www/html/eval.php
  • wp export: Export process succeed but the file can't be located.
    • It works when pointing to the server path: wp export --dir=/var/www/html
  • wp i18n make-pot . languages/hello-dolly.pot: Succeeds but the file can't be located.
    • It works when pointing to the server path: wp i18n make-pot . /var/www/html/languages/hello-dolly.pot
  • wp import --authors=create export.xml: Can't find the file.
    • It works when pointing to the server path: wp import --authors=create /var/www/html/export.xml
  • wp menu create "My Menu": Produces error Error: Too many positional arguments: Menu".
  • wp package install wp-cli/server-command: Produces error: Error: The "https://wp-cli.org/package-index/packages.json" file could not be downloaded: allow_url_fopen must be enabled in php.ini (https:// wrapper is disabled in the server configuration by allow_url_fopen=0.
  • wp server: The execution freezes and doesn't respond.
  • wp shell: Produces error Error: The shell binary '/bin/bash' is not valid. You can override the shell to be used through the WP_CLI_CUSTOM_SHELL environment variable..

I also noticed that commands that prompt user's input are quitted, not allowing the user to input anything.

Based on the above, I propose three items to investigate:

  1. For frozen commands, we need to figure out how to debug them. It's likely that something is failing and we don't see the error.
  2. String arguments (wrapped with ") are not processed properly. We'd need to review how they are passed to wp-cli.
  3. File paths need to be prefixed with the document root (e.g. /var/www/html). The fact that you are in the site folder but can't point to files will be confusing to users. Besides, we need to warn users that only files within the site folder can be accessed (this is a limitation as we only mount this path).

UPDATE: I saw in the Playground documentation that it doesn't support the full list of wp-cli commands. Probably some of the ones mentioned above (especially the ones that freeze) are likely not working due to this limitation.

Other Tests

Additionally, I tested changes introduced in #203 (using the test button) and confirmed WP-CLI commands can be executed and display the excepted output.

Comment on lines +20 to +26
const php: NodePHP = await NodePHP.load(options.phpVersion,{
requestHandler: {
documentRoot: options.documentRoot,
absoluteUrl: options.absoluteUrl,
}
});
const [, php] = phpInstances;
php.mount(projectPath, options.documentRoot);
Copy link
Contributor

Choose a reason for hiding this comment

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

For reference, when we bump the @php-wasm/node version, we'll need to update this part to conform this breaking change.

Comment on lines +20 to +26
const php: NodePHP = await NodePHP.load(options.phpVersion,{
requestHandler: {
documentRoot: options.documentRoot,
absoluteUrl: options.absoluteUrl,
}
});
const [, php] = phpInstances;
php.mount(projectPath, options.documentRoot);
Copy link
Contributor

Choose a reason for hiding this comment

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

For reference, the project path is mounted following the current logic when running the site in WordPress mode:

async function runWordPressMode(
php: NodePHP,
{ documentRoot, projectPath, absoluteUrl }: WPNowOptions
) {
php.mount(projectPath, documentRoot);

process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test' && ! process.env.E2E,
release: `${ app.getVersion() ? app.getVersion() : COMMIT_HASH }-${ getPlatformName() }`,
} );
if ( ! isCLI() ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should enable Sentry in second instances (like when invoking the CLI) to be able to collect exceptions produced when executing CLI commands. I originally disabled this in the PoC because it was interfering with the testing, but might be interesting to consider it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's explore this option in a separate PR.


const commands = {
wp: async () => {
disableConsole();
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be interesting to rewire console logs to the logging system instead of disabling it. This will facilitate debugging potential issues on CLI commands.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's explore this option in a separate PR.

// When running PHP on Playground, the value of constant PHP_OS is set to Linux.
// This implies that platform-specific logic won't work as expected. To solve this,
// we use an environment variable to ensure WP-CLI runs the Windows-specific logic.
putenv( 'WP_CLI_TEST_IS_WINDOWS=${isWindows ? 1 : 0}' );
Copy link
Contributor

Choose a reason for hiding this comment

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

This environment variable is mostly used by unit tests in wp-cli. As described in the inline comment, it allows us to tell wp-cli when the command is being executed in Windows. It should be enough as a temporary workaround but in the future, we should plan a more robust solution to match the value provided in PHP_OS with the actual OS.

)

SET COMMAND=%*
SET CLI=wp %COMMAND%
Copy link
Contributor

@fluiddot fluiddot Jun 13, 2024

Choose a reason for hiding this comment

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

I noticed that running commands that include quotes in the arguments don't work on Windows. I tried different approaches but nonce seemed to work.

E.g.: wp eval 'echo WP_CONTENT_DIR;'

# Conflicts:
#	package-lock.json
#	package.json
#	src/__mocks__/electron.ts
#	src/index.ts
@@ -115,6 +116,7 @@
"hpagent": "1.2.0",
"react-markdown": "^9.0.1",
"semver": "^7.6.0",
"shell-quote": "^1.8.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

This package is used to parse the CLI arguments passed as a single string to an array of strings, as need to invoke wp-cli.

@fluiddot
Copy link
Contributor

fluiddot commented Jun 13, 2024

After the fixes 8b9c2aa, 97a6a4d, and aa9d943, I reviewed the failed commands and some of them now work 🎊.

Here's the list of pending commands to be addressed:

  • wp cli info: The execution freezes and doesn't respond.
  • wp cron test: Only works when the site is running.
  • wp db create: The execution freezes and doesn't respond.
  • wp db check: The execution freezes and doesn't respond.
  • wp db cli: The execution freezes and doesn't respond.
  • wp db query < debug.sql: Produces error `env: mysql: No such file or directory
  • wp package install wp-cli/server-command: Produces error: Error: The "https://wp-cli.org/package-index/packages.json" file could not be downloaded: allow_url_fopen must be enabled in php.ini (https:// wrapper is disabled in the server configuration by allow_url_fopen=0.
  • wp server: The execution freezes and doesn't respond.
  • wp shell: Produces error Error: The shell binary '/bin/bash' is not valid. You can override the shell to be used through the WP_CLI_CUSTOM_SHELL environment variable..

For some of the above errors, the logs are showing an error related to the File System but couldn't pinpoint the code that produces it.

@fluiddot fluiddot self-assigned this Jun 13, 2024
@kozer kozer mentioned this pull request Jun 17, 2024
1 task
Copy link
Contributor

@kozer kozer left a comment

Choose a reason for hiding this comment

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

@dcalhoun and @fluiddot I created a PR that fixes terminal issues on linux: #257

Note that this fix the execution on development as well by using the local running app.

However, I didn't made any fixes for MacOS and Windows, as I couldn't test it.

Other than that, it works as expected, besides the wp shell command ( at least for me ). Ignore the warnings in the image, it's my problem
2024-06-17T13:20:11,011705320+03:00

@kozer
Copy link
Contributor

kozer commented Jun 17, 2024

Updated the PR, as discussed @fluiddot

Copy link
Member

@sejas sejas left a comment

Choose a reason for hiding this comment

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

I tested it on Mac and it works as expected.

From my understanding, this PR is adding to the terminal PATH a bash script that calls Studio app with the --cli flag which calls executeWPCli that runs php-wasm importing the wp-cli.phar that lives in server-files library directory.

wp-cli-success.mp4

I also tested using the parameter --path and it correctly detected the site.

I also tested executing a non existing command and it display the error correctly. Although the text was returned as stdout and not stderr. Left a comment to fix it.

Screenshot 2024-06-17 at 23 41 20

Congrats for this piece of engineering 🥳

bin/wp Outdated
@@ -0,0 +1,11 @@
if [ -z "$APP_PATH" ]; then
Copy link
Member

Choose a reason for hiding this comment

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

Should we add STUDIO as a prefix to reduce the possibility of conflicting with other env variables?

Suggested change
if [ -z "$APP_PATH" ]; then
if [ -z "$STUDIO_APP_PATH" ]; then

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, good idea. Better to use a unique name to avoid conflicts.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion applied in 9a4253a.

Comment on lines +21 to +26
const php: NodePHP = await NodePHP.load(options.phpVersion,{
requestHandler: {
documentRoot: options.documentRoot,
absoluteUrl: options.absoluteUrl,
}
});
const [, php] = phpInstances;
php.mount(projectPath, options.documentRoot);
Copy link
Member

Choose a reason for hiding this comment

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

Nice! Love it!

Comment on lines +257 to +259
// Handle CLI commands
listenCLICommands();
executeCLICommand();
Copy link
Member

Choose a reason for hiding this comment

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

Should we wrap these calls inside if ( isCLI() ) ?

Copy link
Contributor

@fluiddot fluiddot Jun 18, 2024

Choose a reason for hiding this comment

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

isCLI function only checks if the current instance was launched with a CLI command. listenCLICommands needs to be run on the main instance to set up the second-instance event, which is invoked when a second instance is launched. Hence, it needs to be executed independently if a CLI command is passed in the main instance. I'd like to note that this scenario doesn't apply to wp-cli commands as they are meant to be executed in the same instance they are triggered. However, in the future, it might be interesting to run commands on the first instance to manage sites.

Here's a diagram that explains the process:

image

Note

In the diagram, wp-cli commands are considered standalone commands.

executeCLICommand could be wrapped with if ( isCLI() ) but the function already checks if a CLI command exists.

try {
const result = await executeWPCli( projectPath, argsSansPath );
if ( result.stderr ) {
systemLog( result.stderr );
Copy link
Member

Choose a reason for hiding this comment

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

Lets declare const systemError = console.error; and call it on error to produce the exit in the right "file descriptor" 2.

Suggested change
systemLog( result.stderr );
systemError( result.stderr );

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if using console.error is actually pointing to the stderr file descriptor. In any case, I'm ok using the error function to log errors.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion applied in e20496f.

kozer and others added 3 commits June 18, 2024 13:21
* Studio: Fix wp-cli command for linux

* Update: fix variables per environment

* Update: Fix wp script to handle commands on both MacOS and linux
@kozer
Copy link
Contributor

kozer commented Jun 18, 2024

I checked it again, after merging my linux fix and works as expected! Amazing work!

@fluiddot fluiddot merged commit e428f51 into trunk Jun 18, 2024
10 checks passed
@fluiddot fluiddot deleted the feat/execute-studio-bundled-wp-cli-electron branch June 18, 2024 13:43
const exePath = app.getPath( 'exe' );
const appDirectory = app.getAppPath();
const appPath =
process.env.NODE_ENV === 'development' ? `${ exePath } ${ appDirectory }` : exePath;
Copy link
Contributor

Choose a reason for hiding this comment

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

I spotted that this condition should be ! app.isPackaged because the Electron shell app is invoked when the app is not packaged, i.e. produces its own executable. I'll address it in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is addressed in #268.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Enhancement Improvement upon an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants