-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNodeSetupValidator.php
More file actions
328 lines (285 loc) · 10.5 KB
/
Copy pathNodeSetupValidator.php
File metadata and controls
328 lines (285 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
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.'
);
}
/**
* 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;
}
}