Skip to content

Commit 31fb4c8

Browse files
committed
Extract WSL logic to WithWSL trait
Moved WSL-related methods from PhpStormCopilot to a new WithWSL trait for better separation of concerns and code reuse. Updated PhpStormCopilot to use the trait and refactored WSL detection and MCP installation logic accordingly.
1 parent 81db39a commit 31fb4c8

File tree

2 files changed

+131
-119
lines changed

2 files changed

+131
-119
lines changed

src/Concerns/WithWSL.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace Revolution\Laravel\Boost\Concerns;
4+
5+
use Illuminate\Support\Facades\Process;
6+
7+
trait WithWSL
8+
{
9+
protected function isWSL(): bool
10+
{
11+
return ! empty(getenv('WSL_DISTRO_NAME'));
12+
}
13+
14+
protected function installMcpViaWsl(string $name, string $command, array $args): bool
15+
{
16+
// Get Windows LOCALAPPDATA path via wslvar command
17+
$localAppDataResult = Process::run('wslvar LOCALAPPDATA');
18+
$localAppData = trim($localAppDataResult->output());
19+
20+
if (empty($localAppData)) {
21+
return false;
22+
}
23+
24+
$winPath = "{$localAppData}\\github-copilot\\intellij";
25+
$filePath = "{$winPath}\\mcp.json";
26+
27+
// Read existing config via PowerShell
28+
$readCommand = "powershell.exe -NoProfile -Command \"if (Test-Path '{$filePath}') { Get-Content '{$filePath}' -Raw } else { '{}' }\"";
29+
$result = Process::run($readCommand);
30+
31+
$config = json_decode($result->output() ?: '{}', true) ?: [];
32+
33+
if (! isset($config[$this->mcpConfigKey()])) {
34+
$config[$this->mcpConfigKey()] = [];
35+
}
36+
37+
// Transform command and args for PhpStorm WSL compatibility
38+
$transformed = $this->transformMcpCommandForWsl($command, $args);
39+
40+
$config[$this->mcpConfigKey()][$name] = [
41+
'command' => $transformed['command'],
42+
'args' => $transformed['args'],
43+
];
44+
45+
// Remove empty arrays from existing config to avoid compatibility issues
46+
$config = $this->removeEmptyArrays($config);
47+
48+
$jsonContent = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
49+
50+
// Use Windows TEMP path directly
51+
$tempFileName = 'mcp_'.uniqid().'.json';
52+
$winTempPath = "{$localAppData}\\Temp\\{$tempFileName}";
53+
54+
// Write JSON content to temp file via PowerShell with Base64 encoding
55+
$base64Content = base64_encode($jsonContent);
56+
$writeTempCommand = 'powershell.exe -NoProfile -Command "'
57+
."[System.IO.File]::WriteAllBytes('{$winTempPath}', [System.Convert]::FromBase64String('{$base64Content}'))\"";
58+
59+
$writeTempResult = Process::run($writeTempCommand);
60+
61+
if (! $writeTempResult->successful()) {
62+
return false;
63+
}
64+
65+
// Create directory and copy file via PowerShell
66+
$copyCommand = 'powershell.exe -NoProfile -Command "'
67+
."New-Item -ItemType Directory -Path '{$winPath}' -Force | Out-Null; "
68+
."Copy-Item -Path '{$winTempPath}' -Destination '{$filePath}' -Force; "
69+
."Remove-Item -Path '{$winTempPath}' -Force\"";
70+
71+
$copyResult = Process::run($copyCommand);
72+
73+
return $copyResult->successful();
74+
}
75+
76+
/**
77+
* Transform MCP command and args to PhpStorm-compatible WSL format.
78+
*
79+
* @param string $command The command from installMcpViaWsl (e.g., 'wsl', './vendor/bin/sail', or absolute sail path)
80+
* @param array $args The arguments array
81+
* @return array{command: string, args: array} The transformed config for PhpStorm
82+
*/
83+
public function transformMcpCommandForWsl(string $command, array $args): array
84+
{
85+
// Case 1: Sail is being used (command is ./vendor/bin/sail or absolute path to sail)
86+
if (str_ends_with($command, '/vendor/bin/sail') || str_ends_with($command, '\\vendor\\bin\\sail')) {
87+
// Expected args: ["artisan", "boost:mcp"]
88+
// Transform to: wsl.exe --cd /absolute/path ./vendor/bin/sail artisan boost:mcp
89+
$projectPath = base_path();
90+
91+
return [
92+
'command' => 'wsl.exe',
93+
'args' => [
94+
'--cd',
95+
$projectPath,
96+
'./vendor/bin/sail',
97+
...$args,
98+
],
99+
];
100+
}
101+
102+
// Case 2: WSL without Sail (command is already 'wsl.exe')
103+
if (str_starts_with($command, 'wsl')) {
104+
// Args are already in correct format: [php_path, artisan_path, "boost:mcp"]
105+
// No transformation needed
106+
return [
107+
'command' => $command,
108+
'args' => $args,
109+
];
110+
}
111+
112+
// Case 3: Future-proof - direct PHP path (absolute or relative)
113+
// This might happen if boost changes its behavior in the future
114+
// Transform to: wsl.exe --cd /absolute/path {command} {args}
115+
$projectPath = base_path();
116+
117+
return [
118+
'command' => 'wsl.exe',
119+
'args' => [
120+
'--cd',
121+
$projectPath,
122+
$command,
123+
...$args,
124+
],
125+
];
126+
}
127+
}

src/PhpStormCopilot.php

Lines changed: 4 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
namespace Revolution\Laravel\Boost;
66

77
use Exception;
8-
use Illuminate\Support\Arr;
9-
use Illuminate\Support\Facades\Process;
108
use Laravel\Boost\Contracts\McpClient;
119
use Laravel\Boost\Install\CodeEnvironment\CodeEnvironment;
1210
use Laravel\Boost\Install\Enums\Platform;
11+
use Revolution\Laravel\Boost\Concerns\WithWSL;
1312

1413
class PhpStormCopilot extends CodeEnvironment implements McpClient
1514
{
15+
use WithWSL;
16+
1617
public bool $useAbsolutePathForMcp = true;
1718

1819
public function name(): string
@@ -98,9 +99,7 @@ protected function installFileMcp(string $key, string $command, array $args = []
9899
throw new Exception('Testbench is not supported. Consider using laravel-boost-copilot-cli instead.');
99100
}
100101

101-
$is_wsl = ! empty(getenv('WSL_DISTRO_NAME'));
102-
103-
if ($is_wsl) {
102+
if ($this->isWSL()) {
104103
return $this->installMcpViaWsl($key, $command, $args);
105104
}
106105

@@ -109,120 +108,6 @@ protected function installFileMcp(string $key, string $command, array $args = []
109108
return parent::installFileMcp($key, $transformed['command'], $transformed['args'], $env);
110109
}
111110

112-
protected function installMcpViaWsl(string $name, string $command, array $args): bool
113-
{
114-
// Get Windows LOCALAPPDATA path via wslvar command
115-
$localAppDataResult = Process::run('wslvar LOCALAPPDATA');
116-
$localAppData = trim($localAppDataResult->output());
117-
118-
if (empty($localAppData)) {
119-
return false;
120-
}
121-
122-
$winPath = "{$localAppData}\\github-copilot\\intellij";
123-
$filePath = "{$winPath}\\mcp.json";
124-
125-
// Read existing config via PowerShell
126-
$readCommand = "powershell.exe -NoProfile -Command \"if (Test-Path '{$filePath}') { Get-Content '{$filePath}' -Raw } else { '{}' }\"";
127-
$result = Process::run($readCommand);
128-
129-
$config = json_decode($result->output() ?: '{}', true) ?: [];
130-
131-
if (! isset($config[$this->mcpConfigKey()])) {
132-
$config[$this->mcpConfigKey()] = [];
133-
}
134-
135-
// Transform command and args for PhpStorm WSL compatibility
136-
$transformed = $this->transformMcpCommandForWsl($command, $args);
137-
138-
$config[$this->mcpConfigKey()][$name] = [
139-
'command' => $transformed['command'],
140-
'args' => $transformed['args'],
141-
];
142-
143-
// Remove empty arrays from existing config to avoid compatibility issues
144-
$config = $this->removeEmptyArrays($config);
145-
146-
$jsonContent = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
147-
148-
// Use Windows TEMP path directly
149-
$tempFileName = 'mcp_'.uniqid().'.json';
150-
$winTempPath = "{$localAppData}\\Temp\\{$tempFileName}";
151-
152-
// Write JSON content to temp file via PowerShell with Base64 encoding
153-
$base64Content = base64_encode($jsonContent);
154-
$writeTempCommand = 'powershell.exe -NoProfile -Command "'
155-
."[System.IO.File]::WriteAllBytes('{$winTempPath}', [System.Convert]::FromBase64String('{$base64Content}'))\"";
156-
157-
$writeTempResult = Process::run($writeTempCommand);
158-
159-
if (! $writeTempResult->successful()) {
160-
return false;
161-
}
162-
163-
// Create directory and copy file via PowerShell
164-
$copyCommand = 'powershell.exe -NoProfile -Command "'
165-
."New-Item -ItemType Directory -Path '{$winPath}' -Force | Out-Null; "
166-
."Copy-Item -Path '{$winTempPath}' -Destination '{$filePath}' -Force; "
167-
."Remove-Item -Path '{$winTempPath}' -Force\"";
168-
169-
$copyResult = Process::run($copyCommand);
170-
171-
return $copyResult->successful();
172-
}
173-
174-
/**
175-
* Transform MCP command and args to PhpStorm-compatible WSL format.
176-
*
177-
* @param string $command The command from installMcpViaWsl (e.g., 'wsl', './vendor/bin/sail', or absolute sail path)
178-
* @param array $args The arguments array
179-
* @return array{command: string, args: array} The transformed config for PhpStorm
180-
*/
181-
public function transformMcpCommandForWsl(string $command, array $args): array
182-
{
183-
// Case 1: Sail is being used (command is ./vendor/bin/sail or absolute path to sail)
184-
if (str_ends_with($command, '/vendor/bin/sail') || str_ends_with($command, '\\vendor\\bin\\sail')) {
185-
// Expected args: ["artisan", "boost:mcp"]
186-
// Transform to: wsl.exe --cd /absolute/path ./vendor/bin/sail artisan boost:mcp
187-
$projectPath = base_path();
188-
189-
return [
190-
'command' => 'wsl.exe',
191-
'args' => [
192-
'--cd',
193-
$projectPath,
194-
'./vendor/bin/sail',
195-
...$args,
196-
],
197-
];
198-
}
199-
200-
// Case 2: WSL without Sail (command is already 'wsl.exe')
201-
if (str_starts_with($command, 'wsl')) {
202-
// Args are already in correct format: [php_path, artisan_path, "boost:mcp"]
203-
// No transformation needed
204-
return [
205-
'command' => $command,
206-
'args' => $args,
207-
];
208-
}
209-
210-
// Case 3: Future-proof - direct PHP path (absolute or relative)
211-
// This might happen if boost changes its behavior in the future
212-
// Transform to: wsl.exe --cd /absolute/path {command} {args}
213-
$projectPath = base_path();
214-
215-
return [
216-
'command' => 'wsl.exe',
217-
'args' => [
218-
'--cd',
219-
$projectPath,
220-
$command,
221-
...$args,
222-
],
223-
];
224-
}
225-
226111
/**
227112
* Transform Sail command to use bash -c wrapper for proper working directory.
228113
*

0 commit comments

Comments
 (0)