-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add NodeSetupValidator for validating Magento default setup files #142
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,328 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace OpenForgeProject\MageForge\Service; | ||
|
|
||
| use Magento\Framework\Filesystem\Driver\File as FileDriver; | ||
| use Symfony\Component\Console\Style\SymfonyStyle; | ||
| use function Laravel\Prompts\confirm; | ||
|
|
||
| /** | ||
| * Service for validating and restoring Node.js setup files for Magento Standard themes | ||
| */ | ||
| class NodeSetupValidator | ||
| { | ||
| private const REQUIRED_FILES = [ | ||
| 'package.json', | ||
| 'Gruntfile.js', | ||
| 'grunt-config.json', | ||
| ]; | ||
|
|
||
| /** @var array<string> Files that will be generated by npm install */ | ||
| private const GENERATED_FILES = [ | ||
| 'package-lock.json', | ||
| ]; | ||
|
|
||
| private const REQUIRED_DIRECTORIES = [ | ||
| 'node_modules', | ||
| ]; | ||
|
|
||
| private const NODE_MODULES_DIR = 'node_modules/'; | ||
|
|
||
| private const MAGENTO_BASE_PATH = 'vendor/magento/magento2-base'; | ||
|
|
||
| /** | ||
| * Constructor | ||
| * | ||
| * @param FileDriver $fileDriver | ||
| * @param NodePackageManager $nodePackageManager | ||
| */ | ||
| public function __construct( | ||
| private readonly FileDriver $fileDriver, | ||
| private readonly NodePackageManager $nodePackageManager | ||
| ) { | ||
| } | ||
|
|
||
| /** | ||
| * Validate Node.js setup files and offer to restore missing ones | ||
| * | ||
| * @param string $rootPath Root directory to check (usually '.') | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @param bool $isVerbose Whether to show verbose output | ||
| * @return bool True if validation passed or files were restored successfully | ||
| */ | ||
| public function validateAndRestore(string $rootPath, SymfonyStyle $io, bool $isVerbose): bool | ||
| { | ||
| $missing = $this->getMissingFiles($rootPath); | ||
|
|
||
| if (empty($missing)) { | ||
| if ($isVerbose) { | ||
| $io->success('All required Node.js setup files are present.'); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| // Separate source files from generated files/directories | ||
| $missingSourceFiles = $this->filterSourceFiles($missing); | ||
|
|
||
| // If only generated files/directories are missing, restore them directly without asking | ||
| if (empty($missingSourceFiles)) { | ||
| return $this->restoreGeneratedFilesAutomatically($rootPath, $missing, $io, $isVerbose); | ||
| } | ||
|
|
||
| // Ask user if they want to restore missing files | ||
| if (!$this->promptUserForRestoration($missing, $io)) { | ||
| $io->info('Skipping file restoration.'); | ||
| return false; | ||
| } | ||
|
|
||
| // Restore missing files | ||
| return $this->restoreMissingFiles($rootPath, $missing, $io, $isVerbose); | ||
| } | ||
|
|
||
| /** | ||
| * Get list of missing files and directories | ||
| * | ||
| * @param string $rootPath Root directory to check | ||
| * @return array<string> List of missing file/directory names | ||
| */ | ||
| private function getMissingFiles(string $rootPath): array | ||
| { | ||
| $missing = []; | ||
|
|
||
| // Check required files | ||
| foreach (self::REQUIRED_FILES as $file) { | ||
| if (!$this->fileDriver->isExists($rootPath . '/' . $file)) { | ||
| $missing[] = $file; | ||
| } | ||
| } | ||
|
|
||
| // Check generated files | ||
| foreach (self::GENERATED_FILES as $file) { | ||
| if (!$this->fileDriver->isExists($rootPath . '/' . $file)) { | ||
| $missing[] = $file; | ||
| } | ||
| } | ||
|
|
||
| // Check required directories | ||
| foreach (self::REQUIRED_DIRECTORIES as $directory) { | ||
| if (!$this->fileDriver->isDirectory($rootPath . '/' . $directory)) { | ||
| $missing[] = $directory . '/'; | ||
| } | ||
| } | ||
|
|
||
| return $missing; | ||
| } | ||
|
|
||
| /** | ||
| * Filter source files from the list of missing files | ||
| * | ||
| * Returns only files that need to be copied from Magento base | ||
| * (excludes generated files and directories) | ||
| * | ||
| * @param array<string> $missing List of missing files/directories | ||
| * @return array<string> List of source files that need to be restored | ||
| */ | ||
| private function filterSourceFiles(array $missing): array | ||
| { | ||
| return array_filter($missing, fn($item) => !$this->isGeneratedFileOrDirectory($item)); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a file or directory is generated (not a source file) | ||
| * | ||
| * @param string $item File or directory name | ||
| * @return bool True if the item is generated by npm install | ||
| */ | ||
| private function isGeneratedFileOrDirectory(string $item): bool | ||
| { | ||
| return in_array($item, self::GENERATED_FILES, true) || $item === self::NODE_MODULES_DIR; | ||
| } | ||
|
|
||
| /** | ||
| * Restore only generated files/directories automatically without user prompt | ||
| * | ||
| * @param string $rootPath Root directory where files should be restored | ||
| * @param array<string> $missing List of missing files/directories | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @param bool $isVerbose Whether to show verbose output | ||
| * @return bool True if restoration was successful | ||
| */ | ||
| private function restoreGeneratedFilesAutomatically( | ||
| string $rootPath, | ||
| array $missing, | ||
| SymfonyStyle $io, | ||
| bool $isVerbose | ||
| ): bool { | ||
| if ($isVerbose) { | ||
| $io->note('Detected missing generated files/directories. Installing automatically...'); | ||
| foreach ($missing as $item) { | ||
| $io->writeln(" - {$item}"); | ||
| } | ||
| } | ||
| return $this->restoreMissingFiles($rootPath, $missing, $io, $isVerbose); | ||
| } | ||
|
|
||
| /** | ||
| * Prompt user to confirm file restoration | ||
| * | ||
| * @param array<string> $missing List of missing files/directories | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @return bool True if user confirms restoration | ||
| */ | ||
| private function promptUserForRestoration(array $missing, SymfonyStyle $io): bool | ||
| { | ||
| // Display missing files | ||
| $io->warning('The following required files/directories are missing:'); | ||
| foreach ($missing as $item) { | ||
| $suffix = $this->isGeneratedFileOrDirectory($item) | ||
| ? ' (will be generated by npm install)' | ||
| : ''; | ||
| $io->writeln(" - {$item}{$suffix}"); | ||
| } | ||
| $io->newLine(); | ||
|
|
||
| // Ask user if they want to restore | ||
| return confirm( | ||
| label: 'Would you like to restore missing files from Magento base?', | ||
| default: true, | ||
| hint: 'This will copy the standard Magento files to your project root.' | ||
| ); | ||
|
Comment on lines
+186
to
+190
|
||
| } | ||
|
|
||
| /** | ||
| * Restore missing files from Magento base installation | ||
| * | ||
| * @param string $rootPath Root directory where files should be restored | ||
| * @param array<string> $missing List of missing files/directories | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @param bool $isVerbose Whether to show verbose output | ||
| * @return bool True if restoration was successful | ||
| */ | ||
| private function restoreMissingFiles( | ||
| string $rootPath, | ||
| array $missing, | ||
| SymfonyStyle $io, | ||
| bool $isVerbose | ||
| ): bool { | ||
| if (empty($missing)) { | ||
| return true; | ||
| } | ||
|
|
||
| $basePath = self::MAGENTO_BASE_PATH; | ||
|
|
||
| if (!$this->fileDriver->isDirectory($basePath)) { | ||
| $io->error(sprintf( | ||
| 'Magento base directory not found at: %s', | ||
| $basePath | ||
| )); | ||
| return false; | ||
| } | ||
|
|
||
| $restored = []; | ||
| $failed = []; | ||
|
|
||
| foreach ($missing as $item) { | ||
| // Skip generated files/directories - they will be created by npm install | ||
| if ($this->isGeneratedFileOrDirectory($item)) { | ||
| if ($isVerbose) { | ||
| $io->note("Skipping {$item} - will be generated by npm install"); | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| $sourcePath = $basePath . '/' . $item; | ||
| $targetPath = $rootPath . '/' . $item; | ||
|
|
||
| if (!$this->fileDriver->isExists($sourcePath)) { | ||
| if ($isVerbose) { | ||
| $io->warning("Source file not found: {$sourcePath}"); | ||
| } | ||
| $failed[] = $item; | ||
| continue; | ||
| } | ||
|
|
||
| try { | ||
| $this->fileDriver->copy($sourcePath, $targetPath); | ||
| $restored[] = $item; | ||
|
|
||
| if ($isVerbose) { | ||
| $io->writeln("✓ Restored: {$item}"); | ||
| } | ||
| } catch (\Exception $e) { | ||
| $io->error("Failed to restore {$item}: " . $e->getMessage()); | ||
| $failed[] = $item; | ||
| } | ||
| } | ||
|
|
||
| $this->displayRestorationSummary($restored, $failed, $io); | ||
|
|
||
| // If we restored any files or node_modules is missing, run npm install | ||
| if ($this->shouldRunNpmInstall($restored, $missing)) { | ||
| return $this->runNpmInstall($rootPath, $io, $isVerbose) && empty($failed); | ||
| } | ||
|
|
||
| return empty($failed); | ||
| } | ||
|
|
||
| /** | ||
| * Display summary of restoration results | ||
| * | ||
| * @param array<string> $restored List of successfully restored files | ||
| * @param array<string> $failed List of failed files | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @return void | ||
| */ | ||
| private function displayRestorationSummary(array $restored, array $failed, SymfonyStyle $io): void | ||
| { | ||
| if (!empty($restored)) { | ||
| $io->success(sprintf( | ||
| 'Restored %d file(s): %s', | ||
| count($restored), | ||
| implode(', ', $restored) | ||
| )); | ||
| } | ||
|
|
||
| if (!empty($failed)) { | ||
| $io->warning(sprintf( | ||
| 'Failed to restore %d file(s): %s', | ||
| count($failed), | ||
| implode(', ', $failed) | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if npm install should be run | ||
| * | ||
| * @param array<string> $restored List of restored files | ||
| * @param array<string> $missing List of missing files | ||
| * @return bool True if npm install should be run | ||
| */ | ||
| private function shouldRunNpmInstall(array $restored, array $missing): bool | ||
| { | ||
| return !empty($restored) || in_array(self::NODE_MODULES_DIR, $missing, true); | ||
| } | ||
|
|
||
| /** | ||
| * Run npm install to create node_modules and generated files | ||
| * | ||
| * @param string $rootPath Root directory | ||
| * @param SymfonyStyle $io Console IO for output | ||
| * @param bool $isVerbose Whether to show verbose output | ||
| * @return bool True if npm install was successful | ||
| */ | ||
| private function runNpmInstall(string $rootPath, SymfonyStyle $io, bool $isVerbose): bool | ||
| { | ||
| $io->newLine(); | ||
| $io->text('Installing Node.js dependencies...'); | ||
|
|
||
| if (!$this->nodePackageManager->installNodeModules($rootPath, $io, $isVerbose)) { | ||
| $io->error('Failed to install Node.js dependencies.'); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.