diff --git a/modules/backend/console/UserCreate.php b/modules/backend/console/UserCreate.php
index 2a9f5bfce1..b1ec787188 100644
--- a/modules/backend/console/UserCreate.php
+++ b/modules/backend/console/UserCreate.php
@@ -58,7 +58,7 @@ public function handle(): int
'first_name' => $this->option('fname') ?: $this->ask('First name', ''),
'last_name' => $this->option('lname') ?: $this->ask('Last name', ''),
'role_id' => (
- $role = UserRole::where(
+ UserRole::where(
'code',
$this->option('role') ?: $this->choice(
'Role',
diff --git a/modules/backend/formwidgets/Relation.php b/modules/backend/formwidgets/Relation.php
index e15462fcd7..830af3b492 100644
--- a/modules/backend/formwidgets/Relation.php
+++ b/modules/backend/formwidgets/Relation.php
@@ -1,10 +1,12 @@
-getEventHandler('onRemoveAttachment') ?>"
data-request-confirm="= e(trans('backend::lang.fileupload.remove_confirm')) ?>"
- data-request-data="file_id: = $file->id ?>"
+ data-request-data="file_id: '= $file->id ?>'"
>
= e($file->sizeToString()) ?>
diff --git a/modules/backend/formwidgets/fileupload/partials/_file_single.php b/modules/backend/formwidgets/fileupload/partials/_file_single.php
index 3ebd512743..309af56c8d 100644
--- a/modules/backend/formwidgets/fileupload/partials/_file_single.php
+++ b/modules/backend/formwidgets/fileupload/partials/_file_single.php
@@ -39,7 +39,7 @@ class="field-fileupload style-file-single = $singleFile ? 'is-populated' : ''
class="upload-remove-button"
data-request="= $this->getEventHandler('onRemoveAttachment') ?>"
data-request-confirm="= e(trans('backend::lang.fileupload.remove_confirm')) ?>"
- data-request-data="file_id: = $singleFile->id ?>"
+ data-request-data="file_id: '= $singleFile->id ?>'"
>
diff --git a/modules/backend/formwidgets/fileupload/partials/_image_multi.php b/modules/backend/formwidgets/fileupload/partials/_image_multi.php
index fd916e08ed..59364b0cda 100644
--- a/modules/backend/formwidgets/fileupload/partials/_image_multi.php
+++ b/modules/backend/formwidgets/fileupload/partials/_image_multi.php
@@ -36,7 +36,7 @@ class="field-fileupload style-image-multi is-sortable is-multi = count($fileLi
class="upload-remove-button"
data-request="= $this->getEventHandler('onRemoveAttachment') ?>"
data-request-confirm="= e(trans('backend::lang.fileupload.remove_confirm')) ?>"
- data-request-data="file_id: = $file->id ?>"
+ data-request-data="file_id: '= $file->id ?>'"
>
= e($file->sizeToString()) ?>
diff --git a/modules/backend/formwidgets/fileupload/partials/_image_single.php b/modules/backend/formwidgets/fileupload/partials/_image_single.php
index b0c58ef863..cf5ce5c662 100644
--- a/modules/backend/formwidgets/fileupload/partials/_image_single.php
+++ b/modules/backend/formwidgets/fileupload/partials/_image_single.php
@@ -40,7 +40,7 @@ class="upload-button">
class="upload-remove-button"
data-request="= $this->getEventHandler('onRemoveAttachment') ?>"
data-request-confirm="= e(trans('backend::lang.fileupload.remove_confirm')) ?>"
- data-request-data="file_id: = $singleFile->id ?>"
+ data-request-data="file_id: '= $singleFile->id ?>'"
>
= e($singleFile->sizeToString()) ?>
diff --git a/modules/system/tests/classes/AutoDatasourceTest.php b/modules/cms/tests/classes/AutoDatasourceTest.php
similarity index 99%
rename from modules/system/tests/classes/AutoDatasourceTest.php
rename to modules/cms/tests/classes/AutoDatasourceTest.php
index d3550ee691..b481c2afc9 100644
--- a/modules/system/tests/classes/AutoDatasourceTest.php
+++ b/modules/cms/tests/classes/AutoDatasourceTest.php
@@ -1,6 +1,6 @@
app->singleton(Vite::class, \System\Classes\Vite::class);
+ $this->app->singleton(LaravelVite::class, \System\Classes\Asset\Vite::class);
}
/**
@@ -321,19 +321,19 @@ protected function registerConsole()
$this->registerConsoleCommand('plugin.list', \System\Console\PluginList::class);
$this->registerConsoleCommand('mix.compile', Console\Asset\Mix\MixCompile::class);
- $this->registerConsoleCommand('mix.config', Console\Asset\Mix\MixConfig::class);
+ $this->registerConsoleCommand('mix.config', Console\Asset\Mix\MixCreate::class);
$this->registerConsoleCommand('mix.install', Console\Asset\Mix\MixInstall::class);
$this->registerConsoleCommand('mix.list', Console\Asset\Mix\MixList::class);
$this->registerConsoleCommand('mix.watch', Console\Asset\Mix\MixWatch::class);
$this->registerConsoleCommand('vite.compile', Console\Asset\Vite\ViteCompile::class);
- $this->registerConsoleCommand('vite.config', Console\Asset\Vite\ViteConfig::class);
+ $this->registerConsoleCommand('vite.config', Console\Asset\Vite\ViteCreate::class);
$this->registerConsoleCommand('vite.install', Console\Asset\Vite\ViteInstall::class);
$this->registerConsoleCommand('vite.list', Console\Asset\Vite\ViteList::class);
$this->registerConsoleCommand('vite.watch', Console\Asset\Vite\ViteWatch::class);
- $this->registerConsoleCommand('npm.run', Console\NpmRun::class);
- $this->registerConsoleCommand('npm.update', Console\NpmUpdate::class);
+ $this->registerConsoleCommand('npm.run', Console\Asset\NpmRun::class);
+ $this->registerConsoleCommand('npm.update', Console\Asset\NpmUpdate::class);
}
/*
diff --git a/modules/system/classes/MailManager.php b/modules/system/classes/MailManager.php
index b11fee91cb..e918c453ac 100644
--- a/modules/system/classes/MailManager.php
+++ b/modules/system/classes/MailManager.php
@@ -344,7 +344,7 @@ public function listRegisteredLayouts()
/**
* Registers a callback function that defines mail templates.
* The callback function should register templates by calling the manager's
- * registerMailTemplates() function. Thi instance is passed to the
+ * registerMailTemplates() function. This instance is passed to the
* callback function as an argument. Usage:
*
* MailManager::registerCallback(function ($manager) {
diff --git a/modules/system/classes/asset/BundleManager.php b/modules/system/classes/asset/BundleManager.php
new file mode 100644
index 0000000000..c6d54afec0
--- /dev/null
+++ b/modules/system/classes/asset/BundleManager.php
@@ -0,0 +1,246 @@
+
+ * @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'
+ ],
+ ],
+ ];
+
+ /**
+ * 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->registerScaffoldHandler('tailwind', function (string $contents, string $contentType) {
+ return match ($contentType) {
+ 'mix' => $contents . PHP_EOL . << $this->getFixture('css/tailwind.css.fixture'),
+ default => $contents
+ };
+ });
+
+ $manager->registerScaffoldHandler('vue', function (string $contents, string $contentType) {
+ return match ($contentType) {
+ 'vite' => str_replace(
+ '}),',
+ << 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
+ };
+ });
+ });
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/modules/system/classes/PackageJson.php b/modules/system/classes/asset/PackageJson.php
similarity index 97%
rename from modules/system/classes/PackageJson.php
rename to modules/system/classes/asset/PackageJson.php
index 379627cc26..be6469c10b 100644
--- a/modules/system/classes/PackageJson.php
+++ b/modules/system/classes/asset/PackageJson.php
@@ -1,6 +1,6 @@
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`.
diff --git a/modules/system/classes/CompilableAssets.php b/modules/system/classes/asset/PackageManager.php
similarity index 96%
rename from modules/system/classes/CompilableAssets.php
rename to modules/system/classes/asset/PackageManager.php
index 38453bbeeb..edb04c8571 100644
--- a/modules/system/classes/CompilableAssets.php
+++ b/modules/system/classes/asset/PackageManager.php
@@ -1,6 +1,6 @@
* @copyright Winter CMS Maintainers
*/
-class CompilableAssets
+class PackageManager
{
use \Winter\Storm\Support\Traits\Singleton;
@@ -258,7 +258,7 @@ public function getPackage(string $name, bool $includeIgnored = false): array
* 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 `CompilableAssets` class will look for a `package.json` file for Node dependencies, and a config
+ * 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
diff --git a/modules/system/classes/Vite.php b/modules/system/classes/asset/Vite.php
similarity index 87%
rename from modules/system/classes/Vite.php
rename to modules/system/classes/asset/Vite.php
index cad8848d0d..f5dabc7057 100644
--- a/modules/system/classes/Vite.php
+++ b/modules/system/classes/asset/Vite.php
@@ -1,6 +1,6 @@
getPackages('vite')[$package] ?? null)) {
+ if (!($compilableAssetPackage = PackageManager::instance()->getPackages('vite')[$package] ?? null)) {
throw new SystemException('Unable to resolve package: ' . $package);
}
- $this->useHotFile(base_path($compilableAssetPackage['path'] . '/public/hot'));
- return parent::__invoke($entrypoints, $compilableAssetPackage['path'] . '/public/build');
+ $this->useHotFile(base_path($compilableAssetPackage['path'] . '/assets/dist/hot'));
+ return parent::__invoke($entrypoints, $compilableAssetPackage['path'] . '/assets/dist');
}
/**
diff --git a/modules/system/console/asset/AssetCompile.php b/modules/system/console/asset/AssetCompile.php
index 6710f6ad91..3fca7f8e9c 100644
--- a/modules/system/console/asset/AssetCompile.php
+++ b/modules/system/console/asset/AssetCompile.php
@@ -3,8 +3,8 @@
namespace System\Console\Asset;
use Symfony\Component\Process\Process;
-use System\Classes\CompilableAssets;
-use System\Classes\PackageJson;
+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;
@@ -42,7 +42,7 @@ public function compileHandle(string $type): int
return 1;
}
- $compilableAssets = CompilableAssets::instance();
+ $compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$registeredPackages = $compilableAssets->getPackages($type);
@@ -121,7 +121,7 @@ public function compileHandle(string $type): int
public function watchHandle(string $type): int
{
- $compilableAssets = CompilableAssets::instance();
+ $compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$packages = $compilableAssets->getPackages($type);
diff --git a/modules/system/console/asset/AssetConfig.php b/modules/system/console/asset/AssetConfig.php
deleted file mode 100644
index df12b6d190..0000000000
--- a/modules/system/console/asset/AssetConfig.php
+++ /dev/null
@@ -1,202 +0,0 @@
- [
- 'tailwindcss' => '^3.4.0',
- '@tailwindcss/forms' => '^0.5.2',
- '@tailwindcss/typography' => '^0.5.2',
- ],
- 'vue' => [
- 'vue' => '^3.4.0',
- ]
- ];
-
- /**
- * 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;
-
- /**
- * Execute the console command.
- */
- public function handle(): int
- {
- $package = $this->argument('packageName');
-
- $this->fixturePath = __DIR__ . '/fixtures/config';
-
- $compilableAssets = CompilableAssets::instance();
- $compilableAssets->fireCallbacks();
-
- $packages = $compilableAssets->getPackages($this->assetType, true);
-
- if (
- isset($packages[$package])
- && !$this->confirm('Package `' . $package . '` has already been configured, are you sure you wish to continue?')
- ) {
- return 1;
- }
-
- [$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, [
- 'tailwind' => $this->option('tailwind'),
- 'vue' => $this->option('vue'),
- 'stubs' => $this->option('stubs')
- ]);
-
- $packageJson->save();
-
- return 0;
- }
-
- /**
- * Resolve the path and type of the package by name
- *
- * @return array|null[]
- */
- protected function getPackagePathType(string $package): array
- {
- if (str_starts_with($package, 'theme-')) {
- if ($theme = Theme::load(str_after($package, 'theme-'))) {
- return [$theme->getPath(), static::TYPE_THEME];
- }
-
- 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 $package,
- string $type,
- string $path,
- array $options
- ): void {
- if ($options['tailwind']) {
- $this->writeFile(
- $path . '/tailwind.config.js',
- File::get($this->fixturePath . '/tailwind/tailwind.' . $type . '.config.js.fixture')
- );
-
- $this->writeFile(
- $path . '/postcss.config.mjs',
- File::get($this->fixturePath . '/tailwind/postcss.config.js.fixture')
- );
-
- foreach ($this->packages['tailwind'] as $dependency => $version) {
- $packageJson->addDependency($dependency, $version, dev: true);
- }
- }
-
- if ($options['vue']) {
- foreach ($this->packages['vue'] as $dependency => $version) {
- $packageJson->addDependency($dependency, $version, dev: true);
- }
- }
-
- $packageName = strtolower(str_replace('.', '-', $package));
-
- if ($options['stubs']) {
- // Setup css stubs
- if (!File::exists($path . '/assets/src/css')) {
- File::makeDirectory($path . '/assets/src/css', recursive: true);
- }
-
- $this->writeFile(
- $path . '/assets/src/css/' . $packageName . '.css',
- File::get($this->fixturePath . '/css/' . ($options['tailwind'] ? 'tailwind' : 'default') . '.css.fixture')
- );
-
- // Setup js stubs
- if (!File::exists($path . '/assets/src/js')) {
- File::makeDirectory($path . '/assets/src/js', recursive: true);
- }
-
- $this->writeFile(
- $path . '/assets/src/js/' . $packageName . '.js',
- File::get($this->fixturePath . '/js/' . ($options['vue'] ? 'vue' : 'default') . '.js.fixture')
- );
- }
-
- $config = str_replace(
- '{{packageName}}',
- $packageName,
- File::get(
- sprintf(
- '%s/%s/%s%s%s.js.fixture',
- $this->fixturePath,
- $this->assetType,
- pathinfo($this->configFile, PATHINFO_FILENAME),
- $options['tailwind'] ? '.tailwind' : '',
- $options['vue'] ? '.vue' : '',
- )
- )
- );
-
- $this->writeFile($path . '/' . $this->configFile, $config);
- }
-
- /**
- * Write a file but ask for conformation before overwriting
- */
- protected function writeFile(string $path, string $content): int
- {
- if (File::exists($path) && !$this->confirm(sprintf('%s already exists, overwrite?', basename($path)))) {
- return 0;
- }
-
- return File::put($path, $content);
- }
-}
diff --git a/modules/system/console/asset/AssetCreate.php b/modules/system/console/asset/AssetCreate.php
new file mode 100644
index 0000000000..7912a12789
--- /dev/null
+++ b/modules/system/console/asset/AssetCreate.php
@@ -0,0 +1,233 @@
+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 (
+ isset($packages[$package])
+ && !$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();
+
+ $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 ($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 (File::exists($path) && !$this->confirm(sprintf('%s already exists, overwrite?', basename($path)))) {
+ return 0;
+ }
+
+ $result = File::put($path, $content);
+
+ $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
+ }
+}
diff --git a/modules/system/console/asset/AssetInstall.php b/modules/system/console/asset/AssetInstall.php
index 273a512a15..10f5c78e95 100644
--- a/modules/system/console/asset/AssetInstall.php
+++ b/modules/system/console/asset/AssetInstall.php
@@ -5,8 +5,8 @@
use Cms\Classes\Theme;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
-use System\Classes\CompilableAssets;
-use System\Classes\PackageJson;
+use System\Classes\Asset\PackageJson;
+use System\Classes\Asset\PackageManager;
use System\Classes\PluginManager;
use Winter\Storm\Console\Command;
use Winter\Storm\Support\Facades\Config;
@@ -34,7 +34,7 @@ abstract class AssetInstall extends Command
protected string $npmCommand = 'install';
/**
- * Type of asset to be installed, @see CompilableAssets
+ * Type of asset to be installed, @see PackageManager
*/
protected string $assetType;
@@ -44,9 +44,9 @@ abstract class AssetInstall extends Command
protected string $configFile;
/**
- * The packages required for asset compilation
+ * The required packages for this compiler
*/
- protected array $packages;
+ protected array $requiredPackages = [];
/**
* Execute the console command.
@@ -101,7 +101,7 @@ public function handle(): int
protected function getRequestedAndRegisteredPackages(): array
{
- $compilableAssets = CompilableAssets::instance();
+ $compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$registeredPackages = $compilableAssets->getPackages($this->assetType);
@@ -180,7 +180,7 @@ protected function getRequestedAndRegisteredPackages(): array
protected function validateRequirePackagesPresent(PackageJson $packageJson): PackageJson
{
// Check to see if required packages are already present as a dependency
- foreach ($this->packages as $package => $version) {
+ foreach ($this->requiredPackages as $package => $version) {
if (
!$packageJson->hasDependency($package)
&& $this->confirm($package . ' was not found as a dependency in package.json, would you like to add it?', true)
diff --git a/modules/system/console/asset/AssetList.php b/modules/system/console/asset/AssetList.php
index 05c5f8b866..503e7b55bc 100644
--- a/modules/system/console/asset/AssetList.php
+++ b/modules/system/console/asset/AssetList.php
@@ -2,7 +2,7 @@
namespace System\Console\Asset;
-use System\Classes\CompilableAssets;
+use System\Classes\Asset\PackageManager;
use Winter\Storm\Console\Command;
use Winter\Storm\Support\Facades\File;
@@ -12,7 +12,7 @@ abstract class AssetList extends Command
public function handle(): int
{
- $compilableAssets = CompilableAssets::instance();
+ $compilableAssets = PackageManager::instance();
$compilableAssets->fireCallbacks();
$packages = $compilableAssets->getPackages($this->assetType, true);
diff --git a/modules/system/console/NpmRun.php b/modules/system/console/asset/NpmRun.php
similarity index 94%
rename from modules/system/console/NpmRun.php
rename to modules/system/console/asset/NpmRun.php
index 2306d8e8c8..8297cd41cd 100644
--- a/modules/system/console/NpmRun.php
+++ b/modules/system/console/asset/NpmRun.php
@@ -1,10 +1,10 @@
fireCallbacks();
$name = $this->argument('package');
diff --git a/modules/system/console/NpmUpdate.php b/modules/system/console/asset/NpmUpdate.php
similarity index 96%
rename from modules/system/console/NpmUpdate.php
rename to modules/system/console/asset/NpmUpdate.php
index 5fbb17a467..66665bca1a 100644
--- a/modules/system/console/NpmUpdate.php
+++ b/modules/system/console/asset/NpmUpdate.php
@@ -1,6 +1,6 @@
{
const mix = require(basePath + '/node_modules/laravel-mix/src/Mix').primary;
- // disable manifest
- mix.manifest.refresh = _ => void 0
-
- mix.listen('init', function (mix) {
+ mix.listen('init', function (_mix) {
// Setup Winter path aliases
- mix._api.alias({
+ _mix._api.alias({
'$': '%pluginsPath%',
'~': '%appPath%',
});
// disable notifications if not in watch
%notificationInject%
// define options
- mix._api.options({
+ _mix._api.options({
processCssUrls: false,
clearConsole: false,
cssNano: {
@@ -26,11 +23,20 @@ module.exports = async () => {
}
});
// enable source maps for dev builds
- if (!mix._api.inProduction()) {
- mix._api.webpackConfig({
+ if (!_mix._api.inProduction()) {
+ _mix._api.webpackConfig({
devtool: 'inline-source-map'
});
- mix._api.sourceMaps();
+ _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';
+ }
}
});
diff --git a/modules/system/console/asset/mix/MixConfig.php b/modules/system/console/asset/mix/MixCreate.php
similarity index 56%
rename from modules/system/console/asset/mix/MixConfig.php
rename to modules/system/console/asset/mix/MixCreate.php
index 051b8fcc7f..101032ca1e 100644
--- a/modules/system/console/asset/mix/MixConfig.php
+++ b/modules/system/console/asset/mix/MixCreate.php
@@ -1,22 +1,27 @@
'^6.0.41'
+ protected array $requiredPackages = [
+ 'laravel-mix' => '^6.0.41',
];
}
diff --git a/modules/system/console/asset/mix/MixWatch.php b/modules/system/console/asset/mix/MixWatch.php
index 0f7ba4be02..f8d88d4a7d 100644
--- a/modules/system/console/asset/mix/MixWatch.php
+++ b/modules/system/console/asset/mix/MixWatch.php
@@ -2,8 +2,6 @@
namespace System\Console\Asset\Mix;
-use System\Classes\CompilableAssets;
-
class MixWatch extends MixCompile
{
/**
diff --git a/modules/system/console/asset/vite/ViteConfig.php b/modules/system/console/asset/vite/ViteConfig.php
deleted file mode 100644
index 6d90516617..0000000000
--- a/modules/system/console/asset/vite/ViteConfig.php
+++ /dev/null
@@ -1,32 +0,0 @@
-makePackageName($this->argument('packageName'));
+ $this->output->writeln('');
+ $this->info('Add the following to your twig to enable asset loading:');
+ $this->output->writeln(sprintf(
+ '{{ vite([\'assets/src/css/%1$s.css\', \'assets/src/js/%1$s.js\'], \'%2$s\') }}>',
+ $packageName,
+ strtolower($this->argument('packageName'))
+ ));
+ }
+}
diff --git a/modules/system/console/asset/vite/ViteInstall.php b/modules/system/console/asset/vite/ViteInstall.php
index 4a031189c7..0e0c4ea8bf 100644
--- a/modules/system/console/asset/vite/ViteInstall.php
+++ b/modules/system/console/asset/vite/ViteInstall.php
@@ -35,9 +35,9 @@ class ViteInstall extends AssetInstall
protected string $configFile = 'vite.config.mjs';
/**
- * The packages required for asset compilation
+ * The required packages for this compiler
*/
- protected array $packages = [
+ protected array $requiredPackages = [
'vite' => '^5.2.11',
'laravel-vite-plugin' => '^1.0.4',
];
diff --git a/modules/system/console/scaffold/plugin/plugin.stub b/modules/system/console/scaffold/plugin/plugin.stub
index a6eb461676..b15df75f42 100644
--- a/modules/system/console/scaffold/plugin/plugin.stub
+++ b/modules/system/console/scaffold/plugin/plugin.stub
@@ -48,7 +48,7 @@ class Plugin extends PluginBase
return []; // Remove this line to activate
return [
- '{{ plugin_namespace }}\Components\MyComponent' => 'myComponent',
+ \{{ plugin_namespace }}\Components\MyComponent::class => 'myComponent',
];
}
diff --git a/modules/system/tests/classes/ImageResizerTest.php b/modules/system/tests/classes/ImageResizerTest.php
index fb31a5bfe9..76a533172e 100644
--- a/modules/system/tests/classes/ImageResizerTest.php
+++ b/modules/system/tests/classes/ImageResizerTest.php
@@ -50,6 +50,10 @@ public function tearDown(): void
*/
public function testConfiguration()
{
+ if (!in_array('Cms', Config::get('cms.loadModules', []))) {
+ $this->markTestSkipped('The CMS module is not active.');
+ }
+
// Resize with default options
$imageResizer = new ImageResizer(
(new CmsController())->themeUrl('assets/images/winter.png'),
@@ -178,6 +182,10 @@ public function testConfiguration()
*/
public function testURLSources()
{
+ if (!in_array('Cms', Config::get('cms.loadModules', []))) {
+ $this->markTestSkipped('The CMS module is not active.');
+ }
+
// Theme URL (absolute URL)
$this->setUpStorage();
$this->copyMedia();
@@ -352,6 +360,10 @@ public function testSpaceInFilename()
public function testGetResizedUrl()
{
+ if (!in_array('Cms', Config::get('cms.loadModules', []))) {
+ $this->markTestSkipped('The CMS module is not active.');
+ }
+
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'));
Config::set('cms.linkPolicy', 'force');
@@ -365,6 +377,10 @@ public function testGetResizedUrl()
public function testGetResizerUrl()
{
+ if (!in_array('Cms', Config::get('cms.loadModules', []))) {
+ $this->markTestSkipped('The CMS module is not active.');
+ }
+
$imageResizer = new ImageResizer((new CmsController())->themeUrl('assets/images/winter.png'));
Config::set('cms.linkPolicy', 'force');
diff --git a/modules/system/tests/classes/asset/BundleManagerTest.php b/modules/system/tests/classes/asset/BundleManagerTest.php
new file mode 100644
index 0000000000..bc6eaaacc8
--- /dev/null
+++ b/modules/system/tests/classes/asset/BundleManagerTest.php
@@ -0,0 +1,232 @@
+bundleManager = BundleManager::instance();
+ }
+
+ /**
+ * Test the registerBundles & registerBundle functions correctly allow the user to append bundles
+ */
+ public function testRegisterBundles(): void
+ {
+ $defaultBundles = $this->bundleManager->getBundles();
+ $this->bundleManager->registerBundles([
+ 'winter1js' => [
+ 'winter1js' => 'v1.0.0'
+ ]
+ ]);
+
+ $this->bundleManager->registerBundle('winter2js', [
+ 'winter2js' => 'v1.0.0'
+ ]);
+
+ $bundles = $this->bundleManager->getBundles();
+ $this->assertCount(count($defaultBundles) + 2, $bundles);
+ $this->assertContains('winter1js', $bundles);
+ $this->assertContains('winter2js', $bundles);
+ }
+
+ /**
+ * This test ensures that defining new bundles does not affect the default bundles
+ */
+ public function testRegisterBundlesDoesNotBreakDefaults(): void
+ {
+ // Get the default bundles so we can validate them existing later
+ $defaultBundles = $this->bundleManager->getBundles();
+ // Flush the instance as if we have just booted
+ BundleManager::forgetInstance();
+ $this->bundleManager = BundleManager::instance();
+ // Register a new bundle
+ $this->bundleManager->registerBundles([
+ 'winterjs' => [
+ 'winterjs' => 'v1.0.0'
+ ]
+ ]);
+ // Grab the current bundles
+ $bundles = $this->bundleManager->getBundles();
+ // Validate that all default bundles have been registered when adding a new bundle
+ foreach ($defaultBundles as $defaultBundle) {
+ $this->assertContains($defaultBundle, $bundles);
+ }
+ }
+
+ /**
+ * Test the forcing a package to a new version
+ */
+ public function testRegisterBundlesOverrideDefault(): void
+ {
+ $tailwindPackages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
+
+ $this->bundleManager->registerBundle('tailwind', [
+ 'tailwindcss' => 'dev-999.999.999',
+ '@tailwindcss/forms' => 'dev-999.999.999',
+ '@tailwindcss/typography' => 'dev-999.999.999',
+ ]);
+
+ $updatedTailwindPackages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
+
+ foreach ($tailwindPackages as $tailwindPackage => $version) {
+ $this->assertArrayHasKey($tailwindPackage, $updatedTailwindPackages);
+ $this->assertNotEquals($updatedTailwindPackages[$tailwindPackage], $version);
+ }
+ }
+
+ /**
+ * Test getting all supported bundles
+ */
+ public function testGetBundles(): void
+ {
+ $bundles = $this->bundleManager->getBundles();
+
+ $this->assertIsArray($bundles);
+ $count = count($bundles);
+
+ $this->bundleManager->registerBundle('test', [
+ 'a' => 'v0.1.2',
+ ]);
+
+ $this->assertCount($count + 1, $this->bundleManager->getBundles());
+ }
+
+ /**
+ * Test getting bundle packages works and allows for config overloading
+ */
+ public function testGetBundlePackages(): void
+ {
+ // Test that getting a default returns an array and validate one of the packages
+ $packages = $this->bundleManager->getBundlePackages('tailwind', 'mix');
+ $this->assertIsArray($packages);
+ $this->assertArrayHasKey('tailwindcss', $packages);
+
+ // Test that getting a package with compiler dependant packages does not return the package for invalid compiler
+ $packages = $this->bundleManager->getBundlePackages('vue', 'mix');
+ $this->assertIsArray($packages);
+ $this->assertArrayNotHasKey('@vitejs/plugin-vue', $packages);
+
+ // Test that getting a package with compiler dependant packages does return the package for a valid compiler
+ $packages = $this->bundleManager->getBundlePackages('vue', 'vite');
+ $this->assertIsArray($packages);
+ $this->assertArrayHasKey('@vitejs/plugin-vue', $packages);
+
+ // Validate that `testing` does not exist
+ $packages = $this->bundleManager->getBundlePackages('testing', 'vite');
+ $this->assertIsArray($packages);
+ $this->assertEmpty($packages);
+
+ $this->bundleManager->registerBundle('testing', [
+ 'a' => 'v0.1.2',
+ 'mix' => [
+ 'b' => 'v0.1.3',
+ ]
+ ]);
+
+ // Validate the testing bundle works with compiler dependent packages
+ $packages = $this->bundleManager->getBundlePackages('testing', 'vite');
+ $this->assertIsArray($packages);
+ $this->assertCount(1, $packages);
+
+ $packages = $this->bundleManager->getBundlePackages('testing', 'mix');
+ $this->assertIsArray($packages);
+ $this->assertCount(2, $packages);
+ }
+
+ /**
+ * Test the AssetBundles setup handlers functionality
+ */
+ public function testSetupHandlers(): void
+ {
+ // Test that the default handler is accessible
+ $handler = $this->bundleManager->getSetupHandler('tailwind');
+ $this->assertIsCallable($handler);
+
+ // Add a new handler
+ $this->bundleManager->registerSetupHandler('testing', fn () => true);
+ $handler = $this->bundleManager->getSetupHandler('testing');
+ $this->assertIsCallable($handler);
+
+ // Validate that handler returned is ours
+ $this->assertTrue($handler());
+
+ // Override the handler
+ $this->bundleManager->registerSetupHandler('testing', fn () => false);
+ $handler = $this->bundleManager->getSetupHandler('testing');
+ $this->assertIsCallable($handler);
+ $this->assertFalse($handler());
+ }
+
+ /**
+ * Test the AssetBundles scaffold handlers functionality
+ */
+ public function testScaffoldHandlers(): void
+ {
+ // Test that the default handler is accessible
+ $handler = $this->bundleManager->getScaffoldHandler('tailwind');
+ $this->assertIsCallable($handler);
+
+ // Add a new handler
+ $this->bundleManager->registerScaffoldHandler('testing', fn () => true);
+ $handler = $this->bundleManager->getScaffoldHandler('testing');
+ $this->assertIsCallable($handler);
+
+ // Validate that handler returned is ours
+ $this->assertTrue($handler());
+
+ // Override the handler
+ $this->bundleManager->registerScaffoldHandler('testing', fn () => false);
+ $handler = $this->bundleManager->getScaffoldHandler('testing');
+ $this->assertIsCallable($handler);
+ $this->assertFalse($handler());
+ }
+
+ /**
+ * Validate that when registered a callable is added to the `callbacks` array within the BundleManager
+ */
+ public function testRegisterCallback(): void
+ {
+ $this->expectExceptionMessage('callback registered');
+ $this->bundleManager->registerCallback(function (BundleManager $manager) {
+ throw new \RuntimeException('callback registered');
+ });
+ }
+
+ /**
+ * Test that calling registerBundles within registerCallback functions correctly
+ */
+ public function testLoadRegisteredBundles(): void
+ {
+ $this->bundleManager->registerCallback(function (BundleManager $manager) {
+ $manager->registerBundles([
+ 'winter-test-js' => [
+ 'test-package' => 'v0.1.2',
+ 'vite' => [
+ 'vite-package' => 'v0.1.3',
+ ]
+ ]
+ ]);
+ });
+
+ $this->assertContains('winter-test-js', $this->bundleManager->getBundles());
+
+ $bundle = $this->bundleManager->getBundlePackages('winter-test-js', 'mix');
+ $this->assertArrayHasKey('test-package', $bundle);
+ $this->assertArrayNotHasKey('vite-package', $bundle);
+
+ $bundle = $this->bundleManager->getBundlePackages('winter-test-js', 'vite');
+ $this->assertArrayHasKey('test-package', $bundle);
+ $this->assertArrayHasKey('vite-package', $bundle);
+ }
+}
diff --git a/modules/system/tests/classes/PackageJsonTest.php b/modules/system/tests/classes/asset/PackageJsonTest.php
similarity index 86%
rename from modules/system/tests/classes/PackageJsonTest.php
rename to modules/system/tests/classes/asset/PackageJsonTest.php
index 661ed6d7eb..ed8d244e9b 100644
--- a/modules/system/tests/classes/PackageJsonTest.php
+++ b/modules/system/tests/classes/asset/PackageJsonTest.php
@@ -1,20 +1,32 @@
testFile = __DIR__ . '/../../fixtures/npm/package-test.json';
+ }
+
/**
* Test loading a package.json file from path
- *
- * @return void
*/
public function testLoadFile(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$contents = $packageJson->getContents();
$this->assertArrayHasKey('workspaces', $contents);
@@ -24,8 +36,6 @@ public function testLoadFile(): void
/**
* Test creating an instance with non-existing file
- *
- * @return void
*/
public function testNewFile(): void
{
@@ -37,8 +47,6 @@ public function testNewFile(): void
/**
* Test creating an instance without a path
- *
- * @return void
*/
public function testMemoryInstance(): void
{
@@ -50,8 +58,6 @@ public function testMemoryInstance(): void
/**
* Test setting and getting the name property
- *
- * @return void
*/
public function testNameMethods(): void
{
@@ -71,10 +77,23 @@ public function testNameMethods(): void
$this->assertEquals('example-name', $contents['name']);
}
+ /**
+ * Test getting the path of the current file
+ */
+ public function testGetPath(): void
+ {
+ $packageJson = new PackageJson(__DIR__ . '/package.json');
+ $path = $packageJson->getPath();
+ $this->assertIsString($path);
+ $this->assertEquals(__DIR__ . '/package.json', $path);
+
+ $packageJson = new PackageJson();
+ $path = $packageJson->getPath();
+ $this->assertNull($path);
+ }
+
/**
* Test validating the name on set
- *
- * @return void
*/
public function testNameValidation(): void
{
@@ -119,24 +138,20 @@ public function testNameValidation(): void
/**
* Test checking package workspace exists
- *
- * @return void
*/
public function testHasWorkspace(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$this->assertFalse($packageJson->hasWorkspace('themes/test'));
}
/**
* Test adding workspace package
- *
- * @return void
*/
public function testAddWorkspace(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasWorkspace('themes/test'));
$packageJson->addWorkspace('themes/test');
$this->assertTrue($packageJson->hasWorkspace('themes/test'));
@@ -150,12 +165,10 @@ public function testAddWorkspace(): void
/**
* Test removing workspace package
- *
- * @return void
*/
public function testRemoveWorkspace(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$packageJson->removeWorkspace('themes/demo');
$this->assertFalse($packageJson->hasWorkspace('themes/demo'));
@@ -168,12 +181,10 @@ public function testRemoveWorkspace(): void
/**
* Test when adding a workspace package, it removes the package from ignored workspace packages
- *
- * @return void
*/
public function testAddWorkspaceRemovesIgnoredPackage(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasWorkspace('modules/backend'));
$this->assertTrue($packageJson->hasIgnoredPackage('modules/backend'));
@@ -185,12 +196,10 @@ public function testAddWorkspaceRemovesIgnoredPackage(): void
/**
* Test checking ignore package workspace exists
- *
- * @return void
*/
public function testHasIgnoredPackage(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasIgnoredPackage('modules/backend'));
$this->assertFalse($packageJson->hasIgnoredPackage('modules/test'));
@@ -202,12 +211,10 @@ public function testHasIgnoredPackage(): void
/**
* Test adding ignore workspace package
- *
- * @return void
*/
public function testAddIgnoredPackage(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertFalse($packageJson->hasIgnoredPackage('themes/example'));
$packageJson->addIgnoredPackage('themes/example');
$this->assertTrue($packageJson->hasIgnoredPackage('themes/example'));
@@ -220,12 +227,10 @@ public function testAddIgnoredPackage(): void
/**
* Test removing ignore workspace package
- *
- * @return void
*/
public function testRemoveIgnoredPackage(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasIgnoredPackage('modules/system'));
$packageJson->removeIgnoredPackage('modules/system');
$this->assertFalse($packageJson->hasIgnoredPackage('modules/system'));
@@ -238,12 +243,10 @@ public function testRemoveIgnoredPackage(): void
/**
* Test when adding an ignore workspace package, it removes the package from workspace packages
- *
- * @return void
*/
public function testAddIgnoredPackageRemovesWorkspace(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasWorkspace('themes/demo'));
$this->assertFalse($packageJson->hasIgnoredPackage('themes/demo'));
@@ -255,12 +258,10 @@ public function testAddIgnoredPackageRemovesWorkspace(): void
/**
* Test checking if package.json has deps
- *
- * @return void
*/
public function testHasDependency(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
// Check that deps exist
$this->assertTrue($packageJson->hasDependency('test'));
$this->assertTrue($packageJson->hasDependency('test-dev'));
@@ -277,7 +278,7 @@ public function testHasDependency(): void
public function testAddDependency(): void
{
// Test adding packages
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$packageJson->addDependency('winter', '4.0.1', dev: false)
->addDependency('winter-dev', '4.0.1', dev: true);
@@ -288,7 +289,7 @@ public function testAddDependency(): void
$this->assertArrayHasKey('winter-dev', $packageJson->getContents()['devDependencies']);
// Test adding packages with overwrites
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
// Should do nothing as test is already in deps not dev deps
$packageJson->addDependency('test', '6.0.1', dev: true, overwrite: false);
@@ -314,12 +315,10 @@ public function testAddDependency(): void
/**
* Test removing dependencies
- *
- * @return void
*/
public function testRemoveDependency(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
// Check package removed from dev deps
$this->assertArrayHasKey('test-dev', $packageJson->getContents()['devDependencies'] ?? []);
@@ -339,12 +338,10 @@ public function testRemoveDependency(): void
/**
* Test checking if a script exists in a package.json
- *
- * @return void
*/
public function testHasScript(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasScript('foo'));
$this->assertTrue($packageJson->hasScript('example'));
@@ -357,12 +354,10 @@ public function testHasScript(): void
/**
* Test getting the value of a script by name
- *
- * @return void
*/
public function testGetScript(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertEquals('bar ./test', $packageJson->getScript('foo'));
$this->assertEquals('example test', $packageJson->getScript('example'));
@@ -374,12 +369,10 @@ public function testGetScript(): void
/**
* Test getting the value of a script by name
- *
- * @return void
*/
public function testAddScript(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$packageJson->addScript('winter', 'winter ./testing');
@@ -398,12 +391,10 @@ public function testAddScript(): void
/**
* Test removing scripts from package.json
- *
- * @return void
*/
public function testRemoveScript(): void
{
- $packageJson = new PackageJson(__DIR__ . '/../fixtures/npm/package-test.json');
+ $packageJson = new PackageJson($this->testFile);
$this->assertTrue($packageJson->hasScript('foo'));
$packageJson->removeScript('foo');
@@ -421,13 +412,11 @@ public function testRemoveScript(): void
/**
* Test saving, when saving with a file path set on init and passing a file path on save. Fails when no path given
- *
- * @return void
*/
public function testSave(): void
{
- $srcFile = __DIR__ . '/../fixtures/npm/package-test.json';
- $backupFile = __DIR__ . '/../fixtures/npm/package-test.json.back';
+ $srcFile = $this->testFile;
+ $backupFile = __DIR__ . '/../../fixtures/npm/package-test.json.back';
// Backup config file
copy($srcFile, $backupFile);
@@ -448,7 +437,7 @@ public function testSave(): void
unlink($backupFile);
// Test saving file to new path
- $testFile = __DIR__ . '/../fixtures/npm/package-test.json.test';
+ $testFile = __DIR__ . '/../../fixtures/npm/package-test.json.test';
$packageJson = new PackageJson($srcFile);
$this->assertNull($packageJson->getName());
$packageJson->setName('testing');
diff --git a/modules/system/tests/console/MixCompileTest.php b/modules/system/tests/console/asset/MixCompileTest.php
similarity index 95%
rename from modules/system/tests/console/MixCompileTest.php
rename to modules/system/tests/console/asset/MixCompileTest.php
index 4d7b6bf288..4f81885f0c 100644
--- a/modules/system/tests/console/MixCompileTest.php
+++ b/modules/system/tests/console/asset/MixCompileTest.php
@@ -1,6 +1,6 @@
markTestSkipped('This test requires node_modules to be installed');
}
+
+ if (!File::exists(base_path('node_modules/.bin/mix'))) {
+ $this->markTestSkipped('This test requires the mix package to be installed');
+ }
}
public function testCompileMultiple()
diff --git a/modules/system/tests/console/MixConfigTest.php b/modules/system/tests/console/asset/MixCreateTest.php
similarity index 89%
rename from modules/system/tests/console/MixConfigTest.php
rename to modules/system/tests/console/asset/MixCreateTest.php
index fd1eb80922..bb30cdb1f8 100644
--- a/modules/system/tests/console/MixConfigTest.php
+++ b/modules/system/tests/console/asset/MixCreateTest.php
@@ -1,12 +1,12 @@
assertFileNotExists($configPath);
// Run the config command to generate the vite config
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->doesntExpectOutput('winter.mix.js already exists, overwrite?')
->assertExitCode(0);
@@ -42,8 +43,9 @@ public function testConfigWritten(): void
File::put($configPath, 'testing');
// Check that refusing to overwrite does not replace file contents
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->expectsQuestion('winter.mix.js already exists, overwrite?', false)
->assertExitCode(0);
@@ -52,8 +54,9 @@ public function testConfigWritten(): void
$this->assertNotEquals($fixture, File::get($configPath));
// Run command confirming to overwrite file contents works
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->expectsQuestion('winter.mix.js already exists, overwrite?', true)
->assertExitCode(0);
@@ -72,9 +75,10 @@ public function testConfigTailwind(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
- '--tailwind' => true
+ '--tailwind' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
@@ -100,9 +104,10 @@ public function testConfigVue(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config with vue
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
- '--vue' => true
+ '--vue' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
@@ -127,10 +132,11 @@ public function testConfigTailwindVue(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config with vue
- $this->artisan('mix:config', [
+ $this->artisan('mix:create', [
'packageName' => $this->testPlugin,
'--tailwind' => true,
- '--vue' => true
+ '--vue' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
diff --git a/modules/system/tests/console/ViteCompileTest.php b/modules/system/tests/console/asset/ViteCompileTest.php
similarity index 93%
rename from modules/system/tests/console/ViteCompileTest.php
rename to modules/system/tests/console/asset/ViteCompileTest.php
index 11a1abb8dc..d51a1c6e56 100644
--- a/modules/system/tests/console/ViteCompileTest.php
+++ b/modules/system/tests/console/asset/ViteCompileTest.php
@@ -1,8 +1,8 @@
markTestSkipped('This test requires node_modules to be installed');
}
+ if (!File::exists(base_path('node_modules/.bin/vite'))) {
+ $this->markTestSkipped('This test requires the vite package to be installed');
+ }
+
$this->themePath = base_path('modules/system/tests/fixtures/themes/vitetest');
// Add our testing theme because it won't be auto discovered
- CompilableAssets::instance()->registerPackage(
+ PackageManager::instance()->registerPackage(
'theme-vitetest',
$this->themePath . '/vite.config.mjs',
'vite'
diff --git a/modules/system/tests/console/ViteConfigTest.php b/modules/system/tests/console/asset/ViteCreateTest.php
similarity index 88%
rename from modules/system/tests/console/ViteConfigTest.php
rename to modules/system/tests/console/asset/ViteCreateTest.php
index 9f629b2bd3..4669e1fb20 100644
--- a/modules/system/tests/console/ViteConfigTest.php
+++ b/modules/system/tests/console/asset/ViteCreateTest.php
@@ -1,12 +1,12 @@
assertFileNotExists($configPath);
// Run the config command to generate the vite config
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->doesntExpectOutput('vite.config.mjs already exists, overwrite?')
->assertExitCode(0);
@@ -32,7 +33,7 @@ public function testConfigWritten(): void
$fixture = str_replace(
'{{packageName}}',
'winter-sample',
- File::get(base_path('modules/system/console/asset/fixtures/config/vite/vite.config.js.fixture')),
+ File::get(base_path('modules/system/console/asset/fixtures/config/vite/vite.config.mjs.fixture')),
);
// Check the file written is what was expected
@@ -42,8 +43,9 @@ public function testConfigWritten(): void
File::put($configPath, 'testing');
// Check that refusing to overwrite does not replace file contents
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->expectsQuestion('vite.config.mjs already exists, overwrite?', false)
->assertExitCode(0);
@@ -52,8 +54,9 @@ public function testConfigWritten(): void
$this->assertNotEquals($fixture, File::get($configPath));
// Run command confirming to overwrite file contents works
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
+ '--no-stubs' => true,
])
->expectsQuestion('vite.config.mjs already exists, overwrite?', true)
->assertExitCode(0);
@@ -72,9 +75,10 @@ public function testConfigTailwind(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
- '--tailwind' => true
+ '--tailwind' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
@@ -100,9 +104,10 @@ public function testConfigVue(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config with vue
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
- '--vue' => true
+ '--vue' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
@@ -127,10 +132,11 @@ public function testConfigTailwindVue(): void
$this->assertFileNotExists($configPath);
// Run the config command to generate the vite config with vue
- $this->artisan('vite:config', [
+ $this->artisan('vite:create', [
'packageName' => $this->testPlugin,
'--tailwind' => true,
- '--vue' => true
+ '--vue' => true,
+ '--no-stubs' => true,
])
->assertExitCode(0);
diff --git a/modules/system/traits/AssetMaker.php b/modules/system/traits/AssetMaker.php
index 0a08b2eb86..b89eb19fa1 100644
--- a/modules/system/traits/AssetMaker.php
+++ b/modules/system/traits/AssetMaker.php
@@ -4,7 +4,7 @@
use System\Classes\CombineAssets;
use System\Classes\PluginManager;
-use System\Classes\Vite;
+use System\Classes\Asset\Vite;
use System\Models\Parameter;
use System\Models\PluginVersion;
use Winter\Storm\Exception\SystemException;