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="" - data-request-data="file_id: id ?>" + data-request-data="file_id: 'id ?>'" >

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 getEventHandler('onRemoveAttachment') ?>" data-request-confirm="" - data-request-data="file_id: id ?>" + data-request-data="file_id: '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 getEventHandler('onRemoveAttachment') ?>" data-request-confirm="" - data-request-data="file_id: id ?>" + data-request-data="file_id: 'id ?>'" >

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="getEventHandler('onRemoveAttachment') ?>" data-request-confirm="" - data-request-data="file_id: id ?>" + data-request-data="file_id: 'id ?>'" >

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;