Skip to content

Commit 0f31aa0

Browse files
committed
Merge branch 'main' into copilot/support-restarting-shell
2 parents ac645a8 + 8ec6706 commit 0f31aa0

3 files changed

Lines changed: 113 additions & 25 deletions

File tree

.github/workflows/copilot-setup-steps.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ jobs:
2121

2222
- name: Check existence of composer.json file
2323
id: check_composer_file
24-
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3
25-
with:
26-
files: "composer.json"
24+
run: echo "files_exists=$(test -f composer.json && echo true || echo false)" >> "$GITHUB_OUTPUT"
2725

2826
- name: Set up PHP environment
2927
if: steps.check_composer_file.outputs.files_exists == 'true'
@@ -38,7 +36,7 @@ jobs:
3836

3937
- name: Install Composer dependencies & cache dependencies
4038
if: steps.check_composer_file.outputs.files_exists == 'true'
41-
uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # v3
39+
uses: ramsey/composer-install@a35c6ebd3d08125aaf8852dff361e686a1a67947 # v3
4240
env:
4341
COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }}
4442
with:

features/shell.feature

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,38 @@ Feature: WordPress REPL
9898
exit
9999
"""
100100
101+
Scenario: Use SHELL environment variable as fallback for bash
102+
Given a WP install
103+
104+
And a session file:
105+
"""
106+
return true;
107+
"""
108+
109+
# SHELL pointing to bash should work (when bash is available).
110+
When I try `SHELL=/bin/bash wp shell --basic < session`
111+
Then STDOUT should contain:
112+
"""
113+
bool(true)
114+
"""
115+
And STDERR should be empty
116+
117+
# SHELL pointing to non-bash binary should be ignored and fall back to /bin/bash.
118+
When I try `SHELL=/bin/sh wp shell --basic < session`
119+
Then STDOUT should contain:
120+
"""
121+
bool(true)
122+
"""
123+
And STDERR should be empty
124+
125+
# SHELL pointing to invalid path should be ignored and fall back to /bin/bash.
126+
When I try `SHELL=/nonsense/path wp shell --basic < session`
127+
Then STDOUT should contain:
128+
"""
129+
bool(true)
130+
"""
131+
And STDERR should be empty
132+
101133
Scenario: Input starting with dash
102134
Given a WP install
103135
And a session file:
@@ -115,6 +147,44 @@ Feature: WordPress REPL
115147
history: -1: invalid option
116148
"""
117149
150+
Scenario: User can define variable named $line
151+
Given a WP install
152+
And a session file:
153+
"""
154+
$line = 'this should work';
155+
$line;
156+
"""
157+
158+
When I run `wp shell --basic < session`
159+
Then STDOUT should contain:
160+
"""
161+
=> string(16) "this should work"
162+
"""
163+
And STDOUT should contain:
164+
"""
165+
=> string(16) "this should work"
166+
"""
167+
168+
Scenario: User can define variables named $out and $evl
169+
Given a WP install
170+
And a session file:
171+
"""
172+
$out = 'out should work';
173+
$evl = 'evl should work';
174+
$out;
175+
$evl;
176+
"""
177+
178+
When I run `wp shell --basic < session`
179+
Then STDOUT should contain:
180+
"""
181+
=> string(15) "out should work"
182+
"""
183+
And STDOUT should contain:
184+
"""
185+
=> string(15) "evl should work"
186+
"""
187+
118188
Scenario: Shell with hook parameter
119189
Given a WP install
120190
And a session file:

src/WP_CLI/Shell/REPL.php

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,50 +39,48 @@ public function start() {
3939
WP_CLI::log( "Detected changes in {$this->watch_path}, restarting shell..." );
4040
return self::EXIT_CODE_RESTART;
4141
}
42+
$__repl_input_line = $this->prompt();
4243

43-
$line = $this->prompt();
44-
45-
if ( '' === $line ) {
44+
if ( '' === $__repl_input_line ) {
4645
continue;
4746
}
4847

4948
// Check for special exit command
50-
if ( 'exit' === trim( $line ) ) {
49+
if ( 'exit' === trim( $__repl_input_line ) ) {
5150
return 0;
5251
}
52+
$__repl_input_line = rtrim( $__repl_input_line, ';' ) . ';';
5353

5454
// Check for special restart command
55-
if ( 'restart' === trim( $line ) ) {
55+
if ( 'restart' === trim( $__repl_input_line ) ) {
5656
WP_CLI::log( 'Restarting shell...' );
5757
return self::EXIT_CODE_RESTART;
5858
}
5959

60-
$line = rtrim( $line, ';' ) . ';';
61-
62-
if ( self::starts_with( self::non_expressions(), $line ) ) {
60+
if ( self::starts_with( self::non_expressions(), $__repl_input_line ) ) {
6361
ob_start();
6462
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
65-
eval( $line );
66-
$out = (string) ob_get_clean();
67-
if ( 0 < strlen( $out ) ) {
68-
$out = rtrim( $out, "\n" ) . "\n";
63+
eval( $__repl_input_line );
64+
$__repl_output = (string) ob_get_clean();
65+
if ( 0 < strlen( $__repl_output ) ) {
66+
$__repl_output = rtrim( $__repl_output, "\n" ) . "\n";
6967
}
70-
fwrite( STDOUT, $out );
68+
fwrite( STDOUT, $__repl_output );
7169
} else {
72-
if ( ! self::starts_with( 'return', $line ) ) {
73-
$line = 'return ' . $line;
70+
if ( ! self::starts_with( 'return', $__repl_input_line ) ) {
71+
$__repl_input_line = 'return ' . $__repl_input_line;
7472
}
7573

7674
// Write directly to STDOUT, to sidestep any output buffers created by plugins
7775
ob_start();
7876
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
79-
$evl = eval( $line );
80-
$out = (string) ob_get_clean();
81-
if ( 0 < strlen( $out ) ) {
82-
echo rtrim( $out, "\n" ) . "\n";
77+
$__repl_eval_result = eval( $__repl_input_line );
78+
$__repl_output = (string) ob_get_clean();
79+
if ( 0 < strlen( $__repl_output ) ) {
80+
echo rtrim( $__repl_output, "\n" ) . "\n";
8381
}
8482
echo '=> ';
85-
var_dump( $evl );
83+
var_dump( $__repl_eval_result );
8684
fwrite( STDOUT, (string) ob_get_clean() );
8785
}
8886
}
@@ -154,8 +152,15 @@ private static function create_prompt_cmd( $prompt, $history_path ) {
154152
$history_path = escapeshellarg( $history_path );
155153
if ( getenv( 'WP_CLI_CUSTOM_SHELL' ) ) {
156154
$shell_binary = (string) getenv( 'WP_CLI_CUSTOM_SHELL' );
157-
} else {
155+
} elseif ( is_file( '/bin/bash' ) && is_readable( '/bin/bash' ) ) {
156+
// Prefer /bin/bash when available since we use bash-specific commands.
158157
$shell_binary = '/bin/bash';
158+
} elseif ( getenv( 'SHELL' ) && self::is_bash_shell( (string) getenv( 'SHELL' ) ) ) {
159+
// Only use SHELL as fallback if it's bash (we use bash-specific commands).
160+
$shell_binary = (string) getenv( 'SHELL' );
161+
} else {
162+
// Final fallback for systems without /bin/bash.
163+
$shell_binary = 'bash';
159164
}
160165

161166
if ( ! is_file( $shell_binary ) || ! is_readable( $shell_binary ) ) {
@@ -176,6 +181,21 @@ private static function create_prompt_cmd( $prompt, $history_path ) {
176181
return "{$shell_binary} -c " . escapeshellarg( $cmd );
177182
}
178183

184+
/**
185+
* Check if a shell binary is bash or bash-compatible.
186+
*
187+
* @param string $shell_path Path to the shell binary.
188+
* @return bool True if the shell is bash, false otherwise.
189+
*/
190+
private static function is_bash_shell( $shell_path ) {
191+
if ( ! is_file( $shell_path ) || ! is_readable( $shell_path ) ) {
192+
return false;
193+
}
194+
// Check if the basename is exactly 'bash' or starts with 'bash' followed by a version/variant.
195+
$basename = basename( $shell_path );
196+
return 'bash' === $basename || 0 === strpos( $basename, 'bash-' );
197+
}
198+
179199
private function set_history_file() {
180200
$data = getcwd() . get_current_user();
181201

0 commit comments

Comments
 (0)