mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
281 lines
9.7 KiB
PHP
281 lines
9.7 KiB
PHP
<?php namespace System\Console;
|
|
|
|
use File;
|
|
use Config;
|
|
use Cms\Classes\Theme;
|
|
use Winter\Storm\Support\Str;
|
|
use Winter\Storm\Console\Command;
|
|
use Symfony\Component\Process\Process;
|
|
use System\Classes\MixAssets;
|
|
use System\Classes\PluginManager;
|
|
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
|
|
|
class MixInstall extends Command
|
|
{
|
|
/**
|
|
* @var string|null The default command name for lazy loading.
|
|
*/
|
|
protected static $defaultName = 'mix:install';
|
|
|
|
/**
|
|
* @var string The name and signature of this command.
|
|
*/
|
|
protected $signature = 'mix:install
|
|
{npmArgs?* : Arguments to pass through to the "npm" binary}
|
|
{--npm= : Defines a custom path to the "npm" binary}
|
|
{--p|package=* : Defines one or more packages to install}';
|
|
|
|
/**
|
|
* @var string The console command description.
|
|
*/
|
|
protected $description = 'Install Node.js dependencies required for mixed assets';
|
|
|
|
/**
|
|
* @var string The path to the "npm" executable.
|
|
*/
|
|
protected $npmPath = 'npm';
|
|
|
|
/**
|
|
* @var string Default version of Laravel Mix to install
|
|
*/
|
|
protected $defaultMixVersion = '^6.0.41';
|
|
|
|
/**
|
|
* @return array Terms used in messages.
|
|
*/
|
|
protected $terms = [
|
|
'complete' => '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();
|
|
}
|
|
}
|