feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
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
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
<?php
namespace System\Console\Asset;
use Symfony\Component\Process\Process;
use System\Classes\Asset\PackageManager;
use System\Classes\Asset\PackageJson;
use Winter\Storm\Console\Command;
use Winter\Storm\Support\Facades\File;
use Winter\Storm\Support\Str;
abstract class AssetCompile extends Command
{
/**
* @var string The console command description.
*/
protected $description = 'Mix and compile assets';
/**
* PackageJson object holding the contents of the active package.json
*/
protected PackageJson $packageJson;
/**
* Name of config file i.e. mix.webpack.js, vite.config.js
*/
protected string $configFile;
/**
* File path being watched, used for cleanup by mix:watch
*/
protected string $watchingFilePath;
public function compileHandle(string $type): int
{
// Exit early if node_modules isn't available yet
if (!File::exists(base_path('node_modules'))) {
$this->error(sprintf(
'The Node dependencies are not available, try running %s:install first.',
$type
));
return 1;
}
$compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$registeredPackages = $compilableAssets->getPackages($type);
$requestedPackages = $this->option('package') ?: [];
// Calling commands in unit tests can cause the option casting to not work correctly,
// ensure that the option value is always an array
if (is_string($requestedPackages)) {
$requestedPackages = [$requestedPackages];
}
// 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)) {
// Get an updated list of packages including any newly added packages
$registeredPackages = $compilableAssets->getPackages($type);
// 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 compilation.');
return 1;
} else {
$this->info('No packages registered for mixing.');
return 0;
}
}
$exits = [];
foreach ($registeredPackages as $name => $package) {
$relativeMixJsPath = $package['config'];
if (!$this->isPackageWithinWorkspace($relativeMixJsPath)) {
$this->error(sprintf(
'Unable to compile "%s", %s was not found in the package.json\'s workspaces.packages property.'
. ' Try running %s:install first.',
$name,
$relativeMixJsPath,
$type
));
continue;
}
if (!$this->option('silent')) {
$this->info(sprintf('Compiling package "%s"', $name));
}
$exitCode = $this->executeProcess(base_path($relativeMixJsPath));
if ($exitCode > 0) {
$this->error(sprintf('Unable to compile package "%s"', $name));
}
if ($this->option('stop-on-error') && $exitCode > 0) {
return $exitCode;
}
$exits[] = $exitCode;
}
return (int) !empty(array_filter($exits));
}
public function watchHandle(string $type): int
{
$compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$packages = $compilableAssets->getPackages($type);
$name = $this->argument('package');
$nameLower = strtolower($name);
if (!in_array($nameLower, array_keys($packages))) {
$this->error(
sprintf('Package "%s" is not a registered package.', $name)
);
return 1;
}
$package = $packages[$nameLower];
$relativeConfigPath = $package['config'];
if (!$this->isPackageWithinWorkspace($relativeConfigPath)) {
$this->error(sprintf(
'Unable to watch "%s", %s was not found in the package.json\'s workspaces.packages property. Try running %s:install first.',
$name,
$relativeConfigPath,
$type
));
return 1;
}
if (!$this->option('silent')) {
$this->info(sprintf('Watching package "%s" for changes', $name));
}
$this->watchingFilePath = $relativeConfigPath;
if ($this->executeProcess(base_path($relativeConfigPath)) !== 0) {
$this->error(sprintf('Unable to compile package "%s"', $name));
return 1;
}
return 0;
}
/**
* Get the package path for the provided winter.mix.js file
*/
protected function getPackagePath(string $path): string
{
return pathinfo($path, PATHINFO_DIRNAME);
}
/**
* Get the path to the mix.webpack.js file for the provided winter.mix.js file
*/
protected function getJsConfigPath(string $path): string
{
return $this->getPackagePath($path) . DIRECTORY_SEPARATOR . $this->configFile;
}
/**
* Check if Mix is able to compile the provided winter.mix.js file
*/
protected function isPackageWithinWorkspace(string $mixJsPath): bool
{
if (!isset($this->packageJson)) {
// Load the main package.json for the project
$this->packageJson = $this->getNpmPackageManifest();
}
return $this->packageJson->hasWorkspace(
Str::replace(DIRECTORY_SEPARATOR, '/', $this->getPackagePath($mixJsPath))
);
}
/**
* Read the package.json file for the project, path configurable with the
* `--manifest` option
*/
protected function getNpmPackageManifest(): PackageJson
{
return new PackageJson(base_path($this->option('manifest') ?? 'package.json'));
}
/**
* Run the mix command against the provided package
*/
protected function executeProcess(string $configPath): int
{
$this->beforeExecution($configPath);
$command = $this->createCommand($configPath);
$commandEnv = $this->createCommandEnv($configPath);
$process = new Process(
$command,
$this->getPackagePath($configPath),
[
'NODE_ENV' => $this->option('production', false) ? 'production' : 'development',
...$commandEnv
],
null,
null
);
if (!$this->option('disable-tty')) {
try {
$process->setTty(true);
} catch (\Throwable $e) {
// This will fail on unsupported systems
}
}
$exitCode = $process->run(function ($status, $stdout) {
if (!$this->option('silent')) {
$this->getOutput()->write($stdout);
}
});
$this->afterExecution($configPath);
return $exitCode;
}
/**
* Ran before dispatching the compile process, use for setting up
*/
protected function beforeExecution(string $configPath): void
{
// do nothing
}
/**
* Ran after dispatching the compile process, use for tearing down
*/
protected function afterExecution(string $configPath): void
{
// do nothing
}
/**
* Create the command array to create a Process object with
*/
abstract protected function createCommand(string $configPath): array;
/**
* Return values to append to the command env
*/
protected function createCommandEnv(string $configPath): array
{
return [];
}
}

View File

@@ -0,0 +1,263 @@
<?php
namespace System\Console\Asset;
use Closure;
use Cms\Classes\Theme;
use Symfony\Component\Console\Input\InputOption;
use System\Classes\Asset\BundleManager;
use System\Classes\Asset\PackageJson;
use System\Classes\Asset\PackageManager;
use System\Classes\PluginManager;
use Winter\Storm\Console\Command;
use Winter\Storm\Support\Facades\File;
abstract class AssetCreate extends Command
{
protected const TYPE_MODULE = 'module';
protected const TYPE_PLUGIN = 'plugin';
protected const TYPE_THEME = 'theme';
/**
* @var string The console command description.
*/
protected $description = 'Creates the compiler configuration files for the provided package and optionally installs the necessary dependencies for any selected asset bundles.';
/**
* Local cache of fixture path
*/
private string $fixturePath;
/**
* The type of compilable to configure
*/
protected string $assetType;
/**
* The name of the config file
*/
protected string $configFile;
/**
* Dynamically generate options for all available bundles
*/
public function __construct()
{
parent::__construct();
foreach (BundleManager::instance()->getBundles() as $bundle) {
$this->addOption($bundle, null, InputOption::VALUE_NONE, 'Create ' . $bundle . ' configuration');
}
}
/**
* Execute the console command.
*/
public function handle(): int
{
$package = $this->argument('packageName');
$this->fixturePath = __DIR__ . '/fixtures/config';
$compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$packages = $compilableAssets->getPackages($this->assetType, true);
if (
// We have the package already
isset($packages[$package])
// If the user has requested `force` & `no-interaction` then we do not ask for confirmation and continue
&& !($this->option('force') && $this->option('no-interaction'))
// If the user has forced but with interaction, then we do not ask for confirmation, else we do
&& !($this->option('force') || $this->confirm('Package `' . $package . '` has already been configured, are you sure you wish to continue?'))
) {
return 0;
}
[$path, $type] = $this->getPackagePathType($package);
if (is_null($path) || is_null($type)) {
$this->error('Package `' . $package . '` could not be resolved');
return 1;
}
$packageJson = new PackageJson($path . '/package.json');
if (!$packageJson->getName()) {
$packageJson->setName(strtolower(str_replace('.', '-', $package)));
}
$this->installConfigs($packageJson, $package, $type, $path);
$verb = File::exists($packageJson->getPath()) ? 'updated' : 'generated';
$packageJson->save();
if (!$this->option('silent')) {
$this->warn("File $verb: " . str_after($packageJson->getPath(), base_path()));
$this->info(ucfirst($this->assetType) . ' configuration complete.');
}
$this->afterExecution();
return 0;
}
/**
* Resolve the path and type of the package by name
*/
protected function getPackagePathType(string $package): array
{
if (str_starts_with($package, 'theme-')) {
if (($theme = Theme::load(str_after($package, 'theme-'))) && File::exists($theme->getPath())) {
return [$theme->getPath(), static::TYPE_THEME];
}
return [null, null];
}
if (str_starts_with($package, 'module-')) {
if (
($modulePath = base_path('modules') . '/' . str_after($package, 'module-'))
&& File::exists($modulePath)
) {
return [$modulePath, static::TYPE_MODULE];
}
return [null, null];
}
if ($plugin = PluginManager::instance()->findByIdentifier($package)) {
return [$plugin->getPluginPath(), static::TYPE_PLUGIN];
}
return [null, null];
}
/**
* Write out config files based on assetType and the requested options
*/
protected function installConfigs(
PackageJson $packageJson,
string $packageName,
string $packageType,
string $packagePath
): void {
// Normalize package name
$packageName = $this->makePackageName($packageName);
// Bind the bundleManager instance
$bundleManager = BundleManager::instance();
// Get the default config
$config = $this->getFixture(
$this->assetType . '/' . pathinfo($this->configFile, PATHINFO_BASENAME) . '.fixture'
);
// For each bundle offered by node packages
foreach ($bundleManager->getBundles() as $bundle) {
// If the bundle was not selected exit
if (!$this->option($bundle)) {
continue;
}
// Require all packages specified by the bundle
foreach ($bundleManager->getBundlePackages($bundle, $this->assetType) as $dependency => $version) {
$packageJson->addDependency($dependency, $version, dev: true);
}
// Fire any setup handlers required
$setupHandler = $bundleManager->getSetupHandler($bundle);
if ($setupHandler) {
Closure::bind($setupHandler, $this)->call($this, $packagePath, $packageType);
}
// Loop through all the scaffold handlers to build configs / stubs
$scaffoldHandler = $bundleManager->getScaffoldHandler($bundle);
if ($scaffoldHandler) {
// Generate the config
$config = Closure::bind($scaffoldHandler, $this)->call($this, $config, $this->assetType);
// Generate stub files if required
if (!$this->option('no-stubs')) {
$css = Closure::bind($scaffoldHandler, $this)->call($this, $css ?? '', 'css');
$js = Closure::bind($scaffoldHandler, $this)->call($this, $js ?? '', 'js');
}
}
}
// Create stub files if required
if (!$this->option('no-stubs')) {
foreach (['css', 'js'] as $asset) {
// Create asset dist dir so laravel-vite-plugin doesn't complain
if (!File::exists($packagePath . '/assets/dist')) {
File::makeDirectory($packagePath . '/assets/dist/', recursive: true);
}
// Create asset src dirs for stubs
if (!File::exists($packagePath . '/assets/src/' . $asset)) {
File::makeDirectory($packagePath . '/assets/src/' . $asset, recursive: true);
}
$this->writeFile(
sprintf('%1$s/assets/src/%2$s/%3$s.%2$s', $packagePath, $asset, $packageName),
$$asset ?? null ? $$asset : $this->getFixture(sprintf('%1$s/default.%1$s.fixture', $asset))
);
}
}
// Write out the config file
$this->writeFile($packagePath . '/' . $this->configFile, str_replace(
'{{packageName}}',
$packageName,
$config
));
}
/**
* Write a file but ask for conformation before overwriting
*/
protected function writeFile(string $path, string $content): int
{
if (
// If forced, ignore file existing and overwrite
(!$this->option('force') && File::exists($path))
&& (
// If no interaction requested, then skip the confirm check and return
$this->option('no-interaction')
// else ask the user if they want overwriting
|| !$this->confirm(sprintf('%s already exists, overwrite?', basename($path)))
)
) {
return 0;
}
File::ensureDirectoryExists(pathinfo($path, PATHINFO_DIRNAME));
$result = File::put($path, $content);
if (!$this->option('silent')) {
$this->warn('File generated: ' . str_after($path, base_path()));
}
return $result;
}
/**
* Helper method for loading fixtures from the default library
*/
protected function getFixture(string $path): string
{
return File::get($this->fixturePath . '/' . $path);
}
/**
* Converts the user supplied package name into a consistent internal format
*/
protected function makePackageName(string $package): string
{
return strtolower(str_replace('.', '-', $package));
}
/**
* Ran after configuration is complete, use for tearing down / reporting
*/
protected function afterExecution(): void
{
// do nothing
}
}

View File

@@ -0,0 +1,409 @@
<?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();
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace System\Console\Asset;
use System\Classes\Asset\PackageManager;
use Winter\Storm\Console\Command;
use Winter\Storm\Support\Facades\File;
abstract class AssetList extends Command
{
protected string $assetType;
public function handle(): int
{
$compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$packages = $compilableAssets->getPackages($this->assetType, true);
if (count($packages) === 0) {
$this->info('No packages have been registered.');
return 0;
}
$errors = [];
$rows = [];
foreach ($packages as $name => $package) {
$rows[] = [
'name' => $name,
'active' => !$package['ignored'],
'path' => $package['path'],
'configuration' => $package['config'],
];
if (!File::exists($package['config'])) {
$errors[] = "The config file for $name doesn't exist, try running artisan $this->assetType:install";
}
}
if ($this->option('json')) {
$this->line(json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
} else {
$this->line('');
$this->info('Packages registered:');
$this->line('');
$this->table(['Name', 'Active', 'Path', 'Configuration'], array_map(function ($row) {
$row['active'] = ($row['active']) ? '<info>Yes</info>' : '<fg=red>No</>';
return $row;
}, $rows));
$this->line('');
}
if (!empty($errors)) {
foreach ($errors as $error) {
$this->error($error);
}
}
return 0;
}
}

View File

@@ -0,0 +1,3 @@
/**
* Css here
*/

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1 @@
console.log('hello world!');

View File

@@ -0,0 +1,8 @@
import { createApp } from "vue";
// import Example from "./components/Example.vue";
//
// const app = createApp({
// components: {Example}
// });
//
// app.mount("#example");

View File

@@ -0,0 +1,4 @@
const mix = require('laravel-mix');
mix.setPublicPath(__dirname);
mix.js('assets/src/js/{{packageName}}.js', 'assets/dist/js/{{packageName}}.js');

View File

@@ -0,0 +1,7 @@
import React from "react";
const App = () => {
return <h1>Hello from React in Winter CMS!</h1>;
};
export default App;

View File

@@ -0,0 +1 @@
import('./{{packageName}}.jsx');

View File

@@ -0,0 +1,5 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./components/App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(<App />);

View File

@@ -0,0 +1,5 @@
export default {
plugins: {
tailwindcss: {},
},
};

View File

@@ -0,0 +1,15 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./assets/src/js/**/*.{js,jsx}',
'./blocks/**/*.block',
'./components/**/*.{htm,php}',
'./controllers/**/*.{htm,php}',
'./formwidgets/**/*.{htm,php}',
'./widgets/**/*.{htm,php}',
],
plugins: [forms],
};

View File

@@ -0,0 +1,15 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./assets/src/js/**/*.{js,jsx}',
'./blocks/**/*.block',
'./layouts/**/*.htm',
'./pages/**/*.htm',
'./partials/**/*.htm',
'./content/**/*.htm',
],
plugins: [forms],
};

View File

@@ -0,0 +1,30 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
const defaultOutDir = 'assets/dist';
export default defineConfig({
base: `${process.env.VITE_BASE}/${defaultOutDir}`,
build: {
outDir: defaultOutDir,
assetsDir: '',
},
plugins: [
laravel({
publicDirectory: defaultOutDir,
input: [
'assets/src/css/{{packageName}}.css',
'assets/src/js/{{packageName}}.js',
],
refresh: {
paths: [
'./**/*.htm',
'./**/*.block',
'assets/src/**/*.css',
'assets/src/**/*.js',
'assets/src/**/*.jsx',
]
},
}),
],
});

View File

@@ -0,0 +1,87 @@
const basePath = '%base%';
const { assertSupportedNodeVersion } = require(basePath + '/node_modules/laravel-mix/src/Engine');
module.exports = async () => {
assertSupportedNodeVersion();
const mix = require(basePath + '/node_modules/laravel-mix/src/Mix').primary;
mix.listen('init', function (_mix) {
// Setup Winter path aliases
_mix._api.alias({
'$': '%pluginsPath%',
'~': '%appPath%',
});
// disable notifications if not in watch
%notificationInject%
// define options
_mix._api.options({
processCssUrls: false,
clearConsole: false,
cssNano: {
discardComments: {removeAll: true},
}
});
// enable source maps for dev builds
if (!_mix._api.inProduction()) {
_mix._api.webpackConfig({
devtool: 'inline-source-map'
});
_mix._api.sourceMaps();
}
// Disable default manifest, allow for custom manifest
if (_mix.config.manifest === 'mix-manifest.json') {
mix.manifest.refresh = _ => void 0;
} else {
if (_mix.config.manifest === true) {
_mix.config.manifest = 'mix-manifest.json';
}
}
});
// override default mix output
mix.listen("loading-plugins", function (plugins) {
plugins.forEach(function (plugin, index) {
switch (plugin.constructor.name) {
case "BuildOutputPlugin":
plugins[index].apply = function (compiler) {
compiler.hooks.done.tap('BuildOutputPlugin', stats => {
if (stats.hasErrors()) {
return false;
}
if (this.options.clearConsole) {
this.clearConsole();
}
let data = stats.toJson({
assets: true,
builtAt: true,
hash: true,
performance: true,
relatedAssets: this.options.showRelated
});
if (data.assets.length && !%silent%) {
console.log(this.statsTable(data));
}
});
};
break;
case "WebpackBarPlugin":
if (%silent% || %noProgress%) {
plugins[index].apply = _ => void 0;
}
break;
}
});
});
require('%mixConfigPath%');
await mix.installDependencies();
await mix.init();
return mix.build();
};

View File

@@ -0,0 +1,4 @@
const mix = require('laravel-mix');
mix.setPublicPath(__dirname + '/assets');
// Your mix configuration below

View File

@@ -0,0 +1,103 @@
<?php
namespace System\Console\Asset\Mix;
use System\Console\Asset\AssetCompile;
use Winter\Storm\Support\Facades\File;
use Winter\Storm\Support\Str;
class MixCompile extends AssetCompile
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'mix:compile';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'mix:compile
{webpackArgs?* : Arguments to pass through to the Webpack CLI}
{--f|production : Runs compilation in "production" mode}
{--s|silent : Enables silent mode, no output will be shown.}
{--d|disable-tty : Disable tty mode}
{--e|stop-on-error : Exit once an error is encountered}
{--m|manifest= : Defines package.json to use for compile}
{--p|package=* : Defines one or more packages to compile}
{--no-progress : Do not show mix progress}';
/**
* @var string The console command description.
*/
protected $description = 'Mix and compile assets';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'mix:build'
];
/**
* Name of config file i.e. mix.webpack.js, vite.config.js
*/
protected string $configFile = 'mix.webpack.js';
/**
* Call the AssetCompile::compileHandle with the mix type
*/
public function handle(): int
{
return $this->compileHandle('mix');
}
/**
* Create the command array to create a Process object with
*/
protected function createCommand(string $configPath): array
{
$basePath = base_path();
$command = $this->argument('webpackArgs') ?? [];
array_unshift(
$command,
$basePath . sprintf('%1$snode_modules%1$s.bin%1$swebpack', DIRECTORY_SEPARATOR),
'build',
$this->option('silent') ? '--stats=none' : '--progress',
'--config=' . $this->getJsConfigPath($configPath)
);
return $command;
}
/**
* Create the temporary mix.webpack.js config file to run webpack with
*/
protected function beforeExecution(string $configPath): void
{
$basePath = base_path();
$fixture = File::get(__DIR__ . '/../fixtures/mix.webpack.js.fixture');
$config = Str::swap([
'%base%' => addslashes($basePath),
'%notificationInject%' => 'mix._api.disableNotifications();',
'%mixConfigPath%' => addslashes($configPath),
'%pluginsPath%' => addslashes(plugins_path()),
'%appPath%' => addslashes(base_path()),
'%silent%' => (int) $this->option('silent'),
'%noProgress%' => (int) $this->option('no-progress')
], $fixture);
File::put($this->getJsConfigPath($configPath), $config);
}
/**
* Remove the temporary mix.webpack.js file
*/
protected function afterExecution(string $configPath): void
{
$webpackConfigPath = $this->getJsConfigPath($configPath);
if (File::exists($webpackConfigPath) && File::isFile($webpackConfigPath)) {
File::delete($webpackConfigPath);
}
}
}

View File

@@ -0,0 +1,37 @@
<?php namespace System\Console\Asset\Mix;
use System\Console\Asset\AssetCreate;
class MixCreate extends AssetCreate
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'mix:create';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'mix:create
{packageName : The package name to add configuration for}
{--no-stubs : Disable stub file generation}
{--s|silent : Enables silent mode, no output will be shown.}
{--f|force : Force file overwrites}';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'mix:config',
];
/**
* The type of compilable to configure
*/
protected string $assetType = 'mix';
/**
* The name of the config file
*/
protected string $configFile = 'winter.mix.js';
}

View File

@@ -0,0 +1,44 @@
<?php namespace System\Console\Asset\Mix;
use System\Console\Asset\AssetInstall;
class MixInstall extends AssetInstall
{
/**
* @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
{assetPackage?* : The asset package name to install.}
{--no-install : Tells Winter not to run npm install after config update.}
{--npm= : Defines a custom path to the "npm" binary.}
{--d|disable-tty : Disable tty mode.}
{--s|silent : Enables silent mode, no output will be shown.}
{--p|package-json= : Defines a custom path to "package.json" file. Must be above the workspace path.}';
/**
* @var string The console command description.
*/
protected $description = 'Install Node.js dependencies required for mixed assets';
/**
* The asset compiler being used
*/
protected string $assetType = 'mix';
/**
* The asset config file
*/
protected string $configFile = 'winter.mix.js';
/**
* The required packages for this compiler
*/
protected array $requiredDependencies = [
'laravel-mix' => '^6.0.41',
];
}

View File

@@ -0,0 +1,29 @@
<?php
namespace System\Console\Asset\Mix;
use System\Console\Asset\AssetList;
class MixList extends AssetList
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'mix:list';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'mix:list
{--json : Output as JSON}';
/**
* @var string The console command description.
*/
protected $description = 'List all registered Mix packages in this project.';
/**
* The asset compiler being used
*/
protected string $assetType = 'mix';
}

View File

@@ -0,0 +1,66 @@
<?php
namespace System\Console\Asset\Mix;
class MixWatch extends MixCompile
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'mix:watch';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'mix:watch
{package : Defines the package to watch for changes}
{webpackArgs?* : Arguments to pass through to the Webpack CLI}
{--f|production : Runs compilation in "production" mode}
{--m|manifest= : Defines package.json to use for compile}
{--s|silent : Enables silent mode, no output will be shown.}
{--d|disable-tty : Disable tty mode}
{--no-progress : Do not show mix progress}';
/**
* @var string The console command description.
*/
protected $description = 'Mix and compile assets on-the-fly as changes are made.';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'mix:dev'
];
/**
* Call the AssetCompile::watchHandle with the mix type
*/
public function handle(): int
{
return $this->watchHandle('mix');
}
/**
* Create the command array to create a Process object with
*/
protected function createCommand(string $configPath): array
{
$command = parent::createCommand($configPath);
// @TODO: Detect Homestead running on Windows to switch to watch-poll-options instead, see https://laravel-mix.com/docs/6.0/cli#polling
$command[] = '--watch';
return $command;
}
/**
* Handle the cleanup of this command if a termination signal is received
*/
public function handleCleanup(): void
{
$this->newLine();
$this->info('Cleaning up: ' . $this->getPackagePath($this->watchingFilePath));
$this->afterExecution(base_path($this->watchingFilePath));
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace System\Console\Asset\Npm;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
use System\Classes\Asset\PackageJson;
use System\Classes\Asset\PackageManager;
use Winter\Storm\Console\Command;
use Winter\Storm\Exception\SystemException;
abstract class NpmCommand extends Command
{
/**
* Gets a package config and its PackageJson file based on the `package` argument.
* @throws SystemException
* @throws \JsonException
*/
protected function getPackage(): ?array
{
$compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$name = $this->argument('package');
if (!$name) {
return null;
}
if (!$compilableAssets->hasPackage($name, true)) {
throw new SystemException(sprintf('Package "%s" is not a registered package.', $name));
}
$package = $compilableAssets->getPackage($name, true)[0] ?? [];
// Assume that packages with matching names have matching package.json files
$packageJson = new PackageJson($package['package'] ?? null);
return [$package, $packageJson];
}
/**
* Starts a npm process with the command and cwd provided
*/
protected function npmRun(array $command, string $path): int
{
$process = new Process(
$command,
base_path($path),
['NODE_ENV' => $this->getNodeEnv()],
null,
null
);
if (!$this->option('disable-tty') && !$this->option('silent')) {
try {
$process->setTty(true);
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
return 1;
} catch (\Throwable $e) {
// This will fail on unsupported systems
}
}
return $process->run(function ($status, $stdout) {
if (!$this->option('silent')) {
$this->getOutput()->write($stdout);
}
});
}
/**
* Get the env env to provide to node
*/
protected function getNodeEnv(): string
{
if (!$this->hasOption('production')) {
return 'development';
}
return $this->option('production', false) ? 'production' : 'development';
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace System\Console\Asset\Npm;
use System\Console\Asset\Npm\NpmCommand;
use Winter\Storm\Exception\SystemException;
class NpmInstall extends NpmCommand
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'npm:install';
/**
* @inheritDoc
*/
protected $description = 'Install Node.js dependencies for a package';
/**
* @inheritDoc
*/
protected $signature = 'npm:install
{package? : The package name to add configuration for}
{npmArgs?* : Arguments to pass through to the "npm" binary}
{--npm= : Defines a custom path to the "npm" binary}
{--d|dev : Install packages in devDependencies}
{--s|silent : Silent mode.}
{--disable-tty : Disable tty mode}';
/**
* Execute the console command.
*/
public function handle(): int
{
$command = ($this->argument('npmArgs')) ?? [];
try {
[$package, $packageJson] = $this->getPackage();
} catch (SystemException $e) {
if (!str_contains($e->getMessage(), 'is not a registered package.')) {
throw $e;
}
array_unshift($command, $this->argument('package'));
}
array_unshift($command, 'npm', 'install');
if ($this->option('dev')) {
$command[] = '--save-dev';
}
return $this->npmRun($command, $package['path'] ?? '');
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace System\Console\Asset\Npm;
use System\Console\Asset\Npm\NpmCommand;
class NpmRun extends NpmCommand
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'npm:run';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'npm:run
{package : Defines the package where the script is located.}
{script : The name of the script to run, as defined in the package.json "scripts" config.}
{additionalArgs?* : Arguments to pass through to the script being run.}
{--f|production : Runs the script in "production" mode.}
{--s|silent : Silent mode.}
{--disable-tty : Disable tty mode}';
/**
* @var string The console command description.
*/
protected $description = 'Runs a script in a given package.';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'mix:run'
];
/**
* Execute the console command.
*/
public function handle(): int
{
[$package, $packageJson] = $this->getPackage();
$script = $this->argument('script');
if (!$packageJson->hasScript($script)) {
$this->error(
sprintf('Script "%s" is not defined in package "%s".', $script, $this->argument('package'))
);
return 1;
}
if (!$this->option('silent')) {
$this->info(sprintf('Running script "%s" in package "%s"', $script, $this->argument('package')));
}
$command = ($this->argument('additionalArgs')) ?? [];
if (count($command)) {
array_unshift($command, 'npm', 'run', $script, '--');
} else {
array_unshift($command, 'npm', 'run', $script);
}
return $this->npmRun($command, $package['path']);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace System\Console\Asset\Npm;
use System\Console\Asset\Npm\NpmCommand;
use Winter\Storm\Exception\SystemException;
class NpmUpdate extends NpmCommand
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'npm:update';
/**
* @inheritDoc
*/
protected $description = 'Update Node.js dependencies required for mixed assets';
/**
* @inheritDoc
*/
protected $signature = 'npm:update
{package? : The package name to add configuration for.}
{npmArgs?* : Arguments to pass through to the "npm" binary.}
{--npm= : Defines a custom path to the "npm" binary.}
{--a|save : Tell npm to update package.json.}
{--s|silent : Silent mode.}
{--disable-tty : Disable tty mode}';
/**
* @inheritDoc
*/
public $replaces = [
'mix:update'
];
/**
* Execute the console command.
*/
public function handle(): int
{
$command = ($this->argument('npmArgs')) ?? [];
try {
[$package, $packageJson] = $this->getPackage();
} catch (SystemException $e) {
if (!str_contains($e->getMessage(), 'is not a registered package.')) {
throw $e;
}
array_unshift($command, $this->argument('package'));
}
$args = ['npm', 'update'];
if ($this->option('save')) {
$args[] = '--save';
}
if (count($command)) {
$args[] = '--';
}
array_unshift($command, ...$args);
return $this->npmRun($command, $package['path'] ?? '');
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace System\Console\Asset\Npm;
use Symfony\Component\Process\Process;
use System\Console\Asset\Npm\NpmCommand;
class NpmVersion extends NpmCommand
{
const NPM_MINIMUM_SUPPORTED_VERSION = '7.0';
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'npm:version';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'npm:version
{--c|compatible : Report compatible version via exit code.}
{--s|silent : Silent mode.}
{--disable-tty : Disable tty mode}';
/**
* @var string The console command description.
*/
protected $description = 'Runs a script in a given package.';
/**
* Execute the console command.
*/
public function handle(): int
{
$process = new Process(
['npm', '--version'],
base_path(),
['NODE_ENV' => $this->getNodeEnv()],
null,
null
);
$output = '';
$exit = $process->run(function ($status, $stdout) use (&$output) {
$output .= $stdout;
});
$output = trim($output);
// Npm failed for some reason, report to user
if ($exit !== 0) {
$this->error('NPM exited with error: ' . $output);
return $exit;
}
// Report the version to user
if (!$this->option('silent')) {
$this->info($output);
}
// If the user has not requested a compatibility check, then return 0
if (!$this->option('compatible')) {
return 0;
}
// If the version of npm is less than the required minimum, then return fail
if (version_compare($output, static::NPM_MINIMUM_SUPPORTED_VERSION, '<')) {
return 1;
}
return 0;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace System\Console\Asset\Vite;
use System\Console\Asset\AssetCompile;
use Winter\Storm\Support\Str;
class ViteCompile extends AssetCompile
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'vite:compile';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'vite:compile
{viteArgs?* : Arguments to pass through to the Vite CLI}
{--f|production : Runs compilation in "production" mode}
{--s|silent : Enables silent mode, no output will be shown.}
{--d|disable-tty : Disable tty mode}
{--e|stop-on-error : Exit once an error is encountered}
{--m|manifest= : Defines package.json to use for compile}
{--p|package=* : Defines one or more packages to compile}
';
/**
* @var string The console command description.
*/
protected $description = 'Vite and compile assets';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'vite:build'
];
/**
* Name of config file i.e. mix.webpack.js, vite.config.js
*/
protected string $configFile = 'vite.config.mjs';
/**
* Call the AssetCompile::compileHandle with the vite type
*/
public function handle(): int
{
return $this->compileHandle('vite');
}
/**
* Create the command array to create a Process object with
*/
protected function createCommand(string $configPath): array
{
$basePath = base_path();
$command = $this->argument('viteArgs') ?? [];
array_unshift(
$command,
$basePath . sprintf('%1$snode_modules%1$s.bin%1$svite', DIRECTORY_SEPARATOR),
'build',
$this->option('silent') ? '--logLevel=silent' : '',
);
return $command;
}
/**
* Return values to append to the command env
*/
protected function createCommandEnv(string $configPath): array
{
return [
'VITE_BASE' => Str::after($this->getPackagePath($configPath), base_path()),
];
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace System\Console\Asset\Vite;
use System\Console\Asset\AssetCreate;
class ViteCreate extends AssetCreate
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'vite:create';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'vite:create
{packageName : The package name to add configuration for}
{--no-stubs : Disable stub file generation}
{--s|silent : Enables silent mode, no output will be shown.}
{--f|force : Force file overwrites}';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'vite:config',
];
/**
* The type of compilable to configure
*/
protected string $assetType = 'vite';
/**
* The name of the config file
*/
protected string $configFile = 'vite.config.mjs';
/**
* Output a helpful message with the twig code to set up vite after config generation is complete
*/
public function afterExecution(): void
{
if ($this->option('silent')) {
return;
}
$packageName = $this->makePackageName($this->argument('packageName'));
$this->output->writeln('');
$this->info('Add the following to your twig to enable asset loading:');
if ($this->option('react')) {
$this->output->writeln(sprintf(
'<fg=blue>{{ viteReactRefresh(\'%1$s\') }}</>',
strtolower($this->argument('packageName'))
));
}
$this->output->writeln(sprintf(
'<fg=blue>{{ vite([\'assets/src/css/%1$s.css\', \'assets/src/js/%1$s.js\'], \'%2$s\') }}</>',
$packageName,
strtolower($this->argument('packageName'))
));
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace System\Console\Asset\Vite;
use System\Console\Asset\AssetInstall;
class ViteInstall extends AssetInstall
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'vite:install';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'vite:install
{assetPackage?* : The asset package name to install.}
{--no-install : Tells Winter not to run npm install after config update.}
{--npm= : Defines a custom path to the "npm" binary.}
{--d|disable-tty : Disable tty mode.}
{--s|silent : Enables silent mode, no output will be shown.}
{--p|package-json= : Defines a custom path to "package.json" file. Must be above the workspace path.}';
/**
* @var string The console command description.
*/
protected $description = 'Install Node.js dependencies required for vite assets';
/**
* The type of compilable to configure
*/
protected string $assetType = 'vite';
/**
* The asset config file
*/
protected string $configFile = 'vite.config.mjs';
/**
* The required packages for this compiler
*/
protected array $requiredDependencies = [
'vite' => '^6.0.0',
'laravel-vite-plugin' => '^1.1.0',
];
}

View File

@@ -0,0 +1,29 @@
<?php
namespace System\Console\Asset\Vite;
use System\Console\Asset\AssetList;
class ViteList extends AssetList
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'vite:list';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'vite:list
{--json : Output as JSON}';
/**
* @var string The console command description.
*/
protected $description = 'List all registered Vite packages in this project.';
/**
* The asset compiler being used
*/
protected string $assetType = 'vite';
}

View File

@@ -0,0 +1,72 @@
<?php
namespace System\Console\Asset\Vite;
use Winter\Storm\Support\Facades\File;
class ViteWatch extends ViteCompile
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'vite:watch';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'vite:watch
{package : The package to watch for changes (ex. author.plugin, theme-code, etc)}
{viteArgs?* : Arguments to pass through to the Webpack CLI}
{--f|production : Runs compilation in "production" mode}
{--m|manifest= : Defines package.json to use for compile}
{--s|silent : Enables silent mode, no output will be shown.}
{--d|disable-tty : Disable tty mode}
{--no-progress : Do not show mix progress}';
/**
* @var string The console command description.
*/
protected $description = 'Vite and compile assets on-the-fly as changes are made.';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'vite:dev'
];
/**
* Call the AssetCompile::watchHandle with the vite type
*/
public function handle(): int
{
return $this->watchHandle('vite');
}
/**
* Create the command array to create a Process object with
*/
protected function createCommand(string $configPath): array
{
$command = parent::createCommand($configPath);
$key = array_search('build', $command);
unset($command[$key]);
$command[] = '--host';
return array_values($command);
}
/**
* Handle the cleanup of this command if a termination signal is received
*/
public function handleCleanup(): void
{
$this->newLine();
$this->info('Running compile to ensure files exist after exit');
$this->call('vite:compile', [
'--package' => $this->argument('package'),
]);
}
}