'install', 'completed' => 'installed', ]; /** * @var string The NPM command to run. */ protected $npmCommand = 'install'; /** * Execute the console command. * @return int */ public function handle(): int { if ($this->option('npm')) { $this->npmPath = $this->option('npm', 'npm'); } if (!version_compare($this->getNpmVersion(), '7', '>')) { $this->error('"npm" version 7 or above must be installed to run this command.'); return 1; } $mixedAssets = MixAssets::instance(); $mixedAssets->fireCallbacks(); $registeredPackages = $mixedAssets->getPackages(); $requestedPackages = $this->option('package') ?: []; // Normalize the requestedPackages option if (count($requestedPackages)) { foreach ($requestedPackages as &$name) { $name = strtolower($name); } unset($name); } // Filter the registered packages to only include requested packages if (count($requestedPackages) && count($registeredPackages)) { $availablePackages = array_keys($registeredPackages); $cmsEnabled = in_array('Cms', Config::get('cms.loadModules')); // Autogenerate config files for packages that don't exist but can be autodiscovered foreach ($requestedPackages as $package) { // Check if the package is already registered if (in_array($package, $availablePackages)) { continue; } // Check if package could be a module (but explicitly ignore core Winter modules) if (Str::startsWith($package, 'module-') && !in_array($package, ['system', 'backend', 'cms'])) { $mixedAssets->registerPackage($package, base_path('modules/' . Str::after($package, 'module-') . '/winter.mix.js')); continue; } // Check if package could be a theme if ( $cmsEnabled && Str::startsWith($package, 'theme-') && Theme::exists(Str::after($package, 'theme-')) ) { $theme = Theme::load(Str::after($package, 'theme-')); $mixedAssets->registerPackage($package, $theme->getPath() . '/winter.mix.js'); continue; } // Check if a package could be a plugin if (PluginManager::instance()->exists($package)) { $mixedAssets->registerPackage($package, PluginManager::instance()->getPluginPath($package) . '/winter.mix.js'); continue; } } // Get an updated list of packages including any newly added packages $registeredPackages = $mixedAssets->getPackages(); // Filter the registered packages to only deal with the requested packages foreach (array_keys($registeredPackages) as $name) { if (!in_array($name, $requestedPackages)) { unset($registeredPackages[$name]); } } } if (!count($registeredPackages)) { if (count($requestedPackages)) { $this->error('No registered packages matched the requested packages for installation.'); return 1; } else { $this->info('No packages registered for mixing.'); return 0; } } // Load the main package.json for the project $packageJsonPath = base_path('package.json'); $packageJson = []; if (File::exists($packageJsonPath)) { $packageJson = json_decode(File::get($packageJsonPath), true); } $workspacesPackages = $packageJson['workspaces']['packages'] ?? []; $ignoredPackages = $packageJson['workspaces']['ignoredPackages'] ?? []; // Check to see if Laravel Mix is already present as a dependency if ( ( !isset($packageJson['dependencies']['laravel-mix']) && !isset($packageJson['devDependencies']['laravel-mix']) ) && $this->confirm('laravel-mix was not found as a dependency in package.json, would you like to add it?', true) ) { $packageJson['devDependencies'] = array_merge($packageJson['devDependencies'] ?? [], ['laravel-mix' => $this->defaultMixVersion]); $this->writePackageJson($packageJsonPath, $packageJson); } // Process each package foreach ($registeredPackages as $name => $package) { // Normalize package path across OS types $packagePath = Str::replace(DIRECTORY_SEPARATOR, '/', $package['path']); // Add the package path to the instance's package.json->workspaces->packages property if not present if ( !in_array($packagePath, $workspacesPackages) && !in_array($packagePath, $ignoredPackages) ) { if ( $this->confirm( sprintf( "Detected %s (%s), should it be added to your package.json?", $name, $packagePath ), true ) ) { $workspacesPackages[] = $packagePath; $this->info(sprintf( 'Adding %s (%s) to the workspaces.packages property in package.json', $name, $packagePath )); } else { $ignoredPackages[] = $packagePath; $this->warn( sprintf('Ignoring %s (%s)', $name, $packagePath) ); } asort($workspacesPackages); asort($ignoredPackages); $packageJson['workspaces']['packages'] = array_values($workspacesPackages); $packageJson['workspaces']['ignoredPackages'] = array_values($ignoredPackages); $this->writePackageJson($packageJsonPath, $packageJson); } // Detect missing winter.mix.js files and install them if (!File::exists($package['mix'])) { $this->info(sprintf( 'No Mix file found for %s, creating one at %s...', $name, $package['mix'] )); File::put($package['mix'], File::get(__DIR__ . '/fixtures/winter.mix.js.fixture')); } } // Ensure separation between package.json modification messages and rest of output $this->info(''); if ($this->installPackageDeps() !== 0) { $this->error("Unable to {$this->terms['complete']} dependencies."); } else { $this->info("Dependencies successfully {$this->terms['completed']}!"); } return 0; } /** * Write to the package.json file */ protected function writePackageJson(string $path, array $data): void { File::put($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } /** * Installs the dependencies for the given package. * * @return int */ protected function installPackageDeps() { $command = $this->argument('npmArgs') ?? []; array_unshift($command, 'npm', $this->npmCommand); $process = new Process($command, base_path(), null, null, null); // Attempt to set tty mode, catch and warn with the exception message if unsupported try { $process->setTty(true); } catch (\Throwable $e) { $this->warn($e->getMessage()); } try { return $process->run(function ($status, $stdout) { $this->getOutput()->write($stdout); }); } catch (ProcessSignaledException $e) { if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) { throw $e; } return 1; } $this->info(''); return $process->getExitCode(); } /** * Gets the install NPM version. * * @return string */ protected function getNpmVersion() { $process = new Process(['npm', '--version']); $process->run(); return $process->getOutput(); } }