From 95d734a6608772aa70a523cd4cd6788505b22062 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Thu, 19 Dec 2024 12:36:21 +0000 Subject: [PATCH 01/38] Added event/extension support to base command --- src/Console/Command.php | 117 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index 3aa4d6aaf..6d0174727 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -1,18 +1,33 @@ -replaces)) { $this->setAliases($this->replaces); } + + $this->extendableConstruct(); + } + + /** + * Override the laravel run function to allow us to run callbacks on the command prior to excution. + * Run the console command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return int + */ + public function run(InputInterface $input, OutputInterface $output): int + { + $this->output = $this->laravel->make( + OutputStyle::class, ['input' => $input, 'output' => $output] + ); + + $this->components = $this->laravel->make(Factory::class, ['output' => $this->output]); + + $this->fireEvent('beforeRun', [$this]); + + $renderer = Termwind::getRenderer(); + renderUsing($this->output->getOutput()); + + try { + // Wizardy + return SymfonyCommand::run( + $this->input = $input, $this->output + ); + } finally { + $this->untrap(); + // Restore the original termwind renderer + renderUsing($renderer); + } } /** @@ -59,4 +109,69 @@ public function error($string, $verbosity = null) { $this->components->error($string, $this->parseVerbosity($verbosity)); } + + /** + * Magic allowing for extendable properties + * + * @param $name + * @return mixed|null + */ + public function __get($name) + { + return $this->extendableGet($name); + } + + /** + * Magic allowing for extendable properties + * + * @param $name + * @param $value + * @return void + */ + public function __set($name, $value) + { + $this->extendableSet($name, $value); + } + + /** + * Magic allowing for dynamic extension + * + * @param $name + * @param $params + * @return mixed + */ + public function __call($name, $params) + { + if ($name === 'extend') { + if (empty($params[0]) || !is_callable($params[0])) { + throw new \InvalidArgumentException('The extend() method requires a callback parameter or closure.'); + } + if ($params[0] instanceof \Closure) { + return $params[0]->call($this, $params[1] ?? $this); + } + return \Closure::fromCallable($params[0])->call($this, $params[1] ?? $this); + } + + return $this->extendableCall($name, $params); + } + + /** + * Magic allowing for dynamic static extension + * + * @param $name + * @param $params + * @return mixed|void + */ + public static function __callStatic($name, $params) + { + if ($name === 'extend') { + if (empty($params[0])) { + throw new \InvalidArgumentException('The extend() method requires a callback parameter or closure.'); + } + self::extendableExtendCallback($params[0], $params[1] ?? false, $params[2] ?? null); + return; + } + + return parent::__callStatic($name, $params); + } } From 5d606325f216554777b3053b95a8ec21b2fb7c7e Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Thu, 19 Dec 2024 13:18:49 +0000 Subject: [PATCH 02/38] Added support for module service provider instances to be bound to the application container --- src/Support/ModuleServiceProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index 96d63ced4..a1fec4240 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -31,6 +31,9 @@ public function boot() if (File::isFile($routesFile)) { $this->loadRoutesFrom($routesFile); } + + // Bind the service provider to the application container + $this->app->instance($this::class, $this); } /** From 93882dadf210b647bc48ffcc1fe194f0f724f406 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 20 Dec 2024 12:15:07 +0000 Subject: [PATCH 03/38] Migrated code from winter to storm --- src/Foundation/Extension/WinterExtension.php | 12 ++ src/Packager/Commands/InfoCommand.php | 74 ++++++++++ src/Packager/Commands/RemoveCommand.php | 70 ++++++++++ src/Packager/Commands/RequireCommand.php | 82 +++++++++++ src/Packager/Commands/SearchCommand.php | 22 +++ src/Packager/Commands/ShowCommand.php | 66 +++++++++ src/Packager/Commands/UpdateCommand.php | 84 ++++++++++++ src/Packager/Composer.php | 135 +++++++++++++++++++ src/Support/ModuleServiceProvider.php | 14 +- src/Support/Traits/HasComposerPackage.php | 35 +++++ 10 files changed, 591 insertions(+), 3 deletions(-) create mode 100644 src/Foundation/Extension/WinterExtension.php create mode 100644 src/Packager/Commands/InfoCommand.php create mode 100644 src/Packager/Commands/RemoveCommand.php create mode 100644 src/Packager/Commands/RequireCommand.php create mode 100644 src/Packager/Commands/SearchCommand.php create mode 100644 src/Packager/Commands/ShowCommand.php create mode 100644 src/Packager/Commands/UpdateCommand.php create mode 100644 src/Packager/Composer.php create mode 100644 src/Support/Traits/HasComposerPackage.php diff --git a/src/Foundation/Extension/WinterExtension.php b/src/Foundation/Extension/WinterExtension.php new file mode 100644 index 000000000..675d5c3a1 --- /dev/null +++ b/src/Foundation/Extension/WinterExtension.php @@ -0,0 +1,12 @@ +package = $package; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = [ + '--format' => 'json', + ]; + + if (!$this->package) { + return $arguments; + } + + $arguments['package'] = $this->package; + + return $arguments; + } + + /** + * @throws CommandException + * @throws WorkDirException + */ + public function execute(): array + { + $output = $this->runComposerCommand(); + $message = implode(PHP_EOL, $output['output']); + + if ($output['code'] !== 0) { + throw new CommandException($message); + } + + $result = json_decode($message, JSON_OBJECT_AS_ARRAY); + + return $this->package + ? $result ?? [] + : $result['installed'] ?? []; + } + + /** + * @inheritDoc + */ + public function getCommandName(): string + { + return 'info'; + } +} diff --git a/src/Packager/Commands/RemoveCommand.php b/src/Packager/Commands/RemoveCommand.php new file mode 100644 index 000000000..8659fb384 --- /dev/null +++ b/src/Packager/Commands/RemoveCommand.php @@ -0,0 +1,70 @@ +package = $package; + $this->dryRun = $dryRun; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + $arguments['packages'] = [$this->package]; + + return $arguments; + } + + public function execute() + { + $output = $this->runComposerCommand(); + $message = implode(PHP_EOL, $output['output']); + + if ($output['code'] !== 0) { + throw new CommandException($message); + } + + Cache::forget(Composer::COMPOSER_CACHE_KEY); + + return $message; + } + + /** + * @inheritDoc + */ + public function getCommandName(): string + { + return 'remove'; + } +} diff --git a/src/Packager/Commands/RequireCommand.php b/src/Packager/Commands/RequireCommand.php new file mode 100644 index 000000000..b31fe8f8b --- /dev/null +++ b/src/Packager/Commands/RequireCommand.php @@ -0,0 +1,82 @@ +package = $package; + $this->dryRun = $dryRun; + $this->dev = $dev; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + if ($this->dev) { + $arguments['--dev'] = true; + } + + $arguments['packages'] = [$this->package]; + + return $arguments; + } + + /** + * @throws CommandException + * @throws WorkDirException + */ + public function execute(): string + { + $output = $this->runComposerCommand(); + $message = implode(PHP_EOL, $output['output']); + + if ($output['code'] !== 0) { + throw new CommandException($message); + } + + Cache::forget(Composer::COMPOSER_CACHE_KEY); + + return $message; + } + + /** + * @inheritDoc + */ + public function getCommandName(): string + { + return 'require'; + } +} diff --git a/src/Packager/Commands/SearchCommand.php b/src/Packager/Commands/SearchCommand.php new file mode 100644 index 000000000..be8728738 --- /dev/null +++ b/src/Packager/Commands/SearchCommand.php @@ -0,0 +1,22 @@ +runComposerCommand(); + + if ($output['code'] !== 0) { + throw new CommandException(implode(PHP_EOL, $output['output'])); + } + + $this->results = json_decode(implode(PHP_EOL, $output['output']), true) ?? []; + + return $this; + } +} diff --git a/src/Packager/Commands/ShowCommand.php b/src/Packager/Commands/ShowCommand.php new file mode 100644 index 000000000..23f45a35b --- /dev/null +++ b/src/Packager/Commands/ShowCommand.php @@ -0,0 +1,66 @@ +path = $path; + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if (!empty($this->package)) { + $arguments['package'] = $this->package; + } + + if ($this->mode !== 'installed') { + $arguments['--' . $this->mode] = true; + } + + if ($this->noDev) { + $arguments['--no-dev'] = true; + } + + if ($this->path) { + $arguments['--path'] = true; + } + + $arguments['--format'] = 'json'; + + return $arguments; + } +} diff --git a/src/Packager/Commands/UpdateCommand.php b/src/Packager/Commands/UpdateCommand.php new file mode 100644 index 000000000..c28e12103 --- /dev/null +++ b/src/Packager/Commands/UpdateCommand.php @@ -0,0 +1,84 @@ +executed) { + return; + } + + $this->includeDev = $includeDev; + $this->lockFileOnly = $lockFileOnly; + $this->ignorePlatformReqs = $ignorePlatformReqs; + $this->ignoreScripts = $ignoreScripts; + $this->dryRun = $dryRun; + $this->package = $package; + + if (in_array($installPreference, [self::PREFER_NONE, self::PREFER_DIST, self::PREFER_SOURCE])) { + $this->installPreference = $installPreference; + } + } + + /** + * @inheritDoc + */ + public function arguments(): array + { + $arguments = []; + + if ($this->package) { + $arguments['packages'] = [$this->package]; + } + + if ($this->dryRun) { + $arguments['--dry-run'] = true; + } + + if ($this->lockFileOnly) { + $arguments['--no-install'] = true; + } + + if ($this->ignorePlatformReqs) { + $arguments['--ignore-platform-reqs'] = true; + } + + if ($this->ignoreScripts) { + $arguments['--no-scripts'] = true; + } + + if (in_array($this->installPreference, [self::PREFER_DIST, self::PREFER_SOURCE])) { + $arguments['--prefer-' . $this->installPreference] = true; + } + + return $arguments; + } + + public function execute() + { + return parent::execute(); + } +} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php new file mode 100644 index 000000000..248150dbd --- /dev/null +++ b/src/Packager/Composer.php @@ -0,0 +1,135 @@ +|string + */ +class Composer +{ + public const COMPOSER_CACHE_KEY = 'winter.system.composer'; + + protected static PackagerComposer $composer; + + protected static array $winterPackages; + + public static function make(bool $fresh = false): PackagerComposer + { + if (!$fresh && isset(static::$composer)) { + return static::$composer; + } + + static::$composer = new PackagerComposer(); + static::$composer->setWorkDir(base_path()); + + static::$composer->setCommand('remove', new RemoveCommand(static::$composer)); + static::$composer->setCommand('require', new RequireCommand(static::$composer)); + static::$composer->setCommand('search', new SearchCommand(static::$composer)); + static::$composer->setCommand('show', new ShowCommand(static::$composer)); + static::$composer->setCommand('info', new InfoCommand(static::$composer)); + static::$composer->setCommand('update', new UpdateCommand(static::$composer)); + + return static::$composer; + } + + public static function __callStatic(string $name, array $args = []): mixed + { + if (!isset(static::$composer)) { + static::make(); + } + + return static::$composer->{$name}(...$args); + } + + public static function getWinterPackages(): array + { + $key = static::COMPOSER_CACHE_KEY . File::lastModified(base_path('composer.lock')); + return static::$winterPackages = Cache::rememberForever($key, function () { + $installed = static::info(); + $packages = []; + foreach ($installed as $package) { + $details = static::info($package['name']); + + $type = match ($details['type']) { + 'winter-plugin', 'october-plugin' => 'plugins', + 'winter-module', 'october-module' => 'modules', + 'winter-theme', 'october-theme' => 'themes', + default => null + }; + + if (!$type) { + continue; + } + + $packages[$type][$details['path']] = $details; + } + + return $packages; + }); + } + + public static function getAvailableUpdates(): array + { + $upgrades = Cache::remember( + static::COMPOSER_CACHE_KEY . '.updates', + 60 * 5, + fn () => static::update(dryRun: true)->getUpgraded() + ); + + $packages = static::getWinterPackageNames(); + + return array_filter($upgrades, function ($key) use ($packages) { + return in_array($key, $packages); + }, ARRAY_FILTER_USE_KEY); + } + + public static function updateAvailable(string $package): bool + { + return isset(static::getAvailableUpdates()[$package]); + } + + public static function getPackageInfoByExtension(WinterExtension $extension): array + { + return static::getPackageInfoByPath($extension->getPath()); + } + + public static function getPackageNameByExtension(WinterExtension $extension): ?string + { + return static::getPackageInfoByPath($extension->getPath())['name']; + } + + public static function getPackageInfoByPath(string $path): array + { + return array_merge(...array_values(static::getWinterPackages()))[$path] ?? []; + } + + public static function getWinterPackageNames(): array + { + return array_values( + array_map( + fn ($package) => $package['name'], + array_merge(...array_values(static::getWinterPackages())) + ) + ); + } +} diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index a1fec4240..2f3574380 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -1,12 +1,17 @@ app->instance($this::class, $this); + + // Register the composer package if exists + $this->setComposerPackage(Composer::getPackageInfoByExtension($this)); } /** diff --git a/src/Support/Traits/HasComposerPackage.php b/src/Support/Traits/HasComposerPackage.php new file mode 100644 index 000000000..d80b40633 --- /dev/null +++ b/src/Support/Traits/HasComposerPackage.php @@ -0,0 +1,35 @@ +composerPackage = $package; + } + + /** + * Get the composer package details + */ + public function getComposerPackage(): ?array + { + return $this->composerPackage; + } + + /** + * Get the composer package name + */ + public function getComposerPackageName(): ?string + { + return $this->composerPackage['name'] ?? null; + } +} From 407e8426effdbbe2bacf05fd2ca1ae81326e545a Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Sat, 4 Jan 2025 19:06:44 +0000 Subject: [PATCH 04/38] Added local remember function to avoid system cache --- src/Packager/Composer.php | 44 +++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 248150dbd..f60c6e565 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -64,7 +64,7 @@ public static function __callStatic(string $name, array $args = []): mixed public static function getWinterPackages(): array { $key = static::COMPOSER_CACHE_KEY . File::lastModified(base_path('composer.lock')); - return static::$winterPackages = Cache::rememberForever($key, function () { + return static::$winterPackages = static::remember($key, function () { $installed = static::info(); $packages = []; foreach ($installed as $package) { @@ -90,10 +90,10 @@ public static function getWinterPackages(): array public static function getAvailableUpdates(): array { - $upgrades = Cache::remember( + $upgrades = static::remember( static::COMPOSER_CACHE_KEY . '.updates', - 60 * 5, - fn () => static::update(dryRun: true)->getUpgraded() + fn () => static::update(dryRun: true)->getUpgraded(), + 60 * 5 ); $packages = static::getWinterPackageNames(); @@ -132,4 +132,40 @@ public static function getWinterPackageNames(): array ) ); } + + /** + * This method moves the composer caching out of cache, this is so it is not invalidated during tests. @TODO: fix. + * + * @param string $key + * @param callable $callable + * @param int|null $expires + * @return mixed + */ + protected static function remember(string $key, callable $callable, ?int $expires = null): mixed + { + $dir = temp_path('composer'); + + if (!File::exists($dir)) { + File::makeDirectory($dir); + } + + $file = $dir . '/' . md5($key) . '.cache'; + + if (File::exists($file)) { + $cache = unserialize(File::get($file)); + + if (is_null($cache['expires']) || time() < $cache['expires']) { + return $cache['result']; + } + } + + $result = $callable(); + + File::put($file, serialize([ + 'expires' => $expires ? time() + $expires : null, + 'result' => $result + ])); + + return $result; + } } From 48eddecb50b13e32329b14d9e545d9c502732c39 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Sat, 4 Jan 2025 19:07:33 +0000 Subject: [PATCH 05/38] Added base methods for modules to inherit --- src/Support/ModuleServiceProvider.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index 2f3574380..d158d7196 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -101,4 +101,14 @@ protected function loadConfigFrom($path, $namespace) $config = $this->app['config']; $config->package($namespace, $path); } + + public function getVersion(): string + { + return $this->composerPackage['versions'][0] ?? 'dev-unknown'; + } + + public function __toString(): string + { + return $this->getIdentifier(); + } } From b1d4f06c2d39fbdf622ed1b6c8f6e652e17a4cea Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Sat, 4 Jan 2025 19:10:34 +0000 Subject: [PATCH 06/38] Added packager to storm deps --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 0e6c31cb4..be5960ecd 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "twig/twig": "^3.14", "wikimedia/less.php": "~3.0", "wikimedia/minify": "~2.2", - "winter/laravel-config-writer": "^1.0.1" + "winter/laravel-config-writer": "^1.0.1", + "winter/packager": "~0.2" }, "require-dev": { "phpunit/phpunit": "^9.5.8", From 6b03b16ff901b953db6322930aab4859b1d53235 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Sun, 5 Jan 2025 14:22:04 +0000 Subject: [PATCH 07/38] Added identifier and path logic to module provider --- src/Support/ModuleServiceProvider.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index d158d7196..3777205db 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -1,6 +1,7 @@ composerPackage['versions'][0] ?? 'dev-unknown'; } + public function getPath(): string + { + return $this->path ?? $this->path = dirname((new \ReflectionClass(get_called_class()))->getFileName()); + } + + public function getIdentifier(): string + { + return $this->identifier ?? $this->identifier = (new \ReflectionClass(get_called_class()))->getNamespaceName(); + } + public function __toString(): string { return $this->getIdentifier(); From 43f0339c5074b919c93c8b5f88176f369aaab7b2 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 6 Jan 2025 13:48:52 +0000 Subject: [PATCH 08/38] Added method for retrieving packages from composer api --- src/Packager/Composer.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index f60c6e565..5244f910b 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -4,7 +4,9 @@ use Illuminate\Support\Facades\Cache; use Winter\Packager\Composer as PackagerComposer; +use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; +use Winter\Storm\Network\Http; use Winter\Storm\Packager\Commands\InfoCommand; use Winter\Storm\Packager\Commands\RemoveCommand; use Winter\Storm\Packager\Commands\RequireCommand; @@ -168,4 +170,31 @@ protected static function remember(string $key, callable $callable, ?int $expire return $result; } + + public static function listPackages(string $type): array + { + return Cache::remember(static::COMPOSER_CACHE_KEY . '.packages.' . $type, 60 * 60 * 24, function () use ($type) { + $page = 0; + $packages = []; + do { + $result = Http::get('https://packagist.org/search.json', function (Http $http) use (&$page, $type) { + $http->data([ + 'q' => '', + 'page' => ++$page, + 'type' => $type + ]); + }); + + if ($result->code != '200') { + throw new ApplicationException('Unable to retrieve packages, failed with code: ' . $result->code); + } + + $data = json_decode($result->body, JSON_OBJECT_AS_ARRAY); + + $packages = array_merge($packages, $data['results']); + } while (isset($data['next'])); + + return $packages; + }); + } } From ce22bcf8fc6718e3d903e7be6276d6c128be104b Mon Sep 17 00:00:00 2001 From: Jack Wilkinson <31214002+jaxwilko@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:55:02 +0000 Subject: [PATCH 09/38] Update src/Console/Command.php --- src/Console/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index 6d0174727..c8b1f04fc 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -75,7 +75,7 @@ public function run(InputInterface $input, OutputInterface $output): int renderUsing($this->output->getOutput()); try { - // Wizardy + // Calling the grandparent run() method, see: https://www.php.net/manual/en/language.oop5.inheritance.php#100005) return SymfonyCommand::run( $this->input = $input, $this->output ); From 607179004a64c9505d5b09d5e7c850d36ec4722f Mon Sep 17 00:00:00 2001 From: Jack Wilkinson <31214002+jaxwilko@users.noreply.github.com> Date: Wed, 12 Mar 2025 22:55:23 +0000 Subject: [PATCH 10/38] Update src/Console/Command.php --- src/Console/Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index c8b1f04fc..84fa8d864 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -75,7 +75,7 @@ public function run(InputInterface $input, OutputInterface $output): int renderUsing($this->output->getOutput()); try { - // Calling the grandparent run() method, see: https://www.php.net/manual/en/language.oop5.inheritance.php#100005) + // Calling the grandparent run() method, see: https://www.php.net/manual/en/language.oop5.inheritance.php#100005 return SymfonyCommand::run( $this->input = $input, $this->output ); From 156529d8e7b6ab41c6fb02ca8189ec5e08018344 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Wed, 9 Jul 2025 13:52:50 +0100 Subject: [PATCH 11/38] Added fix for symfony compatibility --- src/Console/Traits/HandlesCleanup.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Console/Traits/HandlesCleanup.php b/src/Console/Traits/HandlesCleanup.php index d322f737a..5a3d38eb7 100644 --- a/src/Console/Traits/HandlesCleanup.php +++ b/src/Console/Traits/HandlesCleanup.php @@ -43,19 +43,12 @@ public function getSubscribedSignals(): array * * @return int|false The exit code to return or false to continue the normal execution */ - public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */) + public function handleSignal(int $signal): void { // Handle the signal if (method_exists($this, 'handleCleanup')) { $this->handleCleanup(); } - - // Exit cleanly at this point if this was a user termination - if (in_array($signal, [SIGINT, SIGQUIT])) { - return 0; - } - - return false; } /** From d8f6d8bc32db818b992440b77427237fed0b6984 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 28 Jul 2025 02:18:37 +0100 Subject: [PATCH 12/38] Added helper to composer to allow fetching of installed winter packages with version info --- src/Packager/Commands/InfoCommand.php | 19 ++++++++++++++----- src/Packager/Composer.php | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Packager/Commands/InfoCommand.php b/src/Packager/Commands/InfoCommand.php index b302f0916..a92b2a9bf 100644 --- a/src/Packager/Commands/InfoCommand.php +++ b/src/Packager/Commands/InfoCommand.php @@ -2,8 +2,6 @@ namespace Winter\Storm\Packager\Commands; -use Illuminate\Support\Facades\Cache; -use System\Classes\Packager\Composer; use Winter\Packager\Commands\BaseCommand; use Winter\Packager\Exceptions\CommandException; use Winter\Packager\Exceptions\WorkDirException; @@ -11,19 +9,22 @@ class InfoCommand extends BaseCommand { protected ?string $package = null; + protected bool $all = false; + protected bool $latest = false; /** * Command handler. * * @param string|null $package - * @param boolean $dryRun - * @param boolean $dev + * @param boolean $all * @return void * @throws CommandException */ - public function handle(?string $package = null): void + public function handle(?string $package = null, bool $all = false, bool $latest = false): void { $this->package = $package; + $this->all = $all; + $this->latest = $latest; } /** @@ -41,6 +42,14 @@ public function arguments(): array $arguments['package'] = $this->package; + if ($this->all) { + $arguments['--all'] = true; + } + + if ($this->latest) { + $arguments['--latest'] = true; + } + return $arguments; } diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 5244f910b..e92d00f16 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -20,7 +20,7 @@ * @method static i(): array * @method static install(): array * @method static search(string $query, ?string $type = null, bool $onlyNames = false, bool $onlyVendors = false): \Winter\Packager\Commands\Search - * @method static info(?string $package = null): array + * @method static info(?string $package = null, bool $all = false, bool $latest = false): array * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $path = false): object * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update * @method static remove(?string $package = null, bool $dryRun = false): array @@ -135,6 +135,19 @@ public static function getWinterPackageNames(): array ); } + public static function getWinterPackagesWithVersion(): array + { + $packages = []; + foreach (array_merge(...array_values(static::getWinterPackages())) as $package) { + $packages[$package['name']] = [ + 'version' => $package['versions'][0] ?? null, + 'ref' => $package['dist']['reference'] ?? null + ]; + } + + return $packages; + } + /** * This method moves the composer caching out of cache, this is so it is not invalidated during tests. @TODO: fix. * From 1ce4f4bf183b168ae6d370d7c91144f0e09ff641 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 27 Jul 2025 22:01:59 -0700 Subject: [PATCH 13/38] Update src/Console/Command.php --- src/Console/Command.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index 8dec2efb1..54dc873de 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -65,7 +65,8 @@ public function __construct() public function run(InputInterface $input, OutputInterface $output): int { $this->output = $this->laravel->make( - OutputStyle::class, ['input' => $input, 'output' => $output] + OutputStyle::class, + ['input' => $input, 'output' => $output] ); $this->components = $this->laravel->make(Factory::class, ['output' => $this->output]); @@ -78,7 +79,8 @@ public function run(InputInterface $input, OutputInterface $output): int try { // Calling the grandparent run() method, see: https://www.php.net/manual/en/language.oop5.inheritance.php#100005 return SymfonyCommand::run( - $this->input = $input, $this->output + $this->input = $input, + $this->output ); } finally { $this->untrap(); From 90ba47a898178501e7b4c5be82e0de370d362325 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Sun, 27 Jul 2025 23:11:01 -0600 Subject: [PATCH 14/38] Revert change to HandlesCleanup Applied in L12 PR instead. --- src/Console/Traits/HandlesCleanup.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Console/Traits/HandlesCleanup.php b/src/Console/Traits/HandlesCleanup.php index 5a3d38eb7..d322f737a 100644 --- a/src/Console/Traits/HandlesCleanup.php +++ b/src/Console/Traits/HandlesCleanup.php @@ -43,12 +43,19 @@ public function getSubscribedSignals(): array * * @return int|false The exit code to return or false to continue the normal execution */ - public function handleSignal(int $signal): void + public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */) { // Handle the signal if (method_exists($this, 'handleCleanup')) { $this->handleCleanup(); } + + // Exit cleanly at this point if this was a user termination + if (in_array($signal, [SIGINT, SIGQUIT])) { + return 0; + } + + return false; } /** From ecafe9487659204424c154f48c55708b7906cf80 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 28 Jul 2025 00:40:33 -0600 Subject: [PATCH 15/38] Cleanup --- src/Console/Command.php | 5 +++++ src/Packager/Commands/InfoCommand.php | 2 +- src/Packager/Commands/ShowCommand.php | 3 +-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index 54dc873de..f93fdb1e5 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -30,6 +30,11 @@ abstract class Command extends BaseCommand implements PromptsForMissingInput, Si use ExtendableTrait; use Emitter; + /** + * @var string|array|null Extensions implemented by this class. + */ + public $implement = null; + /** * @var \Winter\Storm\Foundation\Application */ diff --git a/src/Packager/Commands/InfoCommand.php b/src/Packager/Commands/InfoCommand.php index a92b2a9bf..a8704e1db 100644 --- a/src/Packager/Commands/InfoCommand.php +++ b/src/Packager/Commands/InfoCommand.php @@ -66,7 +66,7 @@ public function execute(): array throw new CommandException($message); } - $result = json_decode($message, JSON_OBJECT_AS_ARRAY); + $result = json_decode($message, flags: JSON_OBJECT_AS_ARRAY); return $this->package ? $result ?? [] diff --git a/src/Packager/Commands/ShowCommand.php b/src/Packager/Commands/ShowCommand.php index 23f45a35b..ebe10b925 100644 --- a/src/Packager/Commands/ShowCommand.php +++ b/src/Packager/Commands/ShowCommand.php @@ -3,7 +3,6 @@ namespace Winter\Storm\Packager\Commands; use Winter\Packager\Commands\Show; -use Winter\Packager\Exceptions\CommandException; class ShowCommand extends Show { @@ -29,7 +28,7 @@ class ShowCommand extends Show * @param boolean $path * @return void */ - public function handle(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $path = false): void + public function handle(?string $mode = 'installed', ?string $package = null, bool $noDev = false, bool $path = false): void { parent::handle($mode, $package, $noDev); From 6c2872c7266cc5f945d4fbe74aac35c3ad5676eb Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Mon, 28 Jul 2025 01:16:36 -0600 Subject: [PATCH 16/38] Fixes for latest version of packager --- src/Packager/Commands/InfoCommand.php | 5 +++++ src/Packager/Commands/RemoveCommand.php | 5 +++++ src/Packager/Commands/RequireCommand.php | 5 +++++ src/Packager/Composer.php | 12 ++++++------ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Packager/Commands/InfoCommand.php b/src/Packager/Commands/InfoCommand.php index a8704e1db..653964a87 100644 --- a/src/Packager/Commands/InfoCommand.php +++ b/src/Packager/Commands/InfoCommand.php @@ -80,4 +80,9 @@ public function getCommandName(): string { return 'info'; } + + public function requiresWorkDir(): bool + { + return false; + } } diff --git a/src/Packager/Commands/RemoveCommand.php b/src/Packager/Commands/RemoveCommand.php index 8659fb384..b2bb5b415 100644 --- a/src/Packager/Commands/RemoveCommand.php +++ b/src/Packager/Commands/RemoveCommand.php @@ -67,4 +67,9 @@ public function getCommandName(): string { return 'remove'; } + + public function requiresWorkDir(): bool + { + return true; + } } diff --git a/src/Packager/Commands/RequireCommand.php b/src/Packager/Commands/RequireCommand.php index b31fe8f8b..e82d0c4eb 100644 --- a/src/Packager/Commands/RequireCommand.php +++ b/src/Packager/Commands/RequireCommand.php @@ -79,4 +79,9 @@ public function getCommandName(): string { return 'require'; } + + public function requiresWorkDir(): bool + { + return true; + } } diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index e92d00f16..ecf04481e 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -44,12 +44,12 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer = new PackagerComposer(); static::$composer->setWorkDir(base_path()); - static::$composer->setCommand('remove', new RemoveCommand(static::$composer)); - static::$composer->setCommand('require', new RequireCommand(static::$composer)); - static::$composer->setCommand('search', new SearchCommand(static::$composer)); - static::$composer->setCommand('show', new ShowCommand(static::$composer)); - static::$composer->setCommand('info', new InfoCommand(static::$composer)); - static::$composer->setCommand('update', new UpdateCommand(static::$composer)); + static::$composer->setCommand('remove', RemoveCommand::class); + static::$composer->setCommand('require', RequireCommand::class); + static::$composer->setCommand('search', SearchCommand::class); + static::$composer->setCommand('show', ShowCommand::class); + static::$composer->setCommand('info', InfoCommand::class); + static::$composer->setCommand('update', UpdateCommand::class); return static::$composer; } From 835d803990de954d1142f5e98e816ffc1ac51cd4 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 28 Jul 2025 17:57:19 +0100 Subject: [PATCH 17/38] Added useful data to return from getAvailableUpdates --- src/Packager/Commands/UpdateCommand.php | 4 -- src/Packager/Composer.php | 58 ++++++++++++++++++------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/Packager/Commands/UpdateCommand.php b/src/Packager/Commands/UpdateCommand.php index c28e12103..fb095ecfb 100644 --- a/src/Packager/Commands/UpdateCommand.php +++ b/src/Packager/Commands/UpdateCommand.php @@ -27,10 +27,6 @@ public function handle( bool $dryRun = false, ?string $package = null ) { - if ($this->executed) { - return; - } - $this->includeDev = $includeDev; $this->lockFileOnly = $lockFileOnly; $this->ignorePlatformReqs = $ignorePlatformReqs; diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index ecf04481e..ddb79c9a3 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -33,8 +33,6 @@ class Composer protected static PackagerComposer $composer; - protected static array $winterPackages; - public static function make(bool $fresh = false): PackagerComposer { if (!$fresh && isset(static::$composer)) { @@ -48,7 +46,7 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer->setCommand('require', RequireCommand::class); static::$composer->setCommand('search', SearchCommand::class); static::$composer->setCommand('show', ShowCommand::class); - static::$composer->setCommand('info', InfoCommand::class); + static::$composer->setCommand('info', new InfoCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); return static::$composer; @@ -65,8 +63,7 @@ public static function __callStatic(string $name, array $args = []): mixed public static function getWinterPackages(): array { - $key = static::COMPOSER_CACHE_KEY . File::lastModified(base_path('composer.lock')); - return static::$winterPackages = static::remember($key, function () { + return static::remember('packages', function () { $installed = static::info(); $packages = []; foreach ($installed as $package) { @@ -92,17 +89,45 @@ public static function getWinterPackages(): array public static function getAvailableUpdates(): array { - $upgrades = static::remember( - static::COMPOSER_CACHE_KEY . '.updates', - fn () => static::update(dryRun: true)->getUpgraded(), - 60 * 5 - ); + return static::remember(__METHOD__, function () { + $upgrades = static::update(dryRun: true)->getUpgraded(); + $packages = static::getWinterPackageNames(); + + $winterPackages = array_filter($upgrades, function ($key) use ($packages) { + return in_array($key, $packages); + }, ARRAY_FILTER_USE_KEY); + + foreach ($winterPackages as $name => $details) { + $winterPackages[$name] = [ + 'from' => $details[0], + 'to' => $details[1], + ]; + + $info = static::info($name, all: true, latest: true); + + $winterPackages[$name] = [ + 'from' => $details[0], + 'to' => $details[1], + 'ref' => $info['dist']['reference'] ?? null, + 'available' => static::filterProductionVersions($info['versions'], [$details[0]]), + ]; + } + + return $winterPackages; + }); + } + + public static function filterProductionVersions(array $versions, array $keep = []): array + { + foreach ($versions as $index => $version) { + if ((!str_starts_with($version, 'v') || str_ends_with($version, '-dev')) && !in_array($version, $keep)) { + unset($versions[$index]); + } + } - $packages = static::getWinterPackageNames(); + usort($versions, fn (string $a, string $b): int => version_compare($a, $b, '<')); - return array_filter($upgrades, function ($key) use ($packages) { - return in_array($key, $packages); - }, ARRAY_FILTER_USE_KEY); + return $versions; } public static function updateAvailable(string $package): bool @@ -153,10 +178,10 @@ public static function getWinterPackagesWithVersion(): array * * @param string $key * @param callable $callable - * @param int|null $expires + * @param int $expires * @return mixed */ - protected static function remember(string $key, callable $callable, ?int $expires = null): mixed + protected static function remember(string $key, callable $callable, int $expires = 60 * 5): mixed { $dir = temp_path('composer'); @@ -164,6 +189,7 @@ protected static function remember(string $key, callable $callable, ?int $expire File::makeDirectory($dir); } + $key = static::COMPOSER_CACHE_KEY . $key . File::lastModified(base_path('composer.lock')); $file = $dir . '/' . md5($key) . '.cache'; if (File::exists($file)) { From 82c251dee21ca9f2f7a8fa32d60fd5b1ee78c073 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 28 Jul 2025 21:10:16 +0100 Subject: [PATCH 18/38] Added fixes for show command --- src/Packager/Commands/ShowCommand.php | 10 ++++++---- src/Packager/Composer.php | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Packager/Commands/ShowCommand.php b/src/Packager/Commands/ShowCommand.php index ebe10b925..4feb59b1c 100644 --- a/src/Packager/Commands/ShowCommand.php +++ b/src/Packager/Commands/ShowCommand.php @@ -3,6 +3,7 @@ namespace Winter\Storm\Packager\Commands; use Winter\Packager\Commands\Show; +use Winter\Packager\Enums\ShowMode; class ShowCommand extends Show { @@ -28,11 +29,12 @@ class ShowCommand extends Show * @param boolean $path * @return void */ - public function handle(?string $mode = 'installed', ?string $package = null, bool $noDev = false, bool $path = false): void + public function handle(string|ShowMode $mode = 'installed', ?string $package = null, bool $noDev = false, bool $path = false): void { - parent::handle($mode, $package, $noDev); - + $this->mode = is_string($mode) ? ShowMode::from($mode) : $mode; + $this->package = $package; $this->path = $path; + $this->noDev = $noDev; } /** @@ -47,7 +49,7 @@ public function arguments(): array } if ($this->mode !== 'installed') { - $arguments['--' . $this->mode] = true; + $arguments['--' . $this->mode->value] = true; } if ($this->noDev) { diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index ddb79c9a3..002b2dd12 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -45,7 +45,7 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer->setCommand('remove', RemoveCommand::class); static::$composer->setCommand('require', RequireCommand::class); static::$composer->setCommand('search', SearchCommand::class); - static::$composer->setCommand('show', ShowCommand::class); + static::$composer->setCommand('show', new ShowCommand(static::$composer)); static::$composer->setCommand('info', new InfoCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); From 3a6a7e1fd015d5901abeaa4bbf9e232f6cdf1e34 Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Mon, 28 Jul 2025 22:25:13 +0100 Subject: [PATCH 19/38] Added fix to prevent empty data being cached and removed the show command --- src/Packager/Commands/ShowCommand.php | 67 --------------------------- src/Packager/Composer.php | 8 +++- 2 files changed, 6 insertions(+), 69 deletions(-) delete mode 100644 src/Packager/Commands/ShowCommand.php diff --git a/src/Packager/Commands/ShowCommand.php b/src/Packager/Commands/ShowCommand.php deleted file mode 100644 index 4feb59b1c..000000000 --- a/src/Packager/Commands/ShowCommand.php +++ /dev/null @@ -1,67 +0,0 @@ -mode = is_string($mode) ? ShowMode::from($mode) : $mode; - $this->package = $package; - $this->path = $path; - $this->noDev = $noDev; - } - - /** - * @inheritDoc - */ - public function arguments(): array - { - $arguments = []; - - if (!empty($this->package)) { - $arguments['package'] = $this->package; - } - - if ($this->mode !== 'installed') { - $arguments['--' . $this->mode->value] = true; - } - - if ($this->noDev) { - $arguments['--no-dev'] = true; - } - - if ($this->path) { - $arguments['--path'] = true; - } - - $arguments['--format'] = 'json'; - - return $arguments; - } -} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 002b2dd12..499031175 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -45,7 +45,6 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer->setCommand('remove', RemoveCommand::class); static::$composer->setCommand('require', RequireCommand::class); static::$composer->setCommand('search', SearchCommand::class); - static::$composer->setCommand('show', new ShowCommand(static::$composer)); static::$composer->setCommand('info', new InfoCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); @@ -63,7 +62,7 @@ public static function __callStatic(string $name, array $args = []): mixed public static function getWinterPackages(): array { - return static::remember('packages', function () { + return static::remember(__METHOD__, function () { $installed = static::info(); $packages = []; foreach ($installed as $package) { @@ -202,6 +201,11 @@ protected static function remember(string $key, callable $callable, int $expires $result = $callable(); + // We don't save nothing + if (!$result) { + return $result; + } + File::put($file, serialize([ 'expires' => $expires ? time() + $expires : null, 'result' => $result From 699951e1112d3b3cd034501c5f7cdafb1463c29e Mon Sep 17 00:00:00 2001 From: Jack Wilkinson Date: Fri, 1 Aug 2025 18:21:27 +0100 Subject: [PATCH 20/38] Added fixes to command and added helper for updating package version requirements --- src/Packager/Commands/RequireCommand.php | 19 +++++++++++-- src/Packager/Commands/UpdateCommand.php | 10 ++++++- src/Packager/Composer.php | 35 +++++++++++++++++++++--- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/Packager/Commands/RequireCommand.php b/src/Packager/Commands/RequireCommand.php index e82d0c4eb..08d70c3d8 100644 --- a/src/Packager/Commands/RequireCommand.php +++ b/src/Packager/Commands/RequireCommand.php @@ -13,6 +13,7 @@ class RequireCommand extends BaseCommand protected ?string $package = null; protected bool $dryRun = false; protected bool $dev = false; + protected bool $returnRequired = false; /** * Command handler. @@ -23,8 +24,12 @@ class RequireCommand extends BaseCommand * @return void * @throws CommandException */ - public function handle(?string $package = null, bool $dryRun = false, bool $dev = false): void - { + public function handle( + ?string $package = null, + bool $dryRun = false, + bool $dev = false, + bool $returnRequired = false + ): void { if (!$package) { throw new CommandException('Must provide a package'); } @@ -32,6 +37,7 @@ public function handle(?string $package = null, bool $dryRun = false, bool $dev $this->package = $package; $this->dryRun = $dryRun; $this->dev = $dev; + $this->returnRequired = $returnRequired; } /** @@ -67,7 +73,14 @@ public function execute(): string throw new CommandException($message); } - Cache::forget(Composer::COMPOSER_CACHE_KEY); + if (!$this->dryRun) { + Cache::forget(Composer::COMPOSER_CACHE_KEY); + } + + if ($this->returnRequired) { + preg_match('/Using version (.*?) /', $output['output'][count($output['output']) - 1], $matches); + return $matches[1] ?? throw new CommandException('Unable to determine required version'); + } return $message; } diff --git a/src/Packager/Commands/UpdateCommand.php b/src/Packager/Commands/UpdateCommand.php index fb095ecfb..76c8de4bc 100644 --- a/src/Packager/Commands/UpdateCommand.php +++ b/src/Packager/Commands/UpdateCommand.php @@ -8,6 +8,8 @@ class UpdateCommand extends Update { protected ?string $package = null; + protected bool $withAllDependencies = false; + /** * Handle options before execution. * @@ -25,7 +27,8 @@ public function handle( string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, - ?string $package = null + ?string $package = null, + bool $withAllDependencies = false ) { $this->includeDev = $includeDev; $this->lockFileOnly = $lockFileOnly; @@ -33,6 +36,7 @@ public function handle( $this->ignoreScripts = $ignoreScripts; $this->dryRun = $dryRun; $this->package = $package; + $this->withAllDependencies = $withAllDependencies; if (in_array($installPreference, [self::PREFER_NONE, self::PREFER_DIST, self::PREFER_SOURCE])) { $this->installPreference = $installPreference; @@ -66,6 +70,10 @@ public function arguments(): array $arguments['--no-scripts'] = true; } + if ($this->withAllDependencies) { + $arguments['--with-all-dependencies'] = true; + } + if (in_array($this->installPreference, [self::PREFER_DIST, self::PREFER_SOURCE])) { $arguments['--prefer-' . $this->installPreference] = true; } diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 499031175..b272348da 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -24,7 +24,7 @@ * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $path = false): object * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update * @method static remove(?string $package = null, bool $dryRun = false): array - * @method static require(?string $package = null, bool $dryRun = false, bool $dev = false): string + * @method static require(?string $package = null, bool $dryRun = false, bool $dev = false, bool $returnRequired = false): string * @method static version(string $detail = 'version'): array|string */ class Composer @@ -43,7 +43,7 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer->setWorkDir(base_path()); static::$composer->setCommand('remove', RemoveCommand::class); - static::$composer->setCommand('require', RequireCommand::class); + static::$composer->setCommand('require', new RequireCommand(static::$composer)); static::$composer->setCommand('search', SearchCommand::class); static::$composer->setCommand('info', new InfoCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); @@ -172,6 +172,31 @@ public static function getWinterPackagesWithVersion(): array return $packages; } + public static function setPackageRequirement(string $package, string $version): bool + { + $composerJsonPath = base_path('composer.json'); + if (!File::exists($composerJsonPath)) { + throw new ApplicationException('composer.json file does not exist.'); + } + + $json = json_decode(File::get($composerJsonPath), JSON_OBJECT_AS_ARRAY); + + $set = false; + foreach (['require', 'require-dev'] as $mode) { + if (isset($json[$mode][$package])) { + $json[$mode][$package] = $version; + $set = true; + break; + } + } + + if ($set) { + File::put($composerJsonPath, json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + } + + return $set; + } + /** * This method moves the composer caching out of cache, this is so it is not invalidated during tests. @TODO: fix. * @@ -180,7 +205,7 @@ public static function getWinterPackagesWithVersion(): array * @param int $expires * @return mixed */ - protected static function remember(string $key, callable $callable, int $expires = 60 * 5): mixed + protected static function remember(string $key, callable $callable, int $expires = 60 * 15): mixed { $dir = temp_path('composer'); @@ -188,7 +213,9 @@ protected static function remember(string $key, callable $callable, int $expires File::makeDirectory($dir); } - $key = static::COMPOSER_CACHE_KEY . $key . File::lastModified(base_path('composer.lock')); + $key = static::COMPOSER_CACHE_KEY . $key; + $key .= File::lastModified(base_path('composer.lock')) . File::lastModified(base_path('composer.json')); + $file = $dir . '/' . md5($key) . '.cache'; if (File::exists($file)) { From 1dd8e84e66f47503149971b0fd930370f676ca3f Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 14:38:11 -0600 Subject: [PATCH 21/38] Update src/Packager/Commands/InfoCommand.php --- src/Packager/Commands/InfoCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Packager/Commands/InfoCommand.php b/src/Packager/Commands/InfoCommand.php index 653964a87..d038bbe48 100644 --- a/src/Packager/Commands/InfoCommand.php +++ b/src/Packager/Commands/InfoCommand.php @@ -6,6 +6,9 @@ use Winter\Packager\Exceptions\CommandException; use Winter\Packager\Exceptions\WorkDirException; +/** + * @TODO: Relocate to Winter\Packager + */ class InfoCommand extends BaseCommand { protected ?string $package = null; From b0de374e7b7474e3884005ad17178b59da0b84f5 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 16:14:04 -0600 Subject: [PATCH 22/38] Remove unnecessary packager overrides Requires https://github.com/wintercms/packager/pull/6 --- src/Packager/Commands/InfoCommand.php | 91 ------------------------- src/Packager/Commands/RemoveCommand.php | 75 -------------------- src/Packager/Composer.php | 22 +++--- 3 files changed, 11 insertions(+), 177 deletions(-) delete mode 100644 src/Packager/Commands/InfoCommand.php delete mode 100644 src/Packager/Commands/RemoveCommand.php diff --git a/src/Packager/Commands/InfoCommand.php b/src/Packager/Commands/InfoCommand.php deleted file mode 100644 index d038bbe48..000000000 --- a/src/Packager/Commands/InfoCommand.php +++ /dev/null @@ -1,91 +0,0 @@ -package = $package; - $this->all = $all; - $this->latest = $latest; - } - - /** - * @inheritDoc - */ - public function arguments(): array - { - $arguments = [ - '--format' => 'json', - ]; - - if (!$this->package) { - return $arguments; - } - - $arguments['package'] = $this->package; - - if ($this->all) { - $arguments['--all'] = true; - } - - if ($this->latest) { - $arguments['--latest'] = true; - } - - return $arguments; - } - - /** - * @throws CommandException - * @throws WorkDirException - */ - public function execute(): array - { - $output = $this->runComposerCommand(); - $message = implode(PHP_EOL, $output['output']); - - if ($output['code'] !== 0) { - throw new CommandException($message); - } - - $result = json_decode($message, flags: JSON_OBJECT_AS_ARRAY); - - return $this->package - ? $result ?? [] - : $result['installed'] ?? []; - } - - /** - * @inheritDoc - */ - public function getCommandName(): string - { - return 'info'; - } - - public function requiresWorkDir(): bool - { - return false; - } -} diff --git a/src/Packager/Commands/RemoveCommand.php b/src/Packager/Commands/RemoveCommand.php deleted file mode 100644 index b2bb5b415..000000000 --- a/src/Packager/Commands/RemoveCommand.php +++ /dev/null @@ -1,75 +0,0 @@ -package = $package; - $this->dryRun = $dryRun; - } - - /** - * @inheritDoc - */ - public function arguments(): array - { - $arguments = []; - - if ($this->dryRun) { - $arguments['--dry-run'] = true; - } - - $arguments['packages'] = [$this->package]; - - return $arguments; - } - - public function execute() - { - $output = $this->runComposerCommand(); - $message = implode(PHP_EOL, $output['output']); - - if ($output['code'] !== 0) { - throw new CommandException($message); - } - - Cache::forget(Composer::COMPOSER_CACHE_KEY); - - return $message; - } - - /** - * @inheritDoc - */ - public function getCommandName(): string - { - return 'remove'; - } - - public function requiresWorkDir(): bool - { - return true; - } -} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index b272348da..33875d5c3 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -4,14 +4,12 @@ use Illuminate\Support\Facades\Cache; use Winter\Packager\Composer as PackagerComposer; +use Winter\Packager\Enums\ShowMode; use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; use Winter\Storm\Network\Http; -use Winter\Storm\Packager\Commands\InfoCommand; -use Winter\Storm\Packager\Commands\RemoveCommand; use Winter\Storm\Packager\Commands\RequireCommand; use Winter\Storm\Packager\Commands\SearchCommand; -use Winter\Storm\Packager\Commands\ShowCommand; use Winter\Storm\Packager\Commands\UpdateCommand; use Winter\Storm\Support\Facades\File; @@ -20,8 +18,7 @@ * @method static i(): array * @method static install(): array * @method static search(string $query, ?string $type = null, bool $onlyNames = false, bool $onlyVendors = false): \Winter\Packager\Commands\Search - * @method static info(?string $package = null, bool $all = false, bool $latest = false): array - * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $path = false): object + * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false): mixed * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update * @method static remove(?string $package = null, bool $dryRun = false): array * @method static require(?string $package = null, bool $dryRun = false, bool $dev = false, bool $returnRequired = false): string @@ -40,12 +37,10 @@ public static function make(bool $fresh = false): PackagerComposer } static::$composer = new PackagerComposer(); - static::$composer->setWorkDir(base_path()); + static::$composer->setWorkDir(realpath(base_path())); - static::$composer->setCommand('remove', RemoveCommand::class); static::$composer->setCommand('require', new RequireCommand(static::$composer)); static::$composer->setCommand('search', SearchCommand::class); - static::$composer->setCommand('info', new InfoCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); return static::$composer; @@ -63,10 +58,10 @@ public static function __callStatic(string $name, array $args = []): mixed public static function getWinterPackages(): array { return static::remember(__METHOD__, function () { - $installed = static::info(); + $installed = static::show(returnArray: true); $packages = []; foreach ($installed as $package) { - $details = static::info($package['name']); + $details = static::show(package: $package['name'], returnArray: true); $type = match ($details['type']) { 'winter-plugin', 'october-plugin' => 'plugins', @@ -102,7 +97,12 @@ public static function getAvailableUpdates(): array 'to' => $details[1], ]; - $info = static::info($name, all: true, latest: true); + $info = static::show( + package: $name, + mode: ShowMode::AVAILABLE, + latest: true, + returnArray: true + ); $winterPackages[$name] = [ 'from' => $details[0], From 08033979ef8117b2ef040f14c7e0e5bc6f06e63b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 16:17:51 -0600 Subject: [PATCH 23/38] Remove unused Search command --- src/Packager/Commands/SearchCommand.php | 22 ---------------------- src/Packager/Composer.php | 3 --- 2 files changed, 25 deletions(-) delete mode 100644 src/Packager/Commands/SearchCommand.php diff --git a/src/Packager/Commands/SearchCommand.php b/src/Packager/Commands/SearchCommand.php deleted file mode 100644 index be8728738..000000000 --- a/src/Packager/Commands/SearchCommand.php +++ /dev/null @@ -1,22 +0,0 @@ -runComposerCommand(); - - if ($output['code'] !== 0) { - throw new CommandException(implode(PHP_EOL, $output['output'])); - } - - $this->results = json_decode(implode(PHP_EOL, $output['output']), true) ?? []; - - return $this; - } -} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 33875d5c3..02c62fced 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -9,7 +9,6 @@ use Winter\Storm\Foundation\Extension\WinterExtension; use Winter\Storm\Network\Http; use Winter\Storm\Packager\Commands\RequireCommand; -use Winter\Storm\Packager\Commands\SearchCommand; use Winter\Storm\Packager\Commands\UpdateCommand; use Winter\Storm\Support\Facades\File; @@ -17,7 +16,6 @@ * @class Composer * @method static i(): array * @method static install(): array - * @method static search(string $query, ?string $type = null, bool $onlyNames = false, bool $onlyVendors = false): \Winter\Packager\Commands\Search * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false): mixed * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update * @method static remove(?string $package = null, bool $dryRun = false): array @@ -40,7 +38,6 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer->setWorkDir(realpath(base_path())); static::$composer->setCommand('require', new RequireCommand(static::$composer)); - static::$composer->setCommand('search', SearchCommand::class); static::$composer->setCommand('update', UpdateCommand::class); return static::$composer; From 6f2b4f76c26d55c4898bb52cceb09ac6447754e0 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 16:46:51 -0600 Subject: [PATCH 24/38] Replace customized RequireCommand with Composer::getLatestSupportedVersion --- src/Packager/Commands/RequireCommand.php | 100 ----------------------- src/Packager/Composer.php | 13 ++- 2 files changed, 10 insertions(+), 103 deletions(-) delete mode 100644 src/Packager/Commands/RequireCommand.php diff --git a/src/Packager/Commands/RequireCommand.php b/src/Packager/Commands/RequireCommand.php deleted file mode 100644 index 08d70c3d8..000000000 --- a/src/Packager/Commands/RequireCommand.php +++ /dev/null @@ -1,100 +0,0 @@ -package = $package; - $this->dryRun = $dryRun; - $this->dev = $dev; - $this->returnRequired = $returnRequired; - } - - /** - * @inheritDoc - */ - public function arguments(): array - { - $arguments = []; - - if ($this->dryRun) { - $arguments['--dry-run'] = true; - } - - if ($this->dev) { - $arguments['--dev'] = true; - } - - $arguments['packages'] = [$this->package]; - - return $arguments; - } - - /** - * @throws CommandException - * @throws WorkDirException - */ - public function execute(): string - { - $output = $this->runComposerCommand(); - $message = implode(PHP_EOL, $output['output']); - - if ($output['code'] !== 0) { - throw new CommandException($message); - } - - if (!$this->dryRun) { - Cache::forget(Composer::COMPOSER_CACHE_KEY); - } - - if ($this->returnRequired) { - preg_match('/Using version (.*?) /', $output['output'][count($output['output']) - 1], $matches); - return $matches[1] ?? throw new CommandException('Unable to determine required version'); - } - - return $message; - } - - /** - * @inheritDoc - */ - public function getCommandName(): string - { - return 'require'; - } - - public function requiresWorkDir(): bool - { - return true; - } -} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 02c62fced..91f917f4e 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -5,10 +5,10 @@ use Illuminate\Support\Facades\Cache; use Winter\Packager\Composer as PackagerComposer; use Winter\Packager\Enums\ShowMode; +use Winter\Packager\Exceptions\CommandException; use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; use Winter\Storm\Network\Http; -use Winter\Storm\Packager\Commands\RequireCommand; use Winter\Storm\Packager\Commands\UpdateCommand; use Winter\Storm\Support\Facades\File; @@ -19,7 +19,6 @@ * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false): mixed * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update * @method static remove(?string $package = null, bool $dryRun = false): array - * @method static require(?string $package = null, bool $dryRun = false, bool $dev = false, bool $returnRequired = false): string * @method static version(string $detail = 'version'): array|string */ class Composer @@ -37,7 +36,6 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer = new PackagerComposer(); static::$composer->setWorkDir(realpath(base_path())); - static::$composer->setCommand('require', new RequireCommand(static::$composer)); static::$composer->setCommand('update', UpdateCommand::class); return static::$composer; @@ -126,6 +124,15 @@ public static function filterProductionVersions(array $versions, array $keep = [ return $versions; } + public static function getLatestSupportedVersion(string $package): string + { + $message = static::require(package: $package, dryRun: true); + $output = explode(PHP_EOL, $message); + preg_match('/Using version (.*?) /', $output[count($output) - 1], $matches); + + return $matches[1] ?? throw new CommandException('Unable to determine required version'); + } + public static function updateAvailable(string $package): bool { return isset(static::getAvailableUpdates()[$package]); From 2f6f264733e178410bb5a06c270519f068d66f00 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 16:53:04 -0600 Subject: [PATCH 25/38] Remove customized Update command Only missing feature from Packager library is the -W (with all dependencies) flag, we aren't using that right now and can add it to Packager directly if we do need it. --- src/Packager/Commands/UpdateCommand.php | 88 ------------------------- src/Packager/Composer.php | 3 - 2 files changed, 91 deletions(-) delete mode 100644 src/Packager/Commands/UpdateCommand.php diff --git a/src/Packager/Commands/UpdateCommand.php b/src/Packager/Commands/UpdateCommand.php deleted file mode 100644 index 76c8de4bc..000000000 --- a/src/Packager/Commands/UpdateCommand.php +++ /dev/null @@ -1,88 +0,0 @@ -includeDev = $includeDev; - $this->lockFileOnly = $lockFileOnly; - $this->ignorePlatformReqs = $ignorePlatformReqs; - $this->ignoreScripts = $ignoreScripts; - $this->dryRun = $dryRun; - $this->package = $package; - $this->withAllDependencies = $withAllDependencies; - - if (in_array($installPreference, [self::PREFER_NONE, self::PREFER_DIST, self::PREFER_SOURCE])) { - $this->installPreference = $installPreference; - } - } - - /** - * @inheritDoc - */ - public function arguments(): array - { - $arguments = []; - - if ($this->package) { - $arguments['packages'] = [$this->package]; - } - - if ($this->dryRun) { - $arguments['--dry-run'] = true; - } - - if ($this->lockFileOnly) { - $arguments['--no-install'] = true; - } - - if ($this->ignorePlatformReqs) { - $arguments['--ignore-platform-reqs'] = true; - } - - if ($this->ignoreScripts) { - $arguments['--no-scripts'] = true; - } - - if ($this->withAllDependencies) { - $arguments['--with-all-dependencies'] = true; - } - - if (in_array($this->installPreference, [self::PREFER_DIST, self::PREFER_SOURCE])) { - $arguments['--prefer-' . $this->installPreference] = true; - } - - return $arguments; - } - - public function execute() - { - return parent::execute(); - } -} diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 91f917f4e..2ca9912c3 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -9,7 +9,6 @@ use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; use Winter\Storm\Network\Http; -use Winter\Storm\Packager\Commands\UpdateCommand; use Winter\Storm\Support\Facades\File; /** @@ -36,8 +35,6 @@ public static function make(bool $fresh = false): PackagerComposer static::$composer = new PackagerComposer(); static::$composer->setWorkDir(realpath(base_path())); - static::$composer->setCommand('update', UpdateCommand::class); - return static::$composer; } From 4d90597d7181e3bced0fb9e0b4e1b25ba2984038 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 16:58:54 -0600 Subject: [PATCH 26/38] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d612578b..b49edf681 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "wikimedia/less.php": "^5.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.0.1", - "winter/packager": "~0.2" + "winter/packager": "^0.4" }, "require-dev": { "phpunit/phpunit": "^9.5.8", From acc4eefd2de7bfbe9d46e003d0c071e6a1dfbd57 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 17:08:26 -0600 Subject: [PATCH 27/38] Rename command beforeRun local event to command.beforeRun and documents it --- src/Console/Command.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Console/Command.php b/src/Console/Command.php index f93fdb1e5..a9d1ff35b 100644 --- a/src/Console/Command.php +++ b/src/Console/Command.php @@ -76,7 +76,20 @@ public function run(InputInterface $input, OutputInterface $output): int $this->components = $this->laravel->make(Factory::class, ['output' => $this->output]); - $this->fireEvent('beforeRun', [$this]); + /** + * @event command.beforeRun + * Called before the command is run; useful for intercepting the output of the command or auditing the commands run + * + * Example usage: + * + * Command::extend(function (Command $command) { + * $command->bindEvent('command.beforeRun', function () use ($command) { + * MyTaskManager::instance()->setOutput($command->getOutput()); + * }); + * }); + * + */ + $this->fireEvent('command.beforeRun', [$this]); $renderer = Termwind::getRenderer(); renderUsing($this->output->getOutput()); From dc4b910e2614cfd0dce6396cb38f3ab9375121c8 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 17:43:53 -0600 Subject: [PATCH 28/38] Update imports --- src/Packager/Composer.php | 15 +++++++++------ src/Support/ModuleServiceProvider.php | 6 +++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 2ca9912c3..9966c3014 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -6,6 +6,11 @@ use Winter\Packager\Composer as PackagerComposer; use Winter\Packager\Enums\ShowMode; use Winter\Packager\Exceptions\CommandException; +use Winter\Packager\Package\Collection; +use Winter\Packager\Package\DetailedPackage; +use Winter\Packager\Package\DetailedVersionedPackage; +use Winter\Packager\Package\Package; +use Winter\Packager\Package\VersionedPackage; use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; use Winter\Storm\Network\Http; @@ -13,12 +18,10 @@ /** * @class Composer - * @method static i(): array - * @method static install(): array - * @method static show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false): mixed - * @method static update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null): \Winter\Packager\Commands\Update - * @method static remove(?string $package = null, bool $dryRun = false): array - * @method static version(string $detail = 'version'): array|string + * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false) + * @method static string require(string $package, bool $dryRun = false, bool $dev = false) + * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null) + * @method static string remove(?string $package = null, bool $dryRun = false) */ class Composer { diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index 3777205db..f832e6409 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -1,7 +1,7 @@ path ?? $this->path = dirname((new \ReflectionClass(get_called_class()))->getFileName()); + return $this->path ?? $this->path = dirname((new ReflectionClass(get_called_class()))->getFileName()); } public function getIdentifier(): string { - return $this->identifier ?? $this->identifier = (new \ReflectionClass(get_called_class()))->getNamespaceName(); + return $this->identifier ?? $this->identifier = (new ReflectionClass(get_called_class()))->getNamespaceName(); } public function __toString(): string From bdbb0a0836144d4486d23cef0123da3b175973a5 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 6 Aug 2025 22:48:38 -0600 Subject: [PATCH 29/38] Update src/Packager/Composer.php --- src/Packager/Composer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 9966c3014..1e3171e36 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -18,7 +18,7 @@ /** * @class Composer - * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false) + * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false, bool $returnArray = false) * @method static string require(string $package, bool $dryRun = false, bool $dev = false) * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null) * @method static string remove(?string $package = null, bool $dryRun = false) From 4e93a9893f12d454f9d19f70e143a8f75e580bfe Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 7 Aug 2025 09:52:52 -0600 Subject: [PATCH 30/38] Move listPackages to Bazaar --- src/Packager/Composer.php | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 9966c3014..048d35fb5 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -2,7 +2,6 @@ namespace Winter\Storm\Packager; -use Illuminate\Support\Facades\Cache; use Winter\Packager\Composer as PackagerComposer; use Winter\Packager\Enums\ShowMode; use Winter\Packager\Exceptions\CommandException; @@ -13,7 +12,6 @@ use Winter\Packager\Package\VersionedPackage; use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; -use Winter\Storm\Network\Http; use Winter\Storm\Support\Facades\File; /** @@ -244,31 +242,4 @@ protected static function remember(string $key, callable $callable, int $expires return $result; } - - public static function listPackages(string $type): array - { - return Cache::remember(static::COMPOSER_CACHE_KEY . '.packages.' . $type, 60 * 60 * 24, function () use ($type) { - $page = 0; - $packages = []; - do { - $result = Http::get('https://packagist.org/search.json', function (Http $http) use (&$page, $type) { - $http->data([ - 'q' => '', - 'page' => ++$page, - 'type' => $type - ]); - }); - - if ($result->code != '200') { - throw new ApplicationException('Unable to retrieve packages, failed with code: ' . $result->code); - } - - $data = json_decode($result->body, JSON_OBJECT_AS_ARRAY); - - $packages = array_merge($packages, $data['results']); - } while (isset($data['next'])); - - return $packages; - }); - } } From d1aa5485a0598133ba7f95f29b96e81748830842 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 7 Aug 2025 10:46:09 -0600 Subject: [PATCH 31/38] Tidying --- composer.json | 9 ++++++-- src/Foundation/Extension/WinterExtension.php | 6 ++++++ src/Packager/Composer.php | 22 +++++++++++++++----- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index b49edf681..4f9b71b3e 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "wikimedia/less.php": "^5.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.0.1", - "winter/packager": "^0.4" + "winter/packager": "^0.4.1" }, "require-dev": { "phpunit/phpunit": "^9.5.8", @@ -100,5 +100,10 @@ } }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "config": { + "allow-plugins": { + "php-http/discovery": false + } + } } diff --git a/src/Foundation/Extension/WinterExtension.php b/src/Foundation/Extension/WinterExtension.php index 675d5c3a1..4639ee856 100644 --- a/src/Foundation/Extension/WinterExtension.php +++ b/src/Foundation/Extension/WinterExtension.php @@ -9,4 +9,10 @@ public function getPath(): string; public function getVersion(): string; public function getIdentifier(): string; + + public function setComposerPackage(?array $package): void; + + public function getComposerPackage(): ?array; + + public function getComposerPackageName(): ?string; } diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index b2f38b5b3..244e6b31d 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -15,7 +15,7 @@ use Winter\Storm\Support\Facades\File; /** - * @class Composer + * Helper class for interacting with Composer through Winter\Packager * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false, bool $returnArray = false) * @method static string require(string $package, bool $dryRun = false, bool $dev = false) * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null) @@ -48,6 +48,10 @@ public static function __callStatic(string $name, array $args = []): mixed return static::$composer->{$name}(...$args); } + /** + * Get the Winter extensions present in the current project + * @return array $packages List of packages ['type' => ['path' => $details]] + */ public static function getWinterPackages(): array { return static::remember(__METHOD__, function () { @@ -56,6 +60,10 @@ public static function getWinterPackages(): array foreach ($installed as $package) { $details = static::show(package: $package['name'], returnArray: true); + if ($package['name'] === 'winter/storm') { + $packages['core'][$details['path']] = $details; + } + $type = match ($details['type']) { 'winter-plugin', 'october-plugin' => 'plugins', 'winter-module', 'october-module' => 'modules', @@ -74,10 +82,14 @@ public static function getWinterPackages(): array }); } + /** + * Get the available updates for the project + * @return array [$package => ['from' => string, 'to' => string, 'ref' => string, 'available' => array]] + */ public static function getAvailableUpdates(): array { return static::remember(__METHOD__, function () { - $upgrades = static::update(dryRun: true)->getUpgraded(); + $upgrades = static::update(dryRun: true, withAllDependencies: true)->getUpgraded(); $packages = static::getWinterPackageNames(); $winterPackages = array_filter($upgrades, function ($key) use ($packages) { @@ -218,10 +230,10 @@ protected static function remember(string $key, callable $callable, int $expires $key = static::COMPOSER_CACHE_KEY . $key; $key .= File::lastModified(base_path('composer.lock')) . File::lastModified(base_path('composer.json')); - $file = $dir . '/' . md5($key) . '.cache'; + $file = $dir . '/' . md5($key) . '.json'; if (File::exists($file)) { - $cache = unserialize(File::get($file)); + $cache = json_decode(File::get($file), flags: JSON_OBJECT_AS_ARRAY); if (is_null($cache['expires']) || time() < $cache['expires']) { return $cache['result']; @@ -235,7 +247,7 @@ protected static function remember(string $key, callable $callable, int $expires return $result; } - File::put($file, serialize([ + File::put($file, json_encode([ 'expires' => $expires ? time() + $expires : null, 'result' => $result ])); From 79abfb7acd1d838a07cc3f98ce7cc48cc6a6c33a Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Thu, 7 Aug 2025 11:12:49 -0600 Subject: [PATCH 32/38] Static analysis fixes --- src/Packager/Composer.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 244e6b31d..451d5aabe 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -16,9 +16,9 @@ /** * Helper class for interacting with Composer through Winter\Packager - * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(?string $mode = 'installed', string $package = null, bool $noDev = false, bool $latest = false, bool $returnArray = false) + * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(ShowMode $mode = ShowMode::INSTALLED, string $package = null, bool $noDev = false, bool $latest = false, bool $returnArray = false) * @method static string require(string $package, bool $dryRun = false, bool $dev = false) - * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null) + * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null, bool $withAllDependencies = false) * @method static string remove(?string $package = null, bool $dryRun = false) */ class Composer @@ -129,7 +129,7 @@ public static function filterProductionVersions(array $versions, array $keep = [ } } - usort($versions, fn (string $a, string $b): int => version_compare($a, $b, '<')); + usort($versions, fn (string $a, string $b): bool => version_compare($a, $b, '<')); return $versions; } @@ -193,7 +193,7 @@ public static function setPackageRequirement(string $package, string $version): throw new ApplicationException('composer.json file does not exist.'); } - $json = json_decode(File::get($composerJsonPath), JSON_OBJECT_AS_ARRAY); + $json = json_decode(File::get($composerJsonPath), flags: JSON_OBJECT_AS_ARRAY); $set = false; foreach (['require', 'require-dev'] as $mode) { @@ -212,7 +212,8 @@ public static function setPackageRequirement(string $package, string $version): } /** - * This method moves the composer caching out of cache, this is so it is not invalidated during tests. @TODO: fix. + * This method moves the composer caching out of cache, this is so it is not invalidated during tests. + * @TODO: fix. * * @param string $key * @param callable $callable From 969a1a70c20555b254213fe83bbee6cae9012f77 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 8 Aug 2025 11:19:39 -0600 Subject: [PATCH 33/38] Tidying --- src/Packager/Composer.php | 68 +++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 451d5aabe..ac87105e4 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -90,7 +90,13 @@ public static function getAvailableUpdates(): array { return static::remember(__METHOD__, function () { $upgrades = static::update(dryRun: true, withAllDependencies: true)->getUpgraded(); - $packages = static::getWinterPackageNames(); + // Get an array of package names that are winter packages + $packages = array_values( + array_map( + fn ($package) => $package['name'], + array_merge(...array_values(static::getWinterPackages())) + ) + ); $winterPackages = array_filter($upgrades, function ($key) use ($packages) { return in_array($key, $packages); @@ -121,19 +127,9 @@ public static function getAvailableUpdates(): array }); } - public static function filterProductionVersions(array $versions, array $keep = []): array - { - foreach ($versions as $index => $version) { - if ((!str_starts_with($version, 'v') || str_ends_with($version, '-dev')) && !in_array($version, $keep)) { - unset($versions[$index]); - } - } - - usort($versions, fn (string $a, string $b): bool => version_compare($a, $b, '<')); - - return $versions; - } - + /** + * Gets the latest supported version constraints for the provided package that Composer would use under the current conditions + */ public static function getLatestSupportedVersion(string $package): string { $message = static::require(package: $package, dryRun: true); @@ -143,36 +139,42 @@ public static function getLatestSupportedVersion(string $package): string return $matches[1] ?? throw new CommandException('Unable to determine required version'); } + /** + * Check if there is an update available for the provided package + */ public static function updateAvailable(string $package): bool { return isset(static::getAvailableUpdates()[$package]); } + /** + * Get the package info for the provided WinterExtension + */ public static function getPackageInfoByExtension(WinterExtension $extension): array { return static::getPackageInfoByPath($extension->getPath()); } + /** + * Get the package name for the provided WinterExtension + */ public static function getPackageNameByExtension(WinterExtension $extension): ?string { return static::getPackageInfoByPath($extension->getPath())['name']; } + /** + * Get the package info from the provided path + */ public static function getPackageInfoByPath(string $path): array { return array_merge(...array_values(static::getWinterPackages()))[$path] ?? []; } - public static function getWinterPackageNames(): array - { - return array_values( - array_map( - fn ($package) => $package['name'], - array_merge(...array_values(static::getWinterPackages())) - ) - ); - } - + /** + * Get list of Winter packages that are present on the system with their current version + * @return array [$package => ['version' => string, 'ref' => string]] + */ public static function getWinterPackagesWithVersion(): array { $packages = []; @@ -205,12 +207,28 @@ public static function setPackageRequirement(string $package, string $version): } if ($set) { - File::put($composerJsonPath, json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + File::put($composerJsonPath, json_encode($json, flags: JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); } return $set; } + /** + * Removes all dev versions not present in the keep paramater + */ + protected static function filterProductionVersions(array $versions, array $keep = []): array + { + foreach ($versions as $index => $version) { + if ((!str_starts_with($version, 'v') || str_ends_with($version, '-dev')) && !in_array($version, $keep)) { + unset($versions[$index]); + } + } + + usort($versions, fn (string $a, string $b): bool => version_compare($a, $b, '<')); + + return $versions; + } + /** * This method moves the composer caching out of cache, this is so it is not invalidated during tests. * @TODO: fix. From 2594bdf3c59d36a719b7a9c9fd37e997fd705ecf Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 8 Aug 2025 12:55:57 -0600 Subject: [PATCH 34/38] Improve performance of working with Composer --- composer.json | 2 +- src/Foundation/Extension/WinterExtension.php | 6 +- src/Packager/Composer.php | 211 +++++++------------ src/Support/ModuleServiceProvider.php | 3 - src/Support/Traits/HasComposerPackage.php | 23 +- 5 files changed, 94 insertions(+), 151 deletions(-) diff --git a/composer.json b/composer.json index 4f9b71b3e..eba8b9c67 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "wikimedia/less.php": "^5.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.0.1", - "winter/packager": "^0.4.1" + "winter/packager": "^0.4.2" }, "require-dev": { "phpunit/phpunit": "^9.5.8", diff --git a/src/Foundation/Extension/WinterExtension.php b/src/Foundation/Extension/WinterExtension.php index 4639ee856..7eb338cdd 100644 --- a/src/Foundation/Extension/WinterExtension.php +++ b/src/Foundation/Extension/WinterExtension.php @@ -10,9 +10,7 @@ public function getVersion(): string; public function getIdentifier(): string; - public function setComposerPackage(?array $package): void; - - public function getComposerPackage(): ?array; - public function getComposerPackageName(): ?string; + + public function getComposerPackageVersion(): ?string; } diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index ac87105e4..4a7686c2a 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -10,21 +10,17 @@ use Winter\Packager\Package\DetailedVersionedPackage; use Winter\Packager\Package\Package; use Winter\Packager\Package\VersionedPackage; -use Winter\Storm\Exception\ApplicationException; use Winter\Storm\Foundation\Extension\WinterExtension; -use Winter\Storm\Support\Facades\File; /** * Helper class for interacting with Composer through Winter\Packager * @method static Collection|DetailedVersionedPackage|DetailedPackage|VersionedPackage|Package|array|null show(ShowMode $mode = ShowMode::INSTALLED, string $package = null, bool $noDev = false, bool $latest = false, bool $returnArray = false) - * @method static string require(string $package, bool $dryRun = false, bool $dev = false) + * @method static string require(string $package, bool $dryRun = false, bool $dev = false, bool $noUpdate = false, bool $noScripts = false) * @method static \Winter\Packager\Commands\Update update(bool $includeDev = true, bool $lockFileOnly = false, bool $ignorePlatformReqs = false, string $installPreference = 'none', bool $ignoreScripts = false, bool $dryRun = false, ?string $package = null, bool $withAllDependencies = false) * @method static string remove(?string $package = null, bool $dryRun = false) */ class Composer { - public const COMPOSER_CACHE_KEY = 'winter.system.composer'; - protected static PackagerComposer $composer; public static function make(bool $fresh = false): PackagerComposer @@ -48,87 +44,104 @@ public static function __callStatic(string $name, array $args = []): mixed return static::$composer->{$name}(...$args); } + /** + * Pin the provided package to the provided version range. If no version range is provided then + * Composer will use whatever it would use if require $package was run. + */ + public static function pin(string $package, ?string $version = null): void + { + $requiredPackage = $package; + if (!is_null($version)) { + $requiredPackage .= ":$version"; + } + static::require($requiredPackage, noUpdate: true, noScripts: true); + } + /** * Get the Winter extensions present in the current project * @return array $packages List of packages ['type' => ['path' => $details]] */ public static function getWinterPackages(): array { - return static::remember(__METHOD__, function () { - $installed = static::show(returnArray: true); - $packages = []; - foreach ($installed as $package) { - $details = static::show(package: $package['name'], returnArray: true); - - if ($package['name'] === 'winter/storm') { - $packages['core'][$details['path']] = $details; - } - - $type = match ($details['type']) { - 'winter-plugin', 'october-plugin' => 'plugins', - 'winter-module', 'october-module' => 'modules', - 'winter-theme', 'october-theme' => 'themes', - default => null - }; - - if (!$type) { - continue; - } - - $packages[$type][$details['path']] = $details; + $installed = static::make()->getInstalledFile()->packages; + $packages = []; + foreach ($installed as $name => $details) { + $type = null; + if ($name === 'winter/storm') { + $type = 'core'; + } + + $type = $type ?? match ($details['type']) { + 'winter-plugin', 'october-plugin' => 'plugins', + 'winter-module', 'october-module' => 'modules', + 'winter-theme', 'october-theme' => 'themes', + default => null + }; + + if (!$type) { + continue; } - return $packages; - }); + $details['path'] = realpath( + static::make()->getComposerVendorDir() + . DIRECTORY_SEPARATOR + . $details['install-path'] + ); + + $packages[$type][$details['path']] = $details; + } + + return $packages; } /** * Get the available updates for the project + * @TODO: Check if we need to cache this * @return array [$package => ['from' => string, 'to' => string, 'ref' => string, 'available' => array]] */ public static function getAvailableUpdates(): array { - return static::remember(__METHOD__, function () { - $upgrades = static::update(dryRun: true, withAllDependencies: true)->getUpgraded(); - // Get an array of package names that are winter packages - $packages = array_values( - array_map( - fn ($package) => $package['name'], - array_merge(...array_values(static::getWinterPackages())) - ) + $upgrades = static::update(dryRun: true, withAllDependencies: true)->getUpgraded(); + // Get an array of package names that are winter packages + $packages = array_values( + array_map( + fn ($package) => $package['name'], + array_merge(...array_values(static::getWinterPackages())) + ) + ); + + $winterPackages = array_filter($upgrades, function ($key) use ($packages) { + return in_array($key, $packages); + }, ARRAY_FILTER_USE_KEY); + + foreach ($winterPackages as $name => $details) { + $winterPackages[$name] = [ + 'from' => $details[0], + 'to' => $details[1], + ]; + + $info = static::show( + package: $name, + mode: ShowMode::AVAILABLE, + latest: true, + returnArray: true ); - $winterPackages = array_filter($upgrades, function ($key) use ($packages) { - return in_array($key, $packages); - }, ARRAY_FILTER_USE_KEY); - - foreach ($winterPackages as $name => $details) { - $winterPackages[$name] = [ - 'from' => $details[0], - 'to' => $details[1], - ]; - - $info = static::show( - package: $name, - mode: ShowMode::AVAILABLE, - latest: true, - returnArray: true - ); - - $winterPackages[$name] = [ - 'from' => $details[0], - 'to' => $details[1], - 'ref' => $info['dist']['reference'] ?? null, - 'available' => static::filterProductionVersions($info['versions'], [$details[0]]), - ]; - } + $winterPackages[$name] = [ + 'from' => $details[0], + 'to' => $details[1], + 'ref' => $info['dist']['reference'] ?? null, + 'available' => static::filterProductionVersions($info['versions'], [$details[0]]), + ]; + } - return $winterPackages; - }); + return $winterPackages; } /** - * Gets the latest supported version constraints for the provided package that Composer would use under the current conditions + * Gets the latest supported version constraints for the provided package that Composer + * would use under the current conditions + * @TODO: Evaluate for removal if it doesn't get used for the UI */ public static function getLatestSupportedVersion(string $package): string { @@ -188,31 +201,6 @@ public static function getWinterPackagesWithVersion(): array return $packages; } - public static function setPackageRequirement(string $package, string $version): bool - { - $composerJsonPath = base_path('composer.json'); - if (!File::exists($composerJsonPath)) { - throw new ApplicationException('composer.json file does not exist.'); - } - - $json = json_decode(File::get($composerJsonPath), flags: JSON_OBJECT_AS_ARRAY); - - $set = false; - foreach (['require', 'require-dev'] as $mode) { - if (isset($json[$mode][$package])) { - $json[$mode][$package] = $version; - $set = true; - break; - } - } - - if ($set) { - File::put($composerJsonPath, json_encode($json, flags: JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); - } - - return $set; - } - /** * Removes all dev versions not present in the keep paramater */ @@ -228,49 +216,4 @@ protected static function filterProductionVersions(array $versions, array $keep return $versions; } - - /** - * This method moves the composer caching out of cache, this is so it is not invalidated during tests. - * @TODO: fix. - * - * @param string $key - * @param callable $callable - * @param int $expires - * @return mixed - */ - protected static function remember(string $key, callable $callable, int $expires = 60 * 15): mixed - { - $dir = temp_path('composer'); - - if (!File::exists($dir)) { - File::makeDirectory($dir); - } - - $key = static::COMPOSER_CACHE_KEY . $key; - $key .= File::lastModified(base_path('composer.lock')) . File::lastModified(base_path('composer.json')); - - $file = $dir . '/' . md5($key) . '.json'; - - if (File::exists($file)) { - $cache = json_decode(File::get($file), flags: JSON_OBJECT_AS_ARRAY); - - if (is_null($cache['expires']) || time() < $cache['expires']) { - return $cache['result']; - } - } - - $result = $callable(); - - // We don't save nothing - if (!$result) { - return $result; - } - - File::put($file, json_encode([ - 'expires' => $expires ? time() + $expires : null, - 'result' => $result - ])); - - return $result; - } } diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index f832e6409..5e635483c 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -44,9 +44,6 @@ public function boot() // Bind the service provider to the application container $this->app->instance($this::class, $this); - - // Register the composer package if exists - $this->setComposerPackage(Composer::getPackageInfoByExtension($this)); } /** diff --git a/src/Support/Traits/HasComposerPackage.php b/src/Support/Traits/HasComposerPackage.php index d80b40633..d7072bace 100644 --- a/src/Support/Traits/HasComposerPackage.php +++ b/src/Support/Traits/HasComposerPackage.php @@ -2,34 +2,39 @@ namespace Winter\Storm\Support\Traits; +use Winter\Storm\Packager\Composer; + trait HasComposerPackage { /** * @var ?array The composer package details for this plugin. + * [ + * 'name' => '', + * ] */ protected ?array $composerPackage = null; /** - * Set the composer package property for the plugin + * Get the composer package details */ - public function setComposerPackage(?array $package): void + protected function getComposerPackage(): ?array { - $this->composerPackage = $package; + return $this->composerPackage ?? $this->composerPackage = Composer::getPackageInfoByExtension($this); } /** - * Get the composer package details + * Get the composer package name */ - public function getComposerPackage(): ?array + public function getComposerPackageName(): ?string { - return $this->composerPackage; + return $this->getComposerPackage()['name'] ?? null; } /** - * Get the composer package name + * Get the composer package version */ - public function getComposerPackageName(): ?string + public function getComposerPackageVersion(): ?string { - return $this->composerPackage['name'] ?? null; + return $this->getComposerPackage()['versions'][0] ?? null; } } From ad5ca6c6b838e0db68576a06c8571798374fcaa5 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 8 Aug 2025 13:07:56 -0600 Subject: [PATCH 35/38] Fix getting composer package version --- src/Support/Traits/HasComposerPackage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Traits/HasComposerPackage.php b/src/Support/Traits/HasComposerPackage.php index d7072bace..d95cc14a2 100644 --- a/src/Support/Traits/HasComposerPackage.php +++ b/src/Support/Traits/HasComposerPackage.php @@ -35,6 +35,6 @@ public function getComposerPackageName(): ?string */ public function getComposerPackageVersion(): ?string { - return $this->getComposerPackage()['versions'][0] ?? null; + return $this->getComposerPackage()['version'] ?? null; } } From 55ef0af1472d563233ddbdc5c2e711017bc7cf00 Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 8 Aug 2025 13:21:01 -0600 Subject: [PATCH 36/38] Fix usage of composer --- composer.json | 2 +- src/Packager/Composer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index eba8b9c67..4ecf9b615 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "wikimedia/less.php": "^5.0", "wikimedia/minify": "~2.2", "winter/laravel-config-writer": "^1.0.1", - "winter/packager": "^0.4.2" + "winter/packager": "^0.4.3" }, "require-dev": { "phpunit/phpunit": "^9.5.8", diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index 4a7686c2a..bb49dbc51 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -193,7 +193,7 @@ public static function getWinterPackagesWithVersion(): array $packages = []; foreach (array_merge(...array_values(static::getWinterPackages())) as $package) { $packages[$package['name']] = [ - 'version' => $package['versions'][0] ?? null, + 'version' => $package['version'] ?? null, 'ref' => $package['dist']['reference'] ?? null ]; } From 47609366c5b3e9d86085099a428025b5be67997b Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Fri, 8 Aug 2025 13:40:10 -0600 Subject: [PATCH 37/38] Fix PHP Stan --- src/Packager/Composer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Packager/Composer.php b/src/Packager/Composer.php index bb49dbc51..6de28d71f 100644 --- a/src/Packager/Composer.php +++ b/src/Packager/Composer.php @@ -212,7 +212,7 @@ protected static function filterProductionVersions(array $versions, array $keep } } - usort($versions, fn (string $a, string $b): bool => version_compare($a, $b, '<')); + usort($versions, fn (string $a, string $b): int => version_compare($b, $a)); return $versions; } From cb77bd5b8c62a646dad7746162e33769147fea4e Mon Sep 17 00:00:00 2001 From: Luke Towers Date: Wed, 13 Aug 2025 16:15:22 -0600 Subject: [PATCH 38/38] Update src/Support/ModuleServiceProvider.php --- src/Support/ModuleServiceProvider.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Support/ModuleServiceProvider.php b/src/Support/ModuleServiceProvider.php index 5e635483c..c3cc76c60 100644 --- a/src/Support/ModuleServiceProvider.php +++ b/src/Support/ModuleServiceProvider.php @@ -3,7 +3,6 @@ use Illuminate\Support\ServiceProvider as ServiceProviderBase; use ReflectionClass; use Winter\Storm\Foundation\Extension\WinterExtension; -use Winter\Storm\Packager\Composer; use Winter\Storm\Support\ClassLoader; use Winter\Storm\Support\Facades\File; use Winter\Storm\Support\Str;