Skip to content

Commit 8e0a4a9

Browse files
committed
feat: improve npm failure diagnostics with detailed error messages
1 parent 725b069 commit 8e0a4a9

1 file changed

Lines changed: 198 additions & 1 deletion

File tree

src/Service/NodePackageManager.php

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public function installNodeModules(string $path, SymfonyStyle $io, bool $isVerbo
6363
chdir($currentDir);
6464
return true;
6565
} catch (\Exception $e) {
66-
$io->error('Failed to install node modules: ' . $e->getMessage());
6766
chdir($currentDir);
67+
$this->diagnoseAndReportNpmFailure($path, $io, $e);
6868
return false;
6969
}
7070
}
@@ -135,4 +135,201 @@ public function checkOutdatedPackages(string $path, SymfonyStyle $io): void
135135

136136
chdir($currentDir);
137137
}
138+
139+
/**
140+
* Diagnose npm installation failure and provide specific error messages
141+
*
142+
* @param string $path
143+
* @param SymfonyStyle $io
144+
* @param \Exception $exception
145+
* @return void
146+
*/
147+
private function diagnoseAndReportNpmFailure(string $path, SymfonyStyle $io, \Exception $exception): void
148+
{
149+
$io->error('Failed to install node modules.');
150+
$io->newLine();
151+
152+
// Check 1: npm availability
153+
if (!$this->isCommandAvailable('npm')) {
154+
$io->error('npm is not installed or not available in PATH.');
155+
$io->writeln('<fg=yellow>Fix:</>');
156+
$io->writeln(' Install Node.js and npm: https://nodejs.org/');
157+
return;
158+
}
159+
160+
// Check 2: node availability
161+
if (!$this->isCommandAvailable('node')) {
162+
$io->error('Node.js is not installed or not available in PATH.');
163+
$io->writeln('<fg=yellow>Fix:</>');
164+
$io->writeln(' Install Node.js: https://nodejs.org/');
165+
return;
166+
}
167+
168+
// Check 3: package.json validity
169+
if (!$this->isPackageJsonValid($path)) {
170+
$io->error('package.json is missing or contains invalid JSON.');
171+
$io->writeln('<fg=yellow>Fix:</>');
172+
$io->writeln(' Verify package.json exists at: ' . $path . '/package.json');
173+
return;
174+
}
175+
176+
// Check 4: package-lock.json corruption
177+
if ($this->fileDriver->isExists($path . '/package-lock.json')) {
178+
if (!$this->isPackageLockValid($path)) {
179+
$io->error('package-lock.json is corrupted or invalid.');
180+
$io->writeln('<fg=yellow>Fix:</>');
181+
$io->writeln(' rm -rf node_modules package-lock.json');
182+
$io->writeln(' npm install');
183+
return;
184+
}
185+
}
186+
187+
// Check 5: Permission issues
188+
if ($this->hasPermissionIssues($path)) {
189+
$io->error('Permission denied when accessing node_modules directory.');
190+
$io->writeln('<fg=yellow>Fix:</>');
191+
$io->writeln(' sudo chown -R $(whoami) ' . $path . '/node_modules');
192+
$io->writeln(' Or delete and reinstall: rm -rf node_modules && npm install');
193+
return;
194+
}
195+
196+
// Check 6: Disk space
197+
if (!$this->hasSufficientDiskSpace($path)) {
198+
$io->error('Insufficient disk space to install node modules.');
199+
$io->writeln('<fg=yellow>Fix:</>');
200+
$io->writeln(' Free up disk space and try again.');
201+
return;
202+
}
203+
204+
// Check 7: npm cache corruption
205+
$errorMessage = $exception->getMessage();
206+
if (str_contains($errorMessage, 'EINTEGRITY') || str_contains($errorMessage, 'sha')) {
207+
$io->error('npm cache is corrupted (integrity checksum mismatch).');
208+
$io->writeln('<fg=yellow>Fix:</>');
209+
$io->writeln(' npm cache clean --force');
210+
$io->writeln(' rm -rf node_modules package-lock.json');
211+
$io->writeln(' npm install');
212+
return;
213+
}
214+
215+
// Check 8: Network issues
216+
if (str_contains($errorMessage, 'ENOTFOUND') || str_contains($errorMessage, 'ETIMEDOUT')) {
217+
$io->error('Network error: Cannot reach npm registry.');
218+
$io->writeln('<fg=yellow>Fix:</>');
219+
$io->writeln(' Check your internet connection and try again.');
220+
$io->writeln(' Or configure a different registry: npm config set registry https://registry.npmjs.org/');
221+
return;
222+
}
223+
224+
// Fallback: Show actual npm error
225+
$io->error('npm install failed with an unknown error.');
226+
$io->writeln('<fg=yellow>Error details:</>');
227+
$io->writeln(' ' . $exception->getMessage());
228+
$io->newLine();
229+
$io->writeln('<fg=yellow>Suggested fix:</>');
230+
$io->writeln(' 1. npm cache clean --force');
231+
$io->writeln(' 2. rm -rf node_modules package-lock.json');
232+
$io->writeln(' 3. npm install');
233+
}
234+
235+
/**
236+
* Check if a command is available
237+
*
238+
* @param string $command
239+
* @return bool
240+
*/
241+
private function isCommandAvailable(string $command): bool
242+
{
243+
try {
244+
$this->shell->execute('which ' . $command . ' > /dev/null 2>&1');
245+
return true;
246+
} catch (\Exception $e) {
247+
return false;
248+
}
249+
}
250+
251+
/**
252+
* Check if package.json is valid
253+
*
254+
* @param string $path
255+
* @return bool
256+
*/
257+
private function isPackageJsonValid(string $path): bool
258+
{
259+
$packageJsonPath = $path . '/package.json';
260+
if (!$this->fileDriver->isExists($packageJsonPath)) {
261+
return false;
262+
}
263+
264+
try {
265+
$content = $this->fileDriver->fileGetContents($packageJsonPath);
266+
json_decode($content, true);
267+
return json_last_error() === JSON_ERROR_NONE;
268+
} catch (\Exception $e) {
269+
return false;
270+
}
271+
}
272+
273+
/**
274+
* Check if package-lock.json is valid
275+
*
276+
* @param string $path
277+
* @return bool
278+
*/
279+
private function isPackageLockValid(string $path): bool
280+
{
281+
$lockPath = $path . '/package-lock.json';
282+
if (!$this->fileDriver->isExists($lockPath)) {
283+
return true; // If it doesn't exist, it's not invalid
284+
}
285+
286+
try {
287+
$content = $this->fileDriver->fileGetContents($lockPath);
288+
json_decode($content, true);
289+
return json_last_error() === JSON_ERROR_NONE;
290+
} catch (\Exception $e) {
291+
return false;
292+
}
293+
}
294+
295+
/**
296+
* Check for permission issues in node_modules
297+
*
298+
* @param string $path
299+
* @return bool
300+
*/
301+
private function hasPermissionIssues(string $path): bool
302+
{
303+
$nodeModulesPath = $path . '/node_modules';
304+
if (!$this->fileDriver->isDirectory($nodeModulesPath)) {
305+
return false; // Directory doesn't exist yet, so no permission issues
306+
}
307+
308+
try {
309+
// Try to write a test file
310+
$testFile = $nodeModulesPath . '/.write-test-' . time();
311+
$this->fileDriver->filePutContents($testFile, 'test');
312+
$this->fileDriver->deleteFile($testFile);
313+
return false;
314+
} catch (\Exception $e) {
315+
return true;
316+
}
317+
}
318+
319+
/**
320+
* Check if there is sufficient disk space
321+
*
322+
* @param string $path
323+
* @return bool
324+
*/
325+
private function hasSufficientDiskSpace(string $path): bool
326+
{
327+
try {
328+
$freeSpace = disk_free_space($path);
329+
// Require at least 100MB free space
330+
return $freeSpace !== false && $freeSpace > 100 * 1024 * 1024;
331+
} catch (\Exception $e) {
332+
return true; // If we can't check, assume it's fine
333+
}
334+
}
138335
}

0 commit comments

Comments
 (0)