feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
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:
297
modules/system/classes/asset/BundleManager.php
Normal file
297
modules/system/classes/asset/BundleManager.php
Normal file
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
namespace System\Classes\Asset;
|
||||
|
||||
use Closure;
|
||||
use Winter\Storm\Support\Traits\Singleton;
|
||||
|
||||
/**
|
||||
* Asset Bundle manager.
|
||||
*
|
||||
* This class manages "asset bundles" registered by the core and plugins that are used by the
|
||||
* [mix|vite]:create commands to generate & populate the required files for a given bundle.
|
||||
* Bundles include information on the specific packages & versions required for the bundle
|
||||
* to function in the context of the Winter package (plugin or theme) it is being used in,
|
||||
* as well as dependencies specific to the desired compiler (e.g. mix or vite).
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Jack Wilkinson <me@jackwilky.com>
|
||||
* @copyright Winter CMS Maintainers
|
||||
*/
|
||||
class BundleManager
|
||||
{
|
||||
use Singleton;
|
||||
|
||||
protected const HANDLER_SETUP = '_setup';
|
||||
protected const HANDLER_SCAFFOLD = '_scaffold';
|
||||
|
||||
/**
|
||||
* List of packages available to install. Allows for `$compilerName` => [`CompilerSpecificPackage`]
|
||||
*/
|
||||
protected array $defaultPackages = [
|
||||
'tailwind' => [
|
||||
'tailwindcss' => '^3.4.0',
|
||||
'@tailwindcss/forms' => '^0.5.3',
|
||||
'@tailwindcss/typography' => '^0.5.2',
|
||||
],
|
||||
'vue' => [
|
||||
'vue' => '^3.4.0',
|
||||
'vite' => [
|
||||
'@vitejs/plugin-vue' => '^5.0.5'
|
||||
],
|
||||
],
|
||||
'react' => [
|
||||
'react' => '^19.0.0',
|
||||
'react-dom' => '^19.0.0',
|
||||
'vite' => [
|
||||
'@vitejs/plugin-react' => '^4.3.4',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* List of registered asset bundles in the system
|
||||
*/
|
||||
protected array $registeredBundles = [];
|
||||
|
||||
/**
|
||||
* Initialize the singleton
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
// Register the default bundles
|
||||
$this->registerCallback(function (self $manager) {
|
||||
$manager->registerBundles($this->defaultPackages);
|
||||
|
||||
$manager->registerSetupHandler('tailwind', function (string $packagePath, string $packageType) {
|
||||
$this->writeFile(
|
||||
$packagePath . '/tailwind.config.js',
|
||||
$this->getFixture('tailwind/tailwind.' . $packageType . '.config.js.fixture')
|
||||
);
|
||||
|
||||
$this->writeFile(
|
||||
$packagePath . '/postcss.config.mjs',
|
||||
$this->getFixture('tailwind/postcss.config.js.fixture')
|
||||
);
|
||||
});
|
||||
|
||||
$manager->registerSetupHandler('react', function (string $packagePath, string $packageType) use ($manager) {
|
||||
if ($this->option('no-stubs')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->writeFile(
|
||||
$packagePath . '/assets/src/js/components/App.jsx',
|
||||
$this->getFixture('react/App.jsx.fixture')
|
||||
);
|
||||
|
||||
$this->writeFile(
|
||||
$packagePath . '/assets/src/js/' . strtolower($this->argument('packageName')) . '.jsx',
|
||||
$this->getFixture('react/package.jsx.fixture')
|
||||
);
|
||||
});
|
||||
|
||||
$manager->registerScaffoldHandler('tailwind', function (string $contents, string $contentType) {
|
||||
return match ($contentType) {
|
||||
'mix' => $contents . PHP_EOL . <<<JAVASCRIPT
|
||||
mix.postCss('assets/src/css/{{packageName}}.css', 'assets/dist/css/{{packageName}}.css', [
|
||||
require('postcss-import'),
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
]);
|
||||
JAVASCRIPT,
|
||||
'css' => $this->getFixture('css/tailwind.css.fixture'),
|
||||
default => $contents
|
||||
};
|
||||
});
|
||||
|
||||
$manager->registerScaffoldHandler('vue', function (string $contents, string $contentType) {
|
||||
return match ($contentType) {
|
||||
'vite' => str_replace(
|
||||
'}),',
|
||||
<<<JAVASCRIPT
|
||||
}),
|
||||
vue({
|
||||
template: {
|
||||
transformAssetUrls: {
|
||||
// The Vue plugin will re-write asset URLs, when referenced
|
||||
// in Single File Components, to point to the Laravel web
|
||||
// server. Setting this to `null` allows the Laravel plugin
|
||||
// to instead re-write asset URLs to point to the Vite
|
||||
// server instead.
|
||||
base: null,
|
||||
|
||||
// The Vue plugin will parse absolute URLs and treat them
|
||||
// as absolute paths to files on disk. Setting this to
|
||||
// `false` will leave absolute URLs un-touched so they can
|
||||
// reference assets in the public directory as expected.
|
||||
includeAbsolute: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
JAVASCRIPT,
|
||||
str_replace(
|
||||
'import laravel from \'laravel-vite-plugin\';',
|
||||
'import laravel from \'laravel-vite-plugin\';' . PHP_EOL . 'import vue from \'@vitejs/plugin-vue\';',
|
||||
$contents
|
||||
)
|
||||
),
|
||||
'mix' => str_replace(
|
||||
'mix.js(\'assets/src/js/{{packageName}}.js\', \'assets/dist/js/{{packageName}}.js\');',
|
||||
'mix.js(\'assets/src/js/{{packageName}}.js\', \'assets/dist/js/{{packageName}}.js\').vue({ version: 3 });',
|
||||
$contents
|
||||
),
|
||||
'js' => $this->getFixture('js/vue.js.fixture'),
|
||||
default => $contents
|
||||
};
|
||||
});
|
||||
|
||||
$manager->registerScaffoldHandler('react', function (string $contents, string $contentType) {
|
||||
return match ($contentType) {
|
||||
'vite' => str_replace(
|
||||
'}),',
|
||||
<<<JAVASCRIPT
|
||||
}),
|
||||
react(),
|
||||
JAVASCRIPT,
|
||||
str_replace(
|
||||
'import laravel from \'laravel-vite-plugin\';',
|
||||
'import laravel from \'laravel-vite-plugin\';' . PHP_EOL . 'import react from \'@vitejs/plugin-react\';',
|
||||
$contents
|
||||
)
|
||||
),
|
||||
'mix' => str_replace(
|
||||
'mix.js(\'assets/src/js/{{packageName}}.js\', \'assets/dist/js/{{packageName}}.js\');',
|
||||
'mix.js(\'assets/src/js/{{packageName}}.js\', \'assets/dist/js/{{packageName}}.js\').react();',
|
||||
$contents
|
||||
),
|
||||
'js' => str_replace(
|
||||
'{{packageName}}',
|
||||
strtolower($this->argument('packageName')),
|
||||
$this->getFixture('react/package.js.fixture')
|
||||
),
|
||||
default => $contents
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the registered asset bundles.
|
||||
*/
|
||||
public function listRegisteredBundles(): array
|
||||
{
|
||||
return $this->registeredBundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all bundles configured
|
||||
*/
|
||||
public function getBundles(): array
|
||||
{
|
||||
return array_keys($this->listRegisteredBundles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the packages for a bundle, with compiler specific packages
|
||||
*/
|
||||
public function getBundlePackages(string $name, string $assetType): array
|
||||
{
|
||||
$config = $this->listRegisteredBundles()[$name] ?? [];
|
||||
|
||||
$packages = [];
|
||||
foreach ($config as $key => $value) {
|
||||
// Skip handlers
|
||||
if (in_array($key, [static::HANDLER_SETUP, static::HANDLER_SCAFFOLD])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge in any compiler specific packages for the current compiler
|
||||
if (is_array($value)) {
|
||||
if ($key === $assetType) {
|
||||
$packages = array_merge($packages, $value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$packages[$key] = $value;
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback function that defines asset bundles. The callback function
|
||||
* should register bundles by calling the manager's registerBundles() function.
|
||||
* This instance is passed to the callback function as an argument. Usage:
|
||||
*
|
||||
* BundleManager::registerCallback(function ($manager) {
|
||||
* $manager->registerAssetBundles([...]);
|
||||
* });
|
||||
*
|
||||
*/
|
||||
public function registerCallback(callable $callback): static
|
||||
{
|
||||
$callback($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers asset bundles.
|
||||
*/
|
||||
public function registerBundles(array $definitions): static
|
||||
{
|
||||
foreach ($definitions as $name => $definition) {
|
||||
$this->registerBundle($name, $definition);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single asset bundle.
|
||||
*/
|
||||
public function registerBundle(string $name, array $definition): static
|
||||
{
|
||||
$this->registeredBundles[$name] = $definition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single bundle setup handler.
|
||||
*/
|
||||
public function registerSetupHandler(string $name, Closure $closure): static
|
||||
{
|
||||
$this->registeredBundles[$name][static::HANDLER_SETUP] = $closure;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single bundle scaffold handler.
|
||||
*/
|
||||
public function registerScaffoldHandler(string $name, Closure $closure): static
|
||||
{
|
||||
$this->registeredBundles[$name][static::HANDLER_SCAFFOLD] = $closure;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the setup handler for a bundle.
|
||||
*/
|
||||
public function getSetupHandler(string $name): ?Closure
|
||||
{
|
||||
return $this->listRegisteredBundles()[$name][static::HANDLER_SETUP] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the scaffold handler for a bundle.
|
||||
*/
|
||||
public function getScaffoldHandler(string $name): ?Closure
|
||||
{
|
||||
return $this->listRegisteredBundles()[$name][static::HANDLER_SCAFFOLD] ?? null;
|
||||
}
|
||||
}
|
||||
278
modules/system/classes/asset/PackageJson.php
Normal file
278
modules/system/classes/asset/PackageJson.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
namespace System\Classes\Asset;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Winter\Storm\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* PHP based interface for interacting with package.json files. This allows for modification of deps, devDeps, package
|
||||
* name and workspaces.
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Jack Wilkinson <me@jackwilky.com>
|
||||
* @author Winter CMS
|
||||
*/
|
||||
class PackageJson
|
||||
{
|
||||
/**
|
||||
* The contents of the package.json being modified
|
||||
*/
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* Create a new instance with optional path, loads file if file already exists
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function __construct(
|
||||
protected ?string $path = null
|
||||
) {
|
||||
if (File::exists($this->path)) {
|
||||
// Test the json to insure it's valid
|
||||
$json = json_decode(File::get($this->path), JSON_OBJECT_AS_ARRAY);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \JsonException('The contents of the file "' . $this->path . '" is not valid json.');
|
||||
}
|
||||
|
||||
$this->data = $json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name if set
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->data['name'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the package name, throws `InvalidArgumentException` on invalid name
|
||||
*/
|
||||
public function setName(?string $name): static
|
||||
{
|
||||
if (is_null($name)) {
|
||||
unset($this->data['name']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($name !== strtolower($name)) {
|
||||
throw new InvalidArgumentException('Package names must be lower case');
|
||||
}
|
||||
|
||||
if (preg_match('/^([._])/', $name)) {
|
||||
throw new InvalidArgumentException('Package names must not start with . or _');
|
||||
}
|
||||
|
||||
if (preg_match('/[~\'\"!()*]/', $name)) {
|
||||
throw new InvalidArgumentException('Package names must not include special characters');
|
||||
}
|
||||
|
||||
if (strlen($name) > 214) {
|
||||
throw new InvalidArgumentException('Package names must not be longer than 214 characters');
|
||||
}
|
||||
|
||||
if ($name !== trim($name)) {
|
||||
throw new InvalidArgumentException('Package names must not include whitespace');
|
||||
}
|
||||
|
||||
$this->data['name'] = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if workspace package is set
|
||||
*/
|
||||
public function hasWorkspace(string $path): bool
|
||||
{
|
||||
return in_array($path, $this->data['workspaces']['packages'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new workspace, removes from ignored workspaces if present
|
||||
*/
|
||||
public function addWorkspace(string $path): static
|
||||
{
|
||||
if (!in_array($path, $this->data['workspaces']['packages'] ?? [])) {
|
||||
$this->data['workspaces']['packages'][] = $path;
|
||||
}
|
||||
|
||||
if (($key = array_search($path, $this->data['workspaces']['ignoredPackages'] ?? [])) !== false) {
|
||||
// remove the package from ignored workspaces
|
||||
unset($this->data['workspaces']['ignoredPackages'][$key]);
|
||||
// reset keys
|
||||
$this->data['workspaces']['ignoredPackages'] = array_values($this->data['workspaces']['ignoredPackages']);
|
||||
}
|
||||
|
||||
// Sort the packages
|
||||
asort($this->data['workspaces']['packages']);
|
||||
$this->data['workspaces']['packages'] = array_values($this->data['workspaces']['packages']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a workspace
|
||||
*/
|
||||
public function removeWorkspace(string $path): static
|
||||
{
|
||||
if (($key = array_search($path, $this->data['workspaces']['packages'] ?? [])) !== false) {
|
||||
// remove the package from workspace packages
|
||||
unset($this->data['workspaces']['packages'][$key]);
|
||||
// reset keys
|
||||
$this->data['workspaces']['packages'] = array_values($this->data['workspaces']['packages']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if package is ignored
|
||||
*/
|
||||
public function hasIgnoredPackage(string $path): bool
|
||||
{
|
||||
return in_array($path, $this->data['workspaces']['ignoredPackages'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ignored package, removes from workspaces if present
|
||||
*/
|
||||
public function addIgnoredPackage(string $path): static
|
||||
{
|
||||
if (!in_array($path, $this->data['workspaces']['ignoredPackages'] ?? [])) {
|
||||
$this->data['workspaces']['ignoredPackages'][] = $path;
|
||||
}
|
||||
|
||||
if (($key = array_search($path, $this->data['workspaces']['packages'] ?? [])) !== false) {
|
||||
// remove the package from ignored workspaces
|
||||
unset($this->data['workspaces']['packages'][$key]);
|
||||
// reset keys
|
||||
$this->data['workspaces']['packages'] = array_values($this->data['workspaces']['packages']);
|
||||
}
|
||||
|
||||
// Sort the packages
|
||||
asort($this->data['workspaces']['ignoredPackages']);
|
||||
$this->data['workspaces']['ignoredPackages'] = array_values($this->data['workspaces']['ignoredPackages'] ?? []);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an ignored package
|
||||
*/
|
||||
public function removeIgnoredPackage(string $path): static
|
||||
{
|
||||
if (($key = array_search($path, $this->data['workspaces']['ignoredPackages'] ?? [])) !== false) {
|
||||
// remove the package from workspace packages
|
||||
unset($this->data['workspaces']['ignoredPackages'][$key]);
|
||||
// reset keys
|
||||
$this->data['workspaces']['ignoredPackages'] = array_values($this->data['workspaces']['ignoredPackages']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if package.json has a dependency
|
||||
*/
|
||||
public function hasDependency(string $package): bool
|
||||
{
|
||||
return isset($this->data['dependencies'][$package]) || isset($this->data['devDependencies'][$package]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dependency, supports adding to `dependencies` or `devDependencies` based on `$dev` and allows moving if
|
||||
* `$overwrite` is set
|
||||
*/
|
||||
public function addDependency(string $package, string $version, bool $dev = false, bool $overwrite = false): static
|
||||
{
|
||||
// If the dep is defined already, but we are not overwriting, then exit
|
||||
if (
|
||||
(isset($this->data['dependencies'][$package]) || isset($this->data['devDependencies'][$package]))
|
||||
&& !$overwrite
|
||||
) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Clear any existing settings because we are overwriting
|
||||
$this->removeDependency($package);
|
||||
|
||||
// Define the dep
|
||||
$this->data[$dev ? 'devDependencies' : 'dependencies'][$package] = $version;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a package from both `dependencies` and `devDependencies`
|
||||
*/
|
||||
public function removeDependency(string $package): static
|
||||
{
|
||||
unset($this->data['dependencies'][$package], $this->data['devDependencies'][$package]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a script exists
|
||||
*/
|
||||
public function hasScript(string $name): bool
|
||||
{
|
||||
return isset($this->data['scripts'][$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a script by name
|
||||
*/
|
||||
public function getScript(string $name): ?string
|
||||
{
|
||||
return $this->data['scripts'][$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a script
|
||||
*/
|
||||
public function addScript(string $name, string $script): static
|
||||
{
|
||||
$this->data['scripts'][$name] = $script;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a script by name
|
||||
*/
|
||||
public function removeScript(string $name): static
|
||||
{
|
||||
unset($this->data['scripts'][$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package.json contents as an array
|
||||
*/
|
||||
public function getContents(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the package.json if set
|
||||
*/
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the contents to a file, if the object was init'ed with a path it will save to the path, or can be
|
||||
* overwritten with `$path`.
|
||||
*/
|
||||
public function save(?string $path = null): int
|
||||
{
|
||||
return File::put(
|
||||
$path ?? $this->path ?? throw new RuntimeException('Unable to save, no path given'),
|
||||
json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
}
|
||||
414
modules/system/classes/asset/PackageManager.php
Normal file
414
modules/system/classes/asset/PackageManager.php
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace System\Classes\Asset;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use InvalidArgumentException;
|
||||
use System\Classes\PluginManager;
|
||||
use Winter\Storm\Exception\SystemException;
|
||||
use Winter\Storm\Filesystem\PathResolver;
|
||||
use Winter\Storm\Support\Facades\Config;
|
||||
use Winter\Storm\Support\Facades\File;
|
||||
use Winter\Storm\Support\Str;
|
||||
|
||||
/**
|
||||
* Package manager.
|
||||
*
|
||||
* This class manages compilable asset "packages" registered by modules, plugins, and themes that
|
||||
* provide configurations for Node.js based compilers (e.g. mix or vite) to process.
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Jack Wilkinson <me@jackwilky.com>
|
||||
* @copyright Winter CMS Maintainers
|
||||
*/
|
||||
class PackageManager
|
||||
{
|
||||
use \Winter\Storm\Support\Traits\Singleton;
|
||||
|
||||
public const TYPE_THEME = 'theme';
|
||||
public const TYPE_MODULE = 'module';
|
||||
public const TYPE_PLUGIN = 'plugin';
|
||||
|
||||
/**
|
||||
* The filename that stores the package definition.
|
||||
*/
|
||||
protected PackageJson $packageJson;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>> List of package types and registration methods
|
||||
*/
|
||||
protected array $compilableConfigs = [
|
||||
'mix' => [
|
||||
'configFile' => 'winter.mix.js'
|
||||
],
|
||||
'vite' => [
|
||||
'configFile' => 'vite.config.mjs'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of packages registered for compiling.
|
||||
*/
|
||||
protected array $packages = [];
|
||||
|
||||
/**
|
||||
* Registered callbacks.
|
||||
*/
|
||||
protected static array $callbacks = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
$this->setPackageJsonPath(base_path('package.json'));
|
||||
|
||||
$packagePaths = [];
|
||||
|
||||
/*
|
||||
* Get packages registered in plugins.
|
||||
*
|
||||
* In the Plugin.php file for your plugin, you can define the `registerMixPackages` or `registerVitePackages`
|
||||
* method and return an array, with the name of the package being the key, and the build config path - relative
|
||||
* to the plugin directory - as the value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* public function registerMixPackages(): array
|
||||
* {
|
||||
* return [
|
||||
* 'package-name-1' => 'winter.mix.js',
|
||||
* 'package-name-2' => 'assets/js/build.js',
|
||||
* ];
|
||||
* }
|
||||
*
|
||||
* public function registerVitePackages(): array
|
||||
* {
|
||||
* return [
|
||||
* 'package-name-1' => 'vite.config.mjs',
|
||||
* 'package-name-2' => 'assets/js/build.js',
|
||||
* ];
|
||||
* }
|
||||
*/
|
||||
foreach ($this->compilableConfigs as $type => $config) {
|
||||
$packages = PluginManager::instance()->getRegistrationMethodValues(
|
||||
$this->getRegistrationMethod($type)
|
||||
);
|
||||
if (count($packages)) {
|
||||
foreach ($packages as $pluginCode => $packageArray) {
|
||||
if (!is_array($packageArray)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($packageArray as $name => $package) {
|
||||
$this->registerPackage(
|
||||
$name,
|
||||
PluginManager::instance()->getPluginPath($pluginCode) . '/' . $package,
|
||||
$type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the currently enabled modules
|
||||
$enabledModules = Config::get('cms.loadModules', []);
|
||||
|
||||
if (in_array('Cms', $enabledModules)) {
|
||||
// Allow current theme to define mix assets
|
||||
$theme = Theme::getActiveTheme();
|
||||
|
||||
if (!is_null($theme)) {
|
||||
$mix = $theme->getConfigValue($type, []);
|
||||
|
||||
if (count($mix)) {
|
||||
foreach ($mix as $name => $file) {
|
||||
$this->registerPackage($name, $theme->getPath() . '/' . $file, $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search modules for compilable packages to autoregister
|
||||
foreach ($enabledModules as $module) {
|
||||
$module = strtolower($module);
|
||||
$path = base_path('modules' . DIRECTORY_SEPARATOR . $module) . DIRECTORY_SEPARATOR . $config['configFile'];
|
||||
if (File::exists($path)) {
|
||||
$packagePaths[$type]["module-$module"] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// Search plugins for compilable packages to autoregister
|
||||
$plugins = PluginManager::instance()->getPlugins();
|
||||
foreach ($plugins as $plugin) {
|
||||
$path = $plugin->getPluginPath() . '/' . $config['configFile'];
|
||||
if (File::exists($path)) {
|
||||
$packagePaths[$type][$plugin->getPluginIdentifier()] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// Search themes for compilable packages to autoregister
|
||||
if (in_array('Cms', $enabledModules)) {
|
||||
$themes = Theme::all();
|
||||
foreach ($themes as $theme) {
|
||||
$path = $theme->getPath() . '/' . $config['configFile'];
|
||||
if (File::exists($path)) {
|
||||
$packagePaths[$type]["theme-" . $theme->getId()] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the autodiscovered compilable packages
|
||||
foreach ($packagePaths as $type => $packages) {
|
||||
foreach ($packages as $package => $path) {
|
||||
try {
|
||||
$this->registerPackage($package, $path, $type);
|
||||
} catch (SystemException $e) {
|
||||
// Either the package name or the config file path have already been registered, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a compilable config.
|
||||
*/
|
||||
public function registerCompilable(string $name, array $config): void
|
||||
{
|
||||
$this->compilableConfigs[$name] = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback for processing.
|
||||
*/
|
||||
public static function registerCallback(callable $callback): void
|
||||
{
|
||||
static::$callbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the deferred callbacks.
|
||||
*/
|
||||
public function fireCallbacks(): static
|
||||
{
|
||||
// Call callbacks
|
||||
foreach (static::$callbacks as $callback) {
|
||||
$callback($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of packages registered.
|
||||
*/
|
||||
public function getPackageCount(): int
|
||||
{
|
||||
return array_sum(array_map(fn ($packages) => count($packages), ...$this->packages));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all packages registered.
|
||||
*/
|
||||
public function getPackages(string $type, bool $includeIgnored = false): array
|
||||
{
|
||||
$packages = $this->packages[$type] ?? [];
|
||||
|
||||
foreach ($packages as $index => $package) {
|
||||
$packages[$index]['ignored'] = $this->isPackageIgnored($package['path']);
|
||||
}
|
||||
|
||||
ksort($packages);
|
||||
|
||||
if (!$includeIgnored) {
|
||||
return array_filter($packages, function ($package) {
|
||||
return !($package['ignored'] ?? false);
|
||||
});
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if package(s) is registered.
|
||||
*/
|
||||
public function hasPackage(string $name, bool $includeIgnored = false): bool
|
||||
{
|
||||
foreach ($this->packages ?? [] as $packages) {
|
||||
foreach ($packages as $packageName => $package) {
|
||||
if ($name === $packageName) {
|
||||
if ((!$this->isPackageIgnored($package['path']) || $includeIgnored)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns package(s).
|
||||
*/
|
||||
public function getPackage(string $name, bool $includeIgnored = false): array
|
||||
{
|
||||
$results = [];
|
||||
foreach ($this->packages ?? [] as $type => $packages) {
|
||||
foreach ($packages as $packageName => $package) {
|
||||
if (($name === $packageName)) {
|
||||
if (!$this->isPackageIgnored($package['path']) || $includeIgnored) {
|
||||
$results[] = $package + ['type' => $type];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an entity as a package for compilation.
|
||||
*
|
||||
* Entities can include plugins, components, themes, modules and much more.
|
||||
*
|
||||
* The name of the package is an alias that can be used to reference this package in other methods within this
|
||||
* class.
|
||||
*
|
||||
* By default, the `PackageManager` class will look for a `package.json` file for Node dependencies, and a config
|
||||
* file for the compilable configuration
|
||||
*
|
||||
* @param string $name The name of the package being registered
|
||||
* @param string $path The path to the compilable JS configuration file (it must be inside of the base_path()). If there is a related package.json file
|
||||
* then it is required to be present in the same directory as the config file
|
||||
* @param string $type The type of compilable
|
||||
* @throws SystemException
|
||||
*/
|
||||
public function registerPackage(string $name, string $path, string $type = 'mix'): void
|
||||
{
|
||||
// Symbolize the path
|
||||
$path = File::symbolizePath($path);
|
||||
|
||||
// Normalize the arguments
|
||||
$name = strtolower($name);
|
||||
$resolvedPath = PathResolver::resolve($path);
|
||||
$pinfo = pathinfo($resolvedPath);
|
||||
$relativePath = Str::after($pinfo['dirname'], base_path() . DIRECTORY_SEPARATOR);
|
||||
$configFile = $pinfo['basename'];
|
||||
|
||||
// Require $configFile to be a JS file
|
||||
$extension = File::extension($configFile);
|
||||
if (!in_array($extension, ['js', 'mjs'])) {
|
||||
throw new SystemException(sprintf(
|
||||
'Compilable configuration for package "%s" must be a JavaScript file ending with .js or .mjs',
|
||||
$name
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the package path exists
|
||||
if (!File::exists(base_path($relativePath))) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Cannot register "%s" as a compilable package; the "%s" path does not exist.',
|
||||
$name,
|
||||
base_path($relativePath)
|
||||
));
|
||||
}
|
||||
|
||||
$package = $relativePath . '/package.json';
|
||||
$config = $relativePath . DIRECTORY_SEPARATOR . $configFile;
|
||||
|
||||
if (!File::exists(base_path($config))) {
|
||||
throw new SystemException(sprintf(
|
||||
'Cannot register "%s" as a compilable package; the config file "%s" does not exist.',
|
||||
$name,
|
||||
$config
|
||||
));
|
||||
}
|
||||
|
||||
// Check for any existing packages already registered under the provided name
|
||||
if (isset($this->packages[$name])) {
|
||||
throw new SystemException(sprintf(
|
||||
'Cannot register "%s" as a compilable package; it has already been registered at %s.',
|
||||
$name,
|
||||
$this->packages[$name]['config']
|
||||
));
|
||||
}
|
||||
|
||||
// Check for any existing package that already registers the given compilable config path
|
||||
foreach ($this->packages[$type] ?? [] as $packageName => $settings) {
|
||||
if ($settings['config'] === $config) {
|
||||
// If the package name is the same, we'll just discard the repeated registration
|
||||
if ($packageName === $name) {
|
||||
return;
|
||||
}
|
||||
throw new SystemException(sprintf(
|
||||
'Cannot register "%s" (%s) as a compilable package; it has already been registered as %s.',
|
||||
$name,
|
||||
$config,
|
||||
$packageName
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Register the package
|
||||
$this->packages[$type][$name] = [
|
||||
'path' => $relativePath,
|
||||
'package' => $package,
|
||||
'config' => $config
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an expected package type from its name
|
||||
*/
|
||||
public function getPackageTypeFromName(string $package): ?string
|
||||
{
|
||||
// Check if package could be a module
|
||||
if (Str::startsWith($package, 'module-') && !in_array($package, ['system', 'backend', 'cms'])) {
|
||||
return static::TYPE_MODULE;
|
||||
}
|
||||
|
||||
// Check if package could be a theme
|
||||
if (
|
||||
in_array('Cms', Config::get('cms.loadModules'))
|
||||
&& Str::startsWith($package, 'theme-')
|
||||
&& Theme::exists(Str::after($package, 'theme-'))
|
||||
) {
|
||||
return static::TYPE_THEME;
|
||||
}
|
||||
|
||||
// Check if a package could be a plugin
|
||||
if (PluginManager::instance()->exists($package)) {
|
||||
return static::TYPE_PLUGIN;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the package.json file path used for checking if packages are in workspaces or ignored
|
||||
*/
|
||||
public function setPackageJsonPath(string $packageJsonPath): static
|
||||
{
|
||||
$this->packageJson = new PackageJson($packageJsonPath);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registration method for a compiler type
|
||||
*/
|
||||
protected function getRegistrationMethod(string $type): string
|
||||
{
|
||||
return sprintf('register%sPackages', ucfirst($type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided package is ignored.
|
||||
*/
|
||||
protected function isPackageIgnored(string $packagePath): bool
|
||||
{
|
||||
return $this->packageJson->hasIgnoredPackage($packagePath);
|
||||
}
|
||||
}
|
||||
80
modules/system/classes/asset/Vite.php
Normal file
80
modules/system/classes/asset/Vite.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace System\Classes\Asset;
|
||||
|
||||
use Illuminate\Foundation\Vite as LaravelVite;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Winter\Storm\Exception\SystemException;
|
||||
|
||||
class Vite extends LaravelVite
|
||||
{
|
||||
/**
|
||||
* Generate Vite tags for an entrypoint(s).
|
||||
*
|
||||
* @param string|array $entrypoints The list of entry points for Vite
|
||||
* @param string|null $package The package name of the plugin or theme
|
||||
* @param string|null $buildDirectory The Vite build directory
|
||||
*
|
||||
* @return HtmlString
|
||||
*
|
||||
* @throws SystemException
|
||||
*/
|
||||
public function __invoke($entrypoints, $package = null, ?string $buildDirectory = null)
|
||||
{
|
||||
if (!$package) {
|
||||
throw new \InvalidArgumentException('A package must be passed');
|
||||
}
|
||||
|
||||
$compilableAssetPackage = static::resolvePackage($package);
|
||||
|
||||
$this->useHotFile(base_path($compilableAssetPackage['path'] . '/assets/dist/hot'));
|
||||
return parent::__invoke($entrypoints, $compilableAssetPackage['path'] . ($buildDirectory ?? '/assets/dist'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SystemException if the package could not be found
|
||||
*/
|
||||
protected static function resolvePackage(string $package): array
|
||||
{
|
||||
// Normalise the package name
|
||||
$package = strtolower($package);
|
||||
|
||||
if (!($compilableAssetPackage = PackageManager::instance()->getPackages('vite', true)[$package] ?? null)) {
|
||||
throw new SystemException('Unable to resolve package: ' . $package);
|
||||
}
|
||||
|
||||
return $compilableAssetPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate Vite tags for an entrypoint(s).
|
||||
*
|
||||
* @param string|array $entrypoints The list of entry points for Vite
|
||||
* @param string $package The package name of the plugin or theme
|
||||
* @param string|null $buildDirectory The Vite build directory
|
||||
*
|
||||
* @throws SystemException
|
||||
*/
|
||||
public static function tags(array|string $entrypoints, string $package, ?string $buildDirectory = null): HtmlString
|
||||
{
|
||||
return App::make(\Illuminate\Foundation\Vite::class)($entrypoints, $package, $buildDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate Vite React Refresh tag.
|
||||
*
|
||||
* @param string $package The package name of the plugin or theme
|
||||
* @param string|null $buildDirectory The Vite build directory
|
||||
*
|
||||
* @throws SystemException
|
||||
*/
|
||||
public static function reactRefreshTag(string $package, ?string $buildDirectory = null): ?HtmlString
|
||||
{
|
||||
$compilableAssetPackage = static::resolvePackage($package);
|
||||
return App::make(\Illuminate\Foundation\Vite::class)
|
||||
->useHotFile(base_path($compilableAssetPackage['path'] . '/assets/dist/hot'))
|
||||
->useBuildDirectory($compilableAssetPackage['path'] . ($buildDirectory ?? '/assets/dist'))
|
||||
->reactRefresh();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user