Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
410 lines
15 KiB
PHP
410 lines
15 KiB
PHP
<?php
|
|
|
|
namespace System\Console\Asset;
|
|
|
|
use Cms\Classes\Theme;
|
|
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
|
use Symfony\Component\Process\Process;
|
|
use System\Classes\Asset\PackageJson;
|
|
use System\Classes\Asset\PackageManager;
|
|
use System\Classes\PluginManager;
|
|
use Winter\Storm\Console\Command;
|
|
use Winter\Storm\Exception\SystemException;
|
|
use Winter\Storm\Support\Facades\Config;
|
|
use Winter\Storm\Support\Facades\File;
|
|
use Winter\Storm\Support\Str;
|
|
|
|
abstract class AssetInstall extends Command
|
|
{
|
|
/**
|
|
* The path to the "npm" executable.
|
|
*/
|
|
protected string $npmPath = 'npm';
|
|
|
|
/**
|
|
* Terms used in messages.
|
|
*/
|
|
protected array $terms = [
|
|
'complete' => 'install',
|
|
'completed' => 'installed',
|
|
];
|
|
|
|
/**
|
|
* Path to package json, if null use base_path.
|
|
*/
|
|
protected ?string $packageJsonPath = null;
|
|
|
|
/**
|
|
* Type of asset to be installed, @see PackageManager
|
|
*/
|
|
protected string $assetType;
|
|
|
|
/**
|
|
* The asset config file
|
|
*/
|
|
protected string $configFile;
|
|
|
|
/**
|
|
* The required dependencies for this compiler
|
|
*/
|
|
protected array $requiredDependencies = [];
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle(): int
|
|
{
|
|
if ($npmPath = $this->option('npm')) {
|
|
if (!File::exists($npmPath) || !is_executable($npmPath)) {
|
|
$this->error('The supplied --npm path does not exist or is not executable.');
|
|
return 1;
|
|
}
|
|
$this->npmPath = $npmPath;
|
|
}
|
|
|
|
if (!version_compare($this->getNpmVersion(), '7', '>')) {
|
|
$this->error('"npm" version 7 or above must be installed to run this command.');
|
|
return 1;
|
|
}
|
|
|
|
// If a custom path is passed, then validate it
|
|
if ($packageJsonPath = $this->option('package-json')) {
|
|
// If this is not an absolute path, then make it relative
|
|
if (!str_starts_with($packageJsonPath, '/')) {
|
|
$packageJsonPath = base_path($packageJsonPath);
|
|
}
|
|
|
|
if (!File::exists($packageJsonPath)) {
|
|
$this->error('The supplied --package-json path does not exist.');
|
|
return 1;
|
|
}
|
|
|
|
$this->packageJsonPath = $packageJsonPath;
|
|
}
|
|
|
|
// Get any packages the user has requested
|
|
$requestedPackages = $this->argument('assetPackage') ?: [];
|
|
|
|
$registeredPackages = $this->getRegisteredPackages($requestedPackages);
|
|
|
|
if (!$registeredPackages) {
|
|
if ($requestedPackages) {
|
|
$this->error('No registered packages matched the requested packages for installation.');
|
|
return 1;
|
|
}
|
|
|
|
$this->info('No packages registered for mixing.');
|
|
return 0;
|
|
}
|
|
|
|
// Get base package.json
|
|
$packageJson = new PackageJson($this->packageJsonPath ?? base_path('package.json'));
|
|
|
|
// Ensure asset compiling packages are set in package.json, then save
|
|
$this->validateRequireDependenciesPresent($packageJson)
|
|
->save();
|
|
|
|
// Process compilable asset packages, then save
|
|
$this->processPackages($registeredPackages, $packageJson)
|
|
->save();
|
|
|
|
if (!$this->option('no-install')) {
|
|
// Ensure separation between package.json modification messages and rest of output
|
|
$this->info('');
|
|
|
|
if ($this->runNpmInstall() !== 0) {
|
|
$this->error("Unable to {$this->terms['complete']} dependencies.");
|
|
return 1;
|
|
}
|
|
|
|
$this->info("Dependencies successfully {$this->terms['completed']}!");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Returns all packages registered by the system filtered by requestedPackages if defined
|
|
* @throws SystemException
|
|
*/
|
|
protected function getRegisteredPackages(array $requestedPackages = []): array
|
|
{
|
|
$packageManager = $this->getPackageManager();
|
|
|
|
$registeredPackages = $packageManager->getPackages($this->assetType, true);
|
|
|
|
// Normalize the requestedPackages option
|
|
$requestedPackages = array_map(fn ($name) => strtolower($name), $requestedPackages);
|
|
|
|
// Filter the registered packages to only include requested packages
|
|
if (count($requestedPackages) && count($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 (isset($registeredPackages[$package])) {
|
|
continue;
|
|
}
|
|
|
|
switch ($packageManager->getPackageTypeFromName($package)) {
|
|
case PackageManager::TYPE_MODULE:
|
|
$packageManager->registerPackage(
|
|
$package,
|
|
base_path('modules/' . Str::after($package, 'module-') . '/' . $this->configFile),
|
|
$this->assetType
|
|
);
|
|
break;
|
|
case PackageManager::TYPE_THEME:
|
|
if (!$cmsEnabled) {
|
|
break;
|
|
}
|
|
$theme = Theme::load(Str::after($package, 'theme-'));
|
|
$packageManager->registerPackage(
|
|
$package,
|
|
$theme->getPath() . '/' . $this->configFile,
|
|
$this->assetType
|
|
);
|
|
break;
|
|
case PackageManager::TYPE_PLUGIN:
|
|
$packageManager->registerPackage(
|
|
$package,
|
|
PluginManager::instance()->getPluginPath($package) . '/' . $this->configFile,
|
|
$this->assetType
|
|
);
|
|
break;
|
|
case null:
|
|
throw new SystemException(sprintf(
|
|
'PackageNotFoundException: The package `%s` does not exist.',
|
|
$package
|
|
));
|
|
}
|
|
}
|
|
|
|
// Get an updated list of packages including any newly added packages
|
|
$registeredPackages = $packageManager->getPackages($this->assetType, true);
|
|
|
|
// 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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $registeredPackages;
|
|
}
|
|
|
|
/**
|
|
* Checks if the package.json of a package has the dependencies required for this command and asks the user if
|
|
* they want to install them if not present.
|
|
*/
|
|
protected function validateRequireDependenciesPresent(PackageJson $packageJson): PackageJson
|
|
{
|
|
// Check to see if required packages are already present as a dependency
|
|
foreach ($this->requiredDependencies as $dependency => $version) {
|
|
if (
|
|
!$packageJson->hasDependency($dependency)
|
|
&& $this->confirm($dependency . ' was not found as a dependency in package.json, would you like to add it?', true)
|
|
) {
|
|
$packageJson->addDependency($dependency, $version, dev: true);
|
|
}
|
|
}
|
|
|
|
return $packageJson;
|
|
}
|
|
|
|
/**
|
|
* Validates if the packages passed can be installed and if possible, installs them.
|
|
* @throws SystemException
|
|
* @throws PackageNotFoundException
|
|
*/
|
|
protected function processPackages(array $registeredPackages, PackageJson $packageJson): PackageJson
|
|
{
|
|
// Check if the user requested a specific package for install
|
|
if ($requestedPackages = array_map(fn ($name) => strtolower($name), $this->argument('assetPackage'))) {
|
|
$packageManager = $this->getPackageManager();
|
|
foreach ($requestedPackages as $requestedPackage) {
|
|
// We did not find the package, exit
|
|
if (!isset($registeredPackages[$requestedPackage])) {
|
|
if ($detected = $packageManager->getPackage($requestedPackage, true)) {
|
|
switch (count($detected)) {
|
|
case 1:
|
|
if ($detected[0]['type'] !== $this->assetType) {
|
|
throw new SystemException(sprintf(
|
|
'PackageNotConfiguredException: The requested package `%s` is only configured for %s. Run `php artisan %s:create %1$s`',
|
|
$requestedPackage,
|
|
$detected[0]['type'],
|
|
$this->assetType
|
|
));
|
|
}
|
|
|
|
if ($detected[0]['ignored']) {
|
|
throw new SystemException(sprintf(
|
|
'PackageIgnoredException: The requested package `%s` is ignored, remove it from package.json to continue',
|
|
$requestedPackage,
|
|
));
|
|
}
|
|
break;
|
|
case 2:
|
|
default:
|
|
if (($detected[0]['ignored'] ?? false) || ($detected[1]['ignored'] ?? false)) {
|
|
throw new SystemException(sprintf(
|
|
'PackageIgnoredException: The requested package `%s` is ignored, remove it from package.json to continue',
|
|
$requestedPackage,
|
|
));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
throw new SystemException(sprintf(
|
|
'PackageNotFoundException: The requested package `%s` could not be found.',
|
|
$requestedPackage,
|
|
));
|
|
}
|
|
|
|
$this->processPackage($packageJson, $requestedPackage, $registeredPackages[$requestedPackage], true);
|
|
}
|
|
|
|
return $packageJson;
|
|
}
|
|
|
|
// Process each found package
|
|
foreach ($registeredPackages as $name => $package) {
|
|
$this->processPackage($packageJson, $name, $package);
|
|
}
|
|
|
|
return $packageJson;
|
|
}
|
|
|
|
/**
|
|
* Adds a package to the project workspace or mark it as ignored based on user input
|
|
*/
|
|
protected function processPackage(PackageJson $packageJson, string $name, array $package, bool $force = false): bool
|
|
{
|
|
// Normalize package path across OS types
|
|
$packagePath = Str::replace(DIRECTORY_SEPARATOR, '/', $package['path']);
|
|
|
|
// Nicely report if the package is already in the workspace
|
|
if ($packageJson->hasWorkspace($packagePath)) {
|
|
$this->warn(sprintf(
|
|
'Package %s (%s) is already included in workspaces.packages.',
|
|
$name,
|
|
$packagePath
|
|
));
|
|
|
|
return true;
|
|
}
|
|
|
|
if ($packageJson->hasIgnoredPackage($packagePath)) {
|
|
$this->warn(sprintf(
|
|
'The requested package %s (%s) is ignored, remove it from package.json to continue.',
|
|
$name,
|
|
$packagePath
|
|
));
|
|
|
|
return true;
|
|
}
|
|
|
|
// Add the package path to the instance's package.json->workspaces->packages property if not present
|
|
if (!$packageJson->hasWorkspace($packagePath) && !$packageJson->hasIgnoredPackage($packagePath)) {
|
|
if (
|
|
$force
|
|
|| $this->confirm(
|
|
sprintf(
|
|
"Detected %s (%s), would you like to add it to package.json to include it in your project workspace? Answer no to ignore it.",
|
|
$name,
|
|
$packagePath
|
|
),
|
|
true
|
|
)
|
|
) {
|
|
$packageJson->addWorkspace($packagePath);
|
|
$this->info(sprintf(
|
|
'Adding %s (%s) to the workspaces.packages property in package.json',
|
|
$name,
|
|
$packagePath
|
|
));
|
|
} else {
|
|
$packageJson->addIgnoredPackage($packagePath);
|
|
$this->warn(
|
|
sprintf('Ignoring %s (%s)', $name, $packagePath)
|
|
);
|
|
}
|
|
}
|
|
|
|
// Detect missing config files and provide feedback
|
|
if (!File::exists($package['config'])) {
|
|
$this->info(sprintf(
|
|
'No config file found for %s, you should run %s:config',
|
|
$name,
|
|
$this->assetType
|
|
));
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Installs the dependencies for the given package.
|
|
*/
|
|
protected function runNpmInstall(): int
|
|
{
|
|
$process = new Process(
|
|
command: [$this->npmPath, 'install'],
|
|
cwd: $this->packageJsonPath ? dirname($this->packageJsonPath) : base_path(),
|
|
timeout: null
|
|
);
|
|
|
|
if (!$this->option('disable-tty')) {
|
|
try {
|
|
$process->setTty(true);
|
|
} catch (\Throwable $e) {
|
|
// This will fail on unsupported systems
|
|
}
|
|
}
|
|
|
|
try {
|
|
return $process->run(function ($status, $stdout) {
|
|
if (!$this->option('silent')) {
|
|
$this->getOutput()->write($stdout);
|
|
}
|
|
});
|
|
} catch (ProcessSignaledException $e) {
|
|
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
|
|
throw $e;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the root package.json as a PackageManager object
|
|
*/
|
|
protected function getPackageManager(): PackageManager
|
|
{
|
|
// Flush the instance
|
|
$packageManager = PackageManager::instance()->fireCallbacks();
|
|
// Ensure the instance follows any custom package.json
|
|
if ($this->packageJsonPath) {
|
|
$packageManager->setPackageJsonPath($this->packageJsonPath);
|
|
}
|
|
|
|
return $packageManager;
|
|
}
|
|
|
|
/**
|
|
* Gets the installed NPM version.
|
|
*/
|
|
protected function getNpmVersion(): string
|
|
{
|
|
$process = new Process([$this->npmPath, '--version']);
|
|
$process->run();
|
|
return $process->getOutput();
|
|
}
|
|
}
|