Skip to content

Commit 07fe118

Browse files
committed
Merge branch 'main' into copilot/catch-exceptions-restart-session
2 parents cec513e + 8ec6706 commit 07fe118

File tree

3 files changed

+111
-21
lines changed

3 files changed

+111
-21
lines changed

.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
@@ -61,6 +61,38 @@ Feature: WordPress REPL
6161
"""
6262
And STDERR should be empty
6363
64+
Scenario: Use SHELL environment variable as fallback for bash
65+
Given a WP install
66+
67+
And a session file:
68+
"""
69+
return true;
70+
"""
71+
72+
# SHELL pointing to bash should work (when bash is available).
73+
When I try `SHELL=/bin/bash wp shell --basic < session`
74+
Then STDOUT should contain:
75+
"""
76+
bool(true)
77+
"""
78+
And STDERR should be empty
79+
80+
# SHELL pointing to non-bash binary should be ignored and fall back to /bin/bash.
81+
When I try `SHELL=/bin/sh wp shell --basic < session`
82+
Then STDOUT should contain:
83+
"""
84+
bool(true)
85+
"""
86+
And STDERR should be empty
87+
88+
# SHELL pointing to invalid path should be ignored and fall back to /bin/bash.
89+
When I try `SHELL=/nonsense/path wp shell --basic < session`
90+
Then STDOUT should contain:
91+
"""
92+
bool(true)
93+
"""
94+
And STDERR should be empty
95+
6496
Scenario: Input starting with dash
6597
Given a WP install
6698
And a session file:
@@ -116,6 +148,44 @@ Feature: WordPress REPL
116148
Error: Call to undefined function nonexistent_function()
117149
"""
118150
151+
Scenario: User can define variable named $line
152+
Given a WP install
153+
And a session file:
154+
"""
155+
$line = 'this should work';
156+
$line;
157+
"""
158+
159+
When I run `wp shell --basic < session`
160+
Then STDOUT should contain:
161+
"""
162+
=> string(16) "this should work"
163+
"""
164+
And STDOUT should contain:
165+
"""
166+
=> string(16) "this should work"
167+
"""
168+
169+
Scenario: User can define variables named $out and $evl
170+
Given a WP install
171+
And a session file:
172+
"""
173+
$out = 'out should work';
174+
$evl = 'evl should work';
175+
$out;
176+
$evl;
177+
"""
178+
179+
When I run `wp shell --basic < session`
180+
Then STDOUT should contain:
181+
"""
182+
=> string(15) "out should work"
183+
"""
184+
And STDOUT should contain:
185+
"""
186+
=> string(15) "evl should work"
187+
"""
188+
119189
Scenario: Shell with hook parameter
120190
Given a WP install
121191
And a session file:

src/WP_CLI/Shell/REPL.php

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,49 +19,49 @@ public function __construct( $prompt ) {
1919
public function start() {
2020
// @phpstan-ignore while.alwaysTrue
2121
while ( true ) {
22-
$line = $this->prompt();
22+
$__repl_input_line = $this->prompt();
2323

24-
if ( '' === $line ) {
24+
if ( '' === $__repl_input_line ) {
2525
continue;
2626
}
2727

28-
$line = rtrim( $line, ';' ) . ';';
28+
$__repl_input_line = rtrim( $__repl_input_line, ';' ) . ';';
2929

30-
if ( self::starts_with( self::non_expressions(), $line ) ) {
30+
if ( self::starts_with( self::non_expressions(), $__repl_input_line ) ) {
3131
ob_start();
3232
try {
3333
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
34-
eval( $line );
34+
eval( $__repl_input_line );
3535
} catch ( \Throwable $e ) {
3636
// Display the error message but continue the session
3737
fwrite( STDERR, get_class( $e ) . ': ' . $e->getMessage() . "\n" );
3838
}
39-
$out = (string) ob_get_clean();
40-
if ( 0 < strlen( $out ) ) {
41-
$out = rtrim( $out, "\n" ) . "\n";
39+
$__repl_output = (string) ob_get_clean();
40+
if ( 0 < strlen( $__repl_output ) ) {
41+
$__repl_output = rtrim( $__repl_output, "\n" ) . "\n";
4242
}
43-
fwrite( STDOUT, $out );
43+
fwrite( STDOUT, $__repl_output );
4444
} else {
45-
if ( ! self::starts_with( 'return', $line ) ) {
46-
$line = 'return ' . $line;
45+
if ( ! self::starts_with( 'return', $__repl_input_line ) ) {
46+
$__repl_input_line = 'return ' . $__repl_input_line;
4747
}
4848

4949
// Write directly to STDOUT, to sidestep any output buffers created by plugins
5050
ob_start();
5151
try {
5252
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
53-
$evl = eval( $line );
53+
$__repl_eval_result = eval( $__repl_input_line );
5454
} catch ( \Throwable $e ) {
5555
// Display the error message but continue the session
5656
fwrite( STDERR, get_class( $e ) . ': ' . $e->getMessage() . "\n" );
5757
$evl = null;
5858
}
59-
$out = (string) ob_get_clean();
60-
if ( 0 < strlen( $out ) ) {
61-
echo rtrim( $out, "\n" ) . "\n";
59+
$__repl_output = (string) ob_get_clean();
60+
if ( 0 < strlen( $__repl_output ) ) {
61+
echo rtrim( $__repl_output, "\n" ) . "\n";
6262
}
6363
echo '=> ';
64-
var_dump( $evl );
64+
var_dump( $__repl_eval_result );
6565
fwrite( STDOUT, (string) ob_get_clean() );
6666
}
6767
}
@@ -133,8 +133,15 @@ private static function create_prompt_cmd( $prompt, $history_path ) {
133133
$history_path = escapeshellarg( $history_path );
134134
if ( getenv( 'WP_CLI_CUSTOM_SHELL' ) ) {
135135
$shell_binary = (string) getenv( 'WP_CLI_CUSTOM_SHELL' );
136-
} else {
136+
} elseif ( is_file( '/bin/bash' ) && is_readable( '/bin/bash' ) ) {
137+
// Prefer /bin/bash when available since we use bash-specific commands.
137138
$shell_binary = '/bin/bash';
139+
} elseif ( getenv( 'SHELL' ) && self::is_bash_shell( (string) getenv( 'SHELL' ) ) ) {
140+
// Only use SHELL as fallback if it's bash (we use bash-specific commands).
141+
$shell_binary = (string) getenv( 'SHELL' );
142+
} else {
143+
// Final fallback for systems without /bin/bash.
144+
$shell_binary = 'bash';
138145
}
139146

140147
if ( ! is_file( $shell_binary ) || ! is_readable( $shell_binary ) ) {
@@ -155,6 +162,21 @@ private static function create_prompt_cmd( $prompt, $history_path ) {
155162
return "{$shell_binary} -c " . escapeshellarg( $cmd );
156163
}
157164

165+
/**
166+
* Check if a shell binary is bash or bash-compatible.
167+
*
168+
* @param string $shell_path Path to the shell binary.
169+
* @return bool True if the shell is bash, false otherwise.
170+
*/
171+
private static function is_bash_shell( $shell_path ) {
172+
if ( ! is_file( $shell_path ) || ! is_readable( $shell_path ) ) {
173+
return false;
174+
}
175+
// Check if the basename is exactly 'bash' or starts with 'bash' followed by a version/variant.
176+
$basename = basename( $shell_path );
177+
return 'bash' === $basename || 0 === strpos( $basename, 'bash-' );
178+
}
179+
158180
private function set_history_file() {
159181
$data = getcwd() . get_current_user();
160182

0 commit comments

Comments
 (0)