From 5ca804a85697dd622c72e77131c8528a0797796d Mon Sep 17 00:00:00 2001 From: simonhamp Date: Fri, 1 Nov 2024 09:56:01 +0000 Subject: [PATCH 001/105] Update CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfea8a7..202c6d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.6.0 - 2024-11-01 + +### What's Changed + +* Skip links as supporting library doesn't support that by @danielpetrica in https://github.com/NativePHP/laravel/pull/378 +* Add `getCenterOfActiveScreen` in Screen class by @danielpetrica in https://github.com/NativePHP/laravel/pull/375 +* Improved window management by @simonhamp in https://github.com/NativePHP/laravel/pull/391 +* Child processes by @simonhamp and @gwleuverink in https://github.com/NativePHP/laravel/pull/389 + +### New Contributors + +* @danielpetrica made their first contribution in https://github.com/NativePHP/laravel/pull/378 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.5.7...0.6.0 + ## 0.5.7 - 2024-09-16 ### What's Changed From 1cb1c56dad7079bceeb2f075f884484578bc50d3 Mon Sep 17 00:00:00 2001 From: Jerke Combee Date: Sat, 2 Nov 2024 15:11:39 +0100 Subject: [PATCH 002/105] Add a function to the window manager in order to list all open windows (#396) --- src/Facades/Window.php | 1 + src/Windows/WindowManager.php | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/Facades/Window.php b/src/Facades/Window.php index 75efc45..5ac1e90 100644 --- a/src/Facades/Window.php +++ b/src/Facades/Window.php @@ -8,6 +8,7 @@ * @method static \Native\Laravel\Windows\PendingOpenWindow open(string $id = 'main') * @method static void close($id = null) * @method static object current() + * @method static array all() * @method static void resize($width, $height, $id = null) * @method static void position($x, $y, $animated = false, $id = null) * @method static void alwaysOnTop($alwaysOnTop = true, $id = null) diff --git a/src/Windows/WindowManager.php b/src/Windows/WindowManager.php index 9890708..49574e6 100644 --- a/src/Windows/WindowManager.php +++ b/src/Windows/WindowManager.php @@ -39,6 +39,18 @@ public function current(): Window ->fromRuntimeWindow($window); } + public function all(): array + { + $windows = (array) $this->client->get('window/all')->json(); + + return array_map( + fn ($window) => (new Window($window['id'])) + ->setClient($this->client) + ->fromRuntimeWindow((object) $window), + $windows + ); + } + public function get(string $id): Window { $window = (object) $this->client->get("window/get/{$id}")->json(); From d8a09b86d7cdd510f7d9c72134acda27398cbe86 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 3 Nov 2024 17:11:30 +0000 Subject: [PATCH 003/105] Add exception handler (#398) * Add exception handler to report exceptions in console * Fix styling --------- Co-authored-by: simonhamp --- src/Exceptions/Handler.php | 15 +++++++++++++++ src/NativeServiceProvider.php | 6 ++++++ 2 files changed, 21 insertions(+) create mode 100644 src/Exceptions/Handler.php diff --git a/src/Exceptions/Handler.php b/src/Exceptions/Handler.php new file mode 100644 index 0000000..d1d3c56 --- /dev/null +++ b/src/Exceptions/Handler.php @@ -0,0 +1,15 @@ +reportable(function (\Throwable $e) { + error_log('[NATIVE_EXCEPTION]: '.$e->getMessage()); + }); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 92f88e4..f6eb5b3 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -11,6 +11,7 @@ use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; use Native\Laravel\Events\EventWatcher; +use Native\Laravel\Exceptions\Handler; use Native\Laravel\Logging\LogWatcher; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -44,6 +45,11 @@ public function packageRegistered() return new MigrateCommand($app['migrator'], $app['events']); }); + $this->app->singleton( + \Illuminate\Contracts\Debug\ExceptionHandler::class, + Handler::class + ); + if (config('nativephp-internal.running')) { Artisan::starting(function ($artisan) { $artisan->resolveCommands([ From 68018b50f3363389441700c32689d37eea6ee01f Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Mon, 4 Nov 2024 11:15:38 +0000 Subject: [PATCH 004/105] Add detail to output --- src/Exceptions/Handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exceptions/Handler.php b/src/Exceptions/Handler.php index d1d3c56..4fce003 100644 --- a/src/Exceptions/Handler.php +++ b/src/Exceptions/Handler.php @@ -9,7 +9,7 @@ class Handler extends \Illuminate\Foundation\Exceptions\Handler public function register(): void { $this->reportable(function (\Throwable $e) { - error_log('[NATIVE_EXCEPTION]: '.$e->getMessage()); + error_log("[NATIVE_EXCEPTION]: {$e->getMessage()} ({$e->getCode()}) in {$e->getFile()}:{$e->getLine()}"); }); } } From b885affa6e8da230b5953e98cb682d20dc97def6 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Mon, 4 Nov 2024 11:19:46 +0000 Subject: [PATCH 005/105] Update CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 202c6d5..a59e823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.6.1 - 2024-11-04 + +### What's Changed + +* Add a function to the window manager in order to list all open windows by @JCombee in https://github.com/NativePHP/laravel/pull/396 +* Add exception handler by @simonhamp in https://github.com/NativePHP/laravel/pull/398 + +### New Contributors + +* @JCombee made their first contribution in https://github.com/NativePHP/laravel/pull/396 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.6.0...0.6.1 + ## 0.6.0 - 2024-11-01 ### What's Changed From 3b2aa37b52c73e4ba3073105ed44f7e02cf94d06 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Wed, 6 Nov 2024 15:58:42 +0000 Subject: [PATCH 006/105] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bea809..4de29bb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Check out the [Getting Started](https://nativephp.com/docs/1/getting-started/int Thanks to the following sponsors for funding NativePHP development. Please consider [sponsoring](https://nativephp.com/docs/getting-started/sponsoring). - [BeyondCode](https://beyondco.de/?utm_source=nativephp-docs&utm_medium=logo&utm_campaign=nativephp) - Essential tools for web developers. -- [Laradir](https://laradir.com/?ref=nativephp-docs) - Connecting the best Laravel Developers with the best Laravel Teams. +- [Laradevs](https://laradevs.com/?ref=nativephp-docs) - Connecting the best Laravel Developers with the best Laravel Teams. - [RedGalaxy](https://www.redgalaxy.co.uk) - A web application development studio based in Cambridgeshire, building solutions to help businesses improve efficiency and profitability. - [Sevalla](https://sevalla.com/?utm_source=nativephp&utm_medium=Referral&utm_campaign=homepage) - Host and manage your applications, databases, and static sites in a single, intuitive platform. - [ServerAuth](https://serverauth.com) - Website Deployment & Server Management, made simple! From fd5860a0c25c9f5a8041ab00211008a69e5d91ba Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 12 Nov 2024 18:27:55 +0000 Subject: [PATCH 007/105] Enable WAL mode in SQLite (#405) --- src/NativeServiceProvider.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index f6eb5b3..f966db7 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Console\Application as Artisan; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\DB; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; @@ -122,6 +123,9 @@ public function rewriteDatabase() ]]); config(['database.default' => 'nativephp']); + + DB::statement('PRAGMA journal_mode=WAL;'); + DB::statement('PRAGMA busy_timeout=5000;'); } public function removeDatabase() From f86de673df0ba055c588f42a840e86a94695bc7f Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 12 Nov 2024 18:28:49 +0000 Subject: [PATCH 008/105] Migrate the dev DB when created (#406) --- src/NativeServiceProvider.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index f966db7..0831933 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -111,6 +111,8 @@ public function rewriteDatabase() if (! file_exists($databasePath)) { touch($databasePath); + + Artisan::call('native:migrate'); } } From 05d8f4375d8dc55a274081423604e1a45caabeac Mon Sep 17 00:00:00 2001 From: simonhamp Date: Wed, 13 Nov 2024 07:27:42 +0000 Subject: [PATCH 009/105] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a59e823..c05ef3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.6.2 - 2024-11-13 + +### What's Changed + +* Enable WAL mode in SQLite by @simonhamp in https://github.com/NativePHP/laravel/pull/405 +* Migrate the dev DB when created by @simonhamp in https://github.com/NativePHP/laravel/pull/406 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.6.1...0.6.2 + ## 0.6.1 - 2024-11-04 ### What's Changed From 9330eb7555e566c83a604b99a0167d416af86822 Mon Sep 17 00:00:00 2001 From: Angelos Michalopoulos Date: Thu, 14 Nov 2024 19:55:32 +0200 Subject: [PATCH 010/105] Use Artisan Facade to call native:migrate (#408) --- src/NativeServiceProvider.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 0831933..9f0fd71 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -2,9 +2,10 @@ namespace Native\Laravel; -use Illuminate\Console\Application as Artisan; +use Illuminate\Console\Application; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Artisan; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; @@ -52,8 +53,8 @@ public function packageRegistered() ); if (config('nativephp-internal.running')) { - Artisan::starting(function ($artisan) { - $artisan->resolveCommands([ + Application::starting(function ($app) { + $app->resolveCommands([ LoadStartupConfigurationCommand::class, LoadPHPConfigurationCommand::class, ]); From 9d768bedd1a6f9da65ce064005866d054252d1a5 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Thu, 14 Nov 2024 17:55:58 +0000 Subject: [PATCH 011/105] Fix styling --- src/NativeServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 9f0fd71..a2d5f03 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -4,8 +4,8 @@ use Illuminate\Console\Application; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\DB; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; From 6e6cf13698eb218eeed129b03fb9b5957dc3e61c Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 14 Nov 2024 18:10:04 +0000 Subject: [PATCH 012/105] Fix accessing window properties (#410) * Add type hint * Add magic getter * Cleanup --- src/Events/EventWatcher.php | 1 - src/Windows/Window.php | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Events/EventWatcher.php b/src/Events/EventWatcher.php index bab9373..bae9ae7 100644 --- a/src/Events/EventWatcher.php +++ b/src/Events/EventWatcher.php @@ -12,7 +12,6 @@ public function __construct(protected Client $client) {} public function register(): void { Event::listen('*', function (string $eventName, array $data) { - $event = $data[0] ?? (object) null; if (! method_exists($event, 'broadcastOn')) { diff --git a/src/Windows/Window.php b/src/Windows/Window.php index 17b3ecb..7eadd24 100644 --- a/src/Windows/Window.php +++ b/src/Windows/Window.php @@ -24,7 +24,7 @@ class Window protected bool $kiosk = false; - protected $rememberState = false; + protected bool $rememberState = false; protected bool $alwaysOnTop = false; @@ -362,4 +362,9 @@ public function fromRuntimeWindow(object $window): static return $this; } + + public function __get($var) + { + return $this->$var ?? null; + } } From c223e0f3e630260bf3e522002ac987c64c193b1c Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 14 Nov 2024 18:13:17 +0000 Subject: [PATCH 013/105] Feature/menubar improvement (#411) * Refactor variable * Remove override method `url` is already available from the `HasUrl` trait * Add tooltip setting * Add resizable setting * Allow updating of tooltip * Allow updating of icon * Support firing a custom event on click --- src/MenuBar/MenuBar.php | 41 ++++++++++++++++++++++++++-------- src/MenuBar/MenuBarManager.php | 14 ++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/MenuBar/MenuBar.php b/src/MenuBar/MenuBar.php index 9885233..cfc2873 100644 --- a/src/MenuBar/MenuBar.php +++ b/src/MenuBar/MenuBar.php @@ -20,12 +20,18 @@ class MenuBar protected string $label = ''; - protected bool $onlyShowContextWindow = false; + protected string $tooltip = ''; + + protected bool $resizable = true; + + protected bool $onlyShowContextMenu = false; protected ?Menu $contextMenu = null; protected bool $alwaysOnTop = false; + protected ?string $event = null; + protected bool $showDockIcon = false; protected Client $client; @@ -51,28 +57,35 @@ public function icon(string $icon): self public function onlyShowContextMenu(bool $onlyContextMenu = true): self { - $this->onlyShowContextWindow = $onlyContextMenu; + $this->onlyShowContextMenu = $onlyContextMenu; return $this; } - public function url(string $url): self + public function showDockIcon($value = true): self { - $this->url = $url; + $this->showDockIcon = $value; return $this; } - public function showDockIcon($value = true): self + public function label(string $label = ''): self { - $this->showDockIcon = $value; + $this->label = $label; return $this; } - public function label(string $label = ''): self + public function tooltip(string $tooltip = ''): self { - $this->label = $label; + $this->tooltip = $tooltip; + + return $this; + } + + public function resizable(bool $resizable = true): static + { + $this->resizable = $resizable; return $this; } @@ -84,6 +97,13 @@ public function alwaysOnTop($alwaysOnTop = true): self return $this; } + public function event(string $event): self + { + $this->event = $event; + + return $this; + } + public function withContextMenu(Menu $menu): self { $this->contextMenu = $menu; @@ -100,15 +120,18 @@ public function toArray(): array 'x' => $this->x, 'y' => $this->y, 'label' => $this->label, + 'tooltip' => $this->tooltip, + 'resizable' => $this->resizable, 'width' => $this->width, 'height' => $this->height, 'vibrancy' => $this->vibrancy, 'showDockIcon' => $this->showDockIcon, 'transparency' => $this->transparent, 'backgroundColor' => $this->backgroundColor, - 'onlyShowContextWindow' => $this->onlyShowContextWindow, + 'onlyShowContextMenu' => $this->onlyShowContextMenu, 'contextMenu' => ! is_null($this->contextMenu) ? $this->contextMenu->toArray()['submenu'] : null, 'alwaysOnTop' => $this->alwaysOnTop, + 'event' => $this->event, ]; } } diff --git a/src/MenuBar/MenuBarManager.php b/src/MenuBar/MenuBarManager.php index 04aedb0..b10992d 100644 --- a/src/MenuBar/MenuBarManager.php +++ b/src/MenuBar/MenuBarManager.php @@ -31,6 +31,20 @@ public function label(string $label) ]); } + public function tooltip(string $tooltip) + { + $this->client->post('menu-bar/tooltip', [ + 'tooltip' => $tooltip, + ]); + } + + public function icon(string $icon) + { + $this->client->post('menu-bar/icon', [ + 'icon' => $icon, + ]); + } + public function contextMenu(Menu $contextMenu) { $this->client->post('menu-bar/context-menu', [ From cee53d740d6e16851cc8d5d89cb71e2a1e8a601c Mon Sep 17 00:00:00 2001 From: simonhamp Date: Thu, 14 Nov 2024 18:15:53 +0000 Subject: [PATCH 014/105] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c05ef3a..af3daf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.6.3 - 2024-11-14 + +### What's Changed + +* Fix native:migrate:fresh by @miagg in https://github.com/NativePHP/laravel/pull/408 +* Fix accessing window properties by @simonhamp in https://github.com/NativePHP/laravel/pull/410 +* MenuBar improvements by @simonhamp in https://github.com/NativePHP/laravel/pull/411 + +### New Contributors + +* @miagg made their first contribution in https://github.com/NativePHP/laravel/pull/408 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.6.2...0.6.3 + ## 0.6.2 - 2024-11-13 ### What's Changed From a3bd955f5b5f7d833d076838bd721c301ab6b9f1 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Fri, 15 Nov 2024 11:36:33 +0000 Subject: [PATCH 015/105] Fix some DB stuff (#413) * Resolve native:migrate command so we can call it * Only run exception handler in native context * Only configure DB if we have one * Silently unlink all SQlite files --- src/NativeServiceProvider.php | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index a2d5f03..d9c28a1 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -47,16 +47,17 @@ public function packageRegistered() return new MigrateCommand($app['migrator'], $app['events']); }); - $this->app->singleton( - \Illuminate\Contracts\Debug\ExceptionHandler::class, - Handler::class - ); - if (config('nativephp-internal.running')) { + $this->app->singleton( + \Illuminate\Contracts\Debug\ExceptionHandler::class, + Handler::class + ); + Application::starting(function ($app) { $app->resolveCommands([ LoadStartupConfigurationCommand::class, LoadPHPConfigurationCommand::class, + MigrateCommand::class, ]); }); @@ -127,8 +128,10 @@ public function rewriteDatabase() config(['database.default' => 'nativephp']); - DB::statement('PRAGMA journal_mode=WAL;'); - DB::statement('PRAGMA busy_timeout=5000;'); + if (file_exists($databasePath)) { + DB::statement('PRAGMA journal_mode=WAL;'); + DB::statement('PRAGMA busy_timeout=5000;'); + } } public function removeDatabase() @@ -137,13 +140,11 @@ public function removeDatabase() if (config('app.debug')) { $databasePath = database_path('nativephp.sqlite'); - - if (! file_exists($databasePath)) { - return; - } } - unlink($databasePath); + @unlink($databasePath); + @unlink($databasePath.'-shm'); + @unlink($database.'-wal'); } protected function configureDisks(): void From 63390309094e2b07838d3f790c85220bd9ab1e8c Mon Sep 17 00:00:00 2001 From: Willem Leuverink Date: Sun, 17 Nov 2024 16:02:41 +0100 Subject: [PATCH 016/105] Add dedicated php child process endpoint (#414) * add separate php endpoint * fix window test --- src/ChildProcess.php | 10 ++++++++-- tests/ChildProcess/ChildProcessTest.php | 16 ++++++++-------- tests/Windows/WindowTest.php | 5 ++++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ChildProcess.php b/src/ChildProcess.php index ff80a07..171210f 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -72,9 +72,15 @@ public function start( public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self { - $cmd = [PHP_BINARY, ...(array) $cmd]; + $process = $this->client->post('child-process/start-php', [ + 'alias' => $alias, + 'cmd' => (array) $cmd, + 'cwd' => $cwd ?? base_path(), + 'env' => $env, + 'persistent' => $persistent, + ])->json(); - return $this->start($cmd, $alias, env: $env, persistent: $persistent); + return $this->fromRuntimeProcess($process); } public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self diff --git a/tests/ChildProcess/ChildProcessTest.php b/tests/ChildProcess/ChildProcessTest.php index a2d9259..7bd13b7 100644 --- a/tests/ChildProcess/ChildProcessTest.php +++ b/tests/ChildProcess/ChildProcessTest.php @@ -35,9 +35,9 @@ ChildProcess::php("-r 'sleep(5);'", 'some-alias', ['baz' => 'zah']); Http::assertSent(function (Request $request) { - return $request->url() === 'http://localhost:4000/api/child-process/start' && + return $request->url() === 'http://localhost:4000/api/child-process/start-php' && $request['alias'] === 'some-alias' && - $request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"] && + $request['cmd'] === ["-r 'sleep(5);'"] && $request['cwd'] === base_path() && $request['env'] === ['baz' => 'zah']; }); @@ -47,9 +47,9 @@ ChildProcess::artisan('foo:bar --verbose', 'some-alias', ['baz' => 'zah']); Http::assertSent(function (Request $request) { - return $request->url() === 'http://localhost:4000/api/child-process/start' && + return $request->url() === 'http://localhost:4000/api/child-process/start-php' && $request['alias'] === 'some-alias' && - $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar --verbose'] && + $request['cmd'] === ['artisan', 'foo:bar --verbose'] && $request['cwd'] === base_path() && $request['env'] === ['baz' => 'zah']; }); @@ -65,18 +65,18 @@ it('accepts either a string or a array as php command argument', function () { ChildProcess::php("-r 'sleep(5);'", 'some-alias'); - Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, "-r 'sleep(5);'"]); + Http::assertSent(fn (Request $request) => $request['cmd'] === ["-r 'sleep(5);'"]); ChildProcess::php(['-r', "'sleep(5);'"], 'some-alias'); - Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, '-r', "'sleep(5);'"]); + Http::assertSent(fn (Request $request) => $request['cmd'] === ['-r', "'sleep(5);'"]); }); it('accepts either a string or a array as artisan command argument', function () { ChildProcess::artisan('foo:bar', 'some-alias'); - Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:bar']); + Http::assertSent(fn (Request $request) => $request['cmd'] === ['artisan', 'foo:bar']); ChildProcess::artisan(['foo:baz'], 'some-alias'); - Http::assertSent(fn (Request $request) => $request['cmd'] === [PHP_BINARY, 'artisan', 'foo:baz']); + Http::assertSent(fn (Request $request) => $request['cmd'] === ['artisan', 'foo:baz']); }); it('sets the cwd to the base path if none was given', function () { diff --git a/tests/Windows/WindowTest.php b/tests/Windows/WindowTest.php index 25dbafc..8cf21d6 100644 --- a/tests/Windows/WindowTest.php +++ b/tests/Windows/WindowTest.php @@ -1,13 +1,16 @@ andReturn(new WindowClass('main')); $window = Window::open() + ->setClient(new Client) ->id('main') ->title('milwad') ->titleBarStyle('milwad') @@ -43,7 +46,7 @@ expect($windowArray['maximizable'])->toBeTrue(); expect($windowArray['closable'])->toBeTrue(); expect($windowArray['fullscreen'])->toBeTrue(); - expect($windowArray['kiosk'])->toBeFalse(); + expect($windowArray['kiosk'])->toBeTrue(); expect($windowArray['autoHideMenuBar'])->toBeTrue(); }); From 1df3c34b7e24a0f7e57caaf38a46d68bcc9769b6 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Sun, 17 Nov 2024 15:08:11 +0000 Subject: [PATCH 017/105] Update CHANGELOG --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3daf4..9a866e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.6.4 - 2024-11-17 + +### What's Changed + +* Fix some DB stuff by @simonhamp in https://github.com/NativePHP/laravel/pull/413 +* Add dedicated PHP ChildProcess endpoint by @gwleuverink in https://github.com/NativePHP/laravel/pull/414 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.6.3...0.6.4 + ## 0.6.3 - 2024-11-14 ### What's Changed From c29bd0d63b64ff87ac3f4859f0dea3d720558304 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 19 Nov 2024 16:26:24 +0100 Subject: [PATCH 018/105] Fix Settings facade DocBloc (#419) --- src/Facades/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facades/Settings.php b/src/Facades/Settings.php index d1126c5..2fc2fdd 100644 --- a/src/Facades/Settings.php +++ b/src/Facades/Settings.php @@ -6,7 +6,7 @@ /** * @method static void set($key, $value) - * @method static void mixed($key, $default = null) + * @method static mixed get($key, $default = null) */ class Settings extends Facade { From 3e0c562e871a038a4ed4f6699251b6eda060fa67 Mon Sep 17 00:00:00 2001 From: A G Date: Wed, 20 Nov 2024 07:29:21 -0500 Subject: [PATCH 019/105] Fake test double for WindowManager::Class: (#422) - Usage of (relatively lenient) WindowManagerContract::class in Laravel container - Implement WindowManagerFake::class with several testing assertions and helper methods - Laravel-style facade Window::fake() method - Tests --- src/Contracts/WindowManager.php | 23 +++++ src/Facades/Window.php | 11 +- src/Fakes/WindowManagerFake.php | 94 +++++++++++++++++ src/NativeServiceProvider.php | 7 ++ src/Windows/WindowManager.php | 3 +- tests/Fakes/FakeWindowManagerTest.php | 142 ++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 src/Contracts/WindowManager.php create mode 100644 src/Fakes/WindowManagerFake.php create mode 100644 tests/Fakes/FakeWindowManagerTest.php diff --git a/src/Contracts/WindowManager.php b/src/Contracts/WindowManager.php new file mode 100644 index 0000000..e4bbe63 --- /dev/null +++ b/src/Contracts/WindowManager.php @@ -0,0 +1,23 @@ + + */ + public function all(): array; + + public function get(string $id): Window; +} diff --git a/src/Facades/Window.php b/src/Facades/Window.php index 5ac1e90..e774755 100644 --- a/src/Facades/Window.php +++ b/src/Facades/Window.php @@ -3,6 +3,8 @@ namespace Native\Laravel\Facades; use Illuminate\Support\Facades\Facade; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; +use Native\Laravel\Fakes\WindowManagerFake; /** * @method static \Native\Laravel\Windows\PendingOpenWindow open(string $id = 'main') @@ -18,8 +20,15 @@ */ class Window extends Facade { + public static function fake() + { + return tap(new WindowManagerFake, function ($fake) { + static::swap($fake); + }); + } + protected static function getFacadeAccessor() { - return \Native\Laravel\Windows\WindowManager::class; + return WindowManagerContract::class; } } diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php new file mode 100644 index 0000000..2f2a1ce --- /dev/null +++ b/src/Fakes/WindowManagerFake.php @@ -0,0 +1,94 @@ + $windows + */ + public function alwaysReturnWindows(array $windows): self + { + $this->forcedWindowReturnValues = $windows; + + return $this; + } + + public function open(string $id = 'main') + { + $this->opened[] = $id; + } + + public function close($id = null) + { + $this->closed[] = $id; + } + + public function hide($id = null) + { + $this->hidden[] = $id; + } + + public function current(): Window + { + $this->ensureForceReturnWindowsProvided(); + + return $this->forcedWindowReturnValues[array_rand($this->forcedWindowReturnValues)]; + } + + /** + * @return array + */ + public function all(): array + { + $this->ensureForceReturnWindowsProvided(); + + return $this->forcedWindowReturnValues; + } + + public function get(string $id): Window + { + $this->ensureForceReturnWindowsProvided(); + + $matchingWindows = array_filter($this->forcedWindowReturnValues, fn (Window $window) => $window->getId() === $id); + + PHPUnit::assertNotEmpty($matchingWindows); + PHPUnit::assertCount(1, $matchingWindows); + + return Arr::first($matchingWindows); + } + + public function assertOpened(string $id): void + { + PHPUnit::assertContains($id, $this->opened); + } + + public function assertClosed(?string $id): void + { + PHPUnit::assertContains($id, $this->closed); + } + + public function assertHidden(?string $id): void + { + PHPUnit::assertContains($id, $this->hidden); + } + + private function ensureForceReturnWindowsProvided(): void + { + PHPUnit::assertNotEmpty($this->forcedWindowReturnValues); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index d9c28a1..dc2e258 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -3,6 +3,7 @@ namespace Native\Laravel; use Illuminate\Console\Application; +use Illuminate\Foundation\Application as Foundation; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; @@ -12,9 +13,11 @@ use Native\Laravel\Commands\MigrateCommand; use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; use Native\Laravel\Logging\LogWatcher; +use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -47,6 +50,10 @@ public function packageRegistered() return new MigrateCommand($app['migrator'], $app['events']); }); + $this->app->bind(WindowManagerContract::class, function (Foundation $app) { + return $app->make(WindowManagerImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/src/Windows/WindowManager.php b/src/Windows/WindowManager.php index 49574e6..6404686 100644 --- a/src/Windows/WindowManager.php +++ b/src/Windows/WindowManager.php @@ -4,8 +4,9 @@ use Native\Laravel\Client\Client; use Native\Laravel\Concerns\DetectsWindowId; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; -class WindowManager +class WindowManager implements WindowManagerContract { use DetectsWindowId; diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php new file mode 100644 index 0000000..5f0fbb1 --- /dev/null +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -0,0 +1,142 @@ +toBeInstanceOf(WindowManagerFake::class); +}); + +it('asserts that a window was opened', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open('secondary'); + + $fake->assertOpened('main'); + $fake->assertOpened('secondary'); + + try { + $fake->assertOpened('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was closed', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close('secondary'); + + $fake->assertClosed('main'); + $fake->assertClosed('secondary'); + + try { + $fake->assertClosed('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was hidden', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide('secondary'); + + $fake->assertHidden('main'); + $fake->assertHidden('secondary'); + + try { + $fake->assertHidden('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('forces the return value of current window', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect($windows)->toContain(app(WindowManagerContract::class)->current()); +}); + +it('forces the return value of all windows', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect(app(WindowManagerContract::class)->all())->toBe($windows); +}); + +it('forces the return value of a specific window', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect(app(WindowManagerContract::class)->get('testA'))->toBe($windows[0]); + expect(app(WindowManagerContract::class)->get('testB'))->toBe($windows[1]); +}); + +test('that the get method throws an exception if multiple matching window ids exist', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->get('testA'); +})->throws(AssertionFailedError::class); + +test('that the get method throws an exception if no matching window id exists', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->get('testB'); +})->throws(AssertionFailedError::class); + +test('that the current method throws an exception if no forced window return values are provided', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->current(); +})->throws(AssertionFailedError::class); + +test('that the all method throws an exception if no forced window return values are provided', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->all(); +})->throws(AssertionFailedError::class); From 3b3d8dc8e12a5150b428cc3b2879467d839e7648 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Wed, 20 Nov 2024 12:29:41 +0000 Subject: [PATCH 020/105] Fix styling --- src/Fakes/WindowManagerFake.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php index 2f2a1ce..c3cf791 100644 --- a/src/Fakes/WindowManagerFake.php +++ b/src/Fakes/WindowManagerFake.php @@ -6,7 +6,6 @@ use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Windows\Window; use PHPUnit\Framework\Assert as PHPUnit; -use RuntimeException; class WindowManagerFake implements WindowManagerContract { From ff9283e990cd3a47c5b7433b9f758b3263b5922f Mon Sep 17 00:00:00 2001 From: A G Date: Fri, 22 Nov 2024 06:24:04 -0500 Subject: [PATCH 021/105] Child process test double: (#430) - ChildProcess::class now implements Contracts\ChildProcess::class interface - Facades\ChildProcess::fake() swaps implementations with a test double concrete - Implement new binding in NativeServiceProvider::class - Implement Contracts/ChildProcess::class methods, mimicing that of the implementation - Implement ChildProcessFake::class with assertion helpers - Test that ChildProcessFake::class assertions work --- src/ChildProcess.php | 3 +- src/Contracts/ChildProcess.php | 28 +++ src/Facades/ChildProcess.php | 14 +- src/Fakes/ChildProcessFake.php | 252 +++++++++++++++++++++++++++ src/NativeServiceProvider.php | 6 + tests/Fakes/FakeChildProcessTest.php | 219 +++++++++++++++++++++++ 6 files changed, 518 insertions(+), 4 deletions(-) create mode 100644 src/Contracts/ChildProcess.php create mode 100644 src/Fakes/ChildProcessFake.php create mode 100644 tests/Fakes/FakeChildProcessTest.php diff --git a/src/ChildProcess.php b/src/ChildProcess.php index 171210f..37cff65 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -3,8 +3,9 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; -class ChildProcess +class ChildProcess implements ChildProcessContract { public readonly int $pid; diff --git a/src/Contracts/ChildProcess.php b/src/Contracts/ChildProcess.php new file mode 100644 index 0000000..9859e3e --- /dev/null +++ b/src/Contracts/ChildProcess.php @@ -0,0 +1,28 @@ +make(ChildProcessFake::class), function ($fake) { + static::swap($fake); + }); + } + protected static function getFacadeAccessor() { - self::clearResolvedInstance(Implement::class); + self::clearResolvedInstance(ChildProcessContract::class); - return Implement::class; + return ChildProcessContract::class; } } diff --git a/src/Fakes/ChildProcessFake.php b/src/Fakes/ChildProcessFake.php new file mode 100644 index 0000000..4e6add8 --- /dev/null +++ b/src/Fakes/ChildProcessFake.php @@ -0,0 +1,252 @@ + + */ + public array $gets = []; + + /** + * @var array + */ + public array $starts = []; + + /** + * @var array + */ + public array $phps = []; + + /** + * @var array + */ + public array $artisans = []; + + /** + * @var array + */ + public array $stops = []; + + /** + * @var array + */ + public array $restarts = []; + + /** + * @var array + */ + public array $messages = []; + + public function get(?string $alias = null): self + { + $this->gets[] = $alias; + + return $this; + } + + public function all(): array + { + return [$this]; + } + + public function start( + array|string $cmd, + string $alias, + ?string $cwd = null, + ?array $env = null, + bool $persistent = false + ): self { + $this->starts[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'cwd' => $cwd, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function php( + array|string $cmd, + string $alias, + ?array $env = null, + ?bool $persistent = false + ): self { + $this->phps[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function artisan( + array|string $cmd, + string $alias, + ?array $env = null, + ?bool $persistent = false + ): self { + $this->artisans[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function stop(?string $alias = null): void + { + $this->stops[] = $alias; + } + + public function restart(?string $alias = null): self + { + $this->restarts[] = $alias; + + return $this; + } + + public function message(string $message, ?string $alias = null): self + { + $this->messages[] = [ + 'message' => $message, + 'alias' => $alias, + ]; + + return $this; + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertGet(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->gets); + + return; + } + + $hit = empty( + array_filter( + $this->gets, + fn (mixed $get) => $alias($get) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?string $cwd, ?array $env, bool $persistent): bool $callback + */ + public function assertStarted(Closure $callback): void + { + $hit = empty( + array_filter( + $this->starts, + fn (array $started) => $callback(...$started) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback + */ + public function assertPhp(Closure $callback): void + { + $hit = empty( + array_filter( + $this->phps, + fn (array $php) => $callback(...$php) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback + */ + public function assertArtisan(Closure $callback): void + { + $hit = empty( + array_filter( + $this->artisans, + fn (array $artisan) => $callback(...$artisan) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertStop(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->stops); + + return; + } + + $hit = empty( + array_filter( + $this->stops, + fn (mixed $stop) => $alias($stop) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertRestart(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->restarts); + + return; + } + + $hit = empty( + array_filter( + $this->restarts, + fn (mixed $restart) => $alias($restart) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(string $message, string|null $alias): bool $callback + */ + public function assertMessage(Closure $callback): void + { + $hit = empty( + array_filter( + $this->messages, + fn (array $message) => $callback(...$message) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index dc2e258..a17d953 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -7,12 +7,14 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; +use Native\Laravel\ChildProcess as ChildProcessImplementation; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; use Native\Laravel\Commands\MigrateCommand; use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; +use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; @@ -54,6 +56,10 @@ public function packageRegistered() return $app->make(WindowManagerImplementation::class); }); + $this->app->bind(ChildProcessContract::class, function (Foundation $app) { + return $app->make(ChildProcessImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/tests/Fakes/FakeChildProcessTest.php b/tests/Fakes/FakeChildProcessTest.php new file mode 100644 index 0000000..8fece8a --- /dev/null +++ b/tests/Fakes/FakeChildProcessTest.php @@ -0,0 +1,219 @@ +toBeInstanceOf(ChildProcessFake::class); +}); + +it('asserts get using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->get('testA'); + $fake->get('testB'); + + $fake->assertGet('testA'); + $fake->assertGet('testB'); + + try { + $fake->assertGet('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts get using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->get('testA'); + $fake->get('testB'); + + $fake->assertGet(fn (string $alias) => $alias === 'testA'); + $fake->assertGet(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertGet(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts started using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->start('cmdA', 'aliasA', 'cwdA', ['envA'], true); + $fake->start('cmdB', 'aliasB', 'cwdB', ['envB'], false); + + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $cwd === 'cwdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $cwd === 'cwdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts php using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->php('cmdA', 'aliasA', ['envA'], true); + $fake->php('cmdB', 'aliasB', ['envB'], false); + + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts artisan using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->artisan('cmdA', 'aliasA', ['envA'], true); + $fake->artisan('cmdB', 'aliasB', ['envB'], false); + + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts stop using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->stop('testA'); + $fake->stop('testB'); + + $fake->assertStop('testA'); + $fake->assertStop('testB'); + + try { + $fake->assertStop('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts stop using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->stop('testA'); + $fake->stop('testB'); + + $fake->assertStop(fn (string $alias) => $alias === 'testA'); + $fake->assertStop(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertStop(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts restart using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->restart('testA'); + $fake->restart('testB'); + + $fake->assertRestart('testA'); + $fake->assertRestart('testB'); + + try { + $fake->assertRestart('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts restart using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->restart('testA'); + $fake->restart('testB'); + + $fake->assertRestart(fn (string $alias) => $alias === 'testA'); + $fake->assertRestart(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertRestart(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts message using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->message('messageA', 'aliasA'); + $fake->message('messageB', 'aliasB'); + + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageA' && $alias === 'aliasA'); + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageB' && $alias === 'aliasB'); + + try { + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + + From 3133d14af3f1f2695dcc3143d07b4824deaf2d4a Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Fri, 22 Nov 2024 12:24:24 +0100 Subject: [PATCH 022/105] fix: Notification facade docbloc (#428) Method returns `static` instead of `object` --- src/Facades/Notification.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Facades/Notification.php b/src/Facades/Notification.php index 4f9f56d..425fea1 100644 --- a/src/Facades/Notification.php +++ b/src/Facades/Notification.php @@ -5,9 +5,9 @@ use Illuminate\Support\Facades\Facade; /** - * @method static object title(string $title) - * @method static object event(string $event) - * @method static object message(string $body) + * @method static static title(string $title) + * @method static static event(string $event) + * @method static static message(string $body) * @method static void show() */ class Notification extends Facade From a6dea1f2bb014ddc74befc44479d24a59a2aca70 Mon Sep 17 00:00:00 2001 From: A G Date: Fri, 22 Nov 2024 06:25:25 -0500 Subject: [PATCH 023/105] Improvements to window test doubles (#426) * Improvements to fake assertions: - Add support for optionally passing closures to methods that perform value assertions - Add count assertions - Enhancements to FakeWindowManagerTest::class * Improvements to window faking: - Resolve test environment error caused FakeWindowManager::open() not returning a Window/PendingOpenWindow object. This now mimics the real WindowManager::open() behavior - Remove usage of PHPUnit assertions in FakeWindowManager::class for internal assertions. Now only used for final assertions. This change fixes issue where PHPUnit was reporting 2 assertions per ::assertX() call on the test double --- src/Facades/Window.php | 2 +- src/Fakes/WindowManagerFake.php | 103 +++++++++++-- tests/Fakes/FakeWindowManagerTest.php | 209 +++++++++++++++++++++++--- 3 files changed, 287 insertions(+), 27 deletions(-) diff --git a/src/Facades/Window.php b/src/Facades/Window.php index e774755..f73e216 100644 --- a/src/Facades/Window.php +++ b/src/Facades/Window.php @@ -22,7 +22,7 @@ class Window extends Facade { public static function fake() { - return tap(new WindowManagerFake, function ($fake) { + return tap(static::getFacadeApplication()->make(WindowManagerFake::class), function ($fake) { static::swap($fake); }); } diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php index c3cf791..8604224 100644 --- a/src/Fakes/WindowManagerFake.php +++ b/src/Fakes/WindowManagerFake.php @@ -2,10 +2,13 @@ namespace Native\Laravel\Fakes; +use Closure; use Illuminate\Support\Arr; +use Native\Laravel\Client\Client; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Windows\Window; use PHPUnit\Framework\Assert as PHPUnit; +use Webmozart\Assert\Assert; class WindowManagerFake implements WindowManagerContract { @@ -17,6 +20,10 @@ class WindowManagerFake implements WindowManagerContract public array $forcedWindowReturnValues = []; + public function __construct( + protected Client $client + ) {} + /** * @param array $windows */ @@ -30,6 +37,21 @@ public function alwaysReturnWindows(array $windows): self public function open(string $id = 'main') { $this->opened[] = $id; + + $this->ensureForceReturnWindowsProvided(); + + $matchingWindows = array_filter( + $this->forcedWindowReturnValues, + fn (Window $window) => $window->getId() === $id + ); + + if (empty($matchingWindows)) { + return $this->forcedWindowReturnValues[array_rand($this->forcedWindowReturnValues)]->setClient($this->client); + } + + Assert::count($matchingWindows, 1); + + return Arr::first($matchingWindows)->setClient($this->client); } public function close($id = null) @@ -65,29 +87,92 @@ public function get(string $id): Window $matchingWindows = array_filter($this->forcedWindowReturnValues, fn (Window $window) => $window->getId() === $id); - PHPUnit::assertNotEmpty($matchingWindows); - PHPUnit::assertCount(1, $matchingWindows); + Assert::notEmpty($matchingWindows); + Assert::count($matchingWindows, 1); return Arr::first($matchingWindows); } - public function assertOpened(string $id): void + /** + * @param string|Closure(string): bool $id + */ + public function assertOpened(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->opened); + + return; + } + + $hit = empty( + array_filter( + $this->opened, + fn (string $openedId) => $id($openedId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $id + */ + public function assertClosed(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->closed); + + return; + } + + $hit = empty( + array_filter( + $this->closed, + fn (mixed $closedId) => $id($closedId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $id + */ + public function assertHidden(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->hidden); + + return; + } + + $hit = empty( + array_filter( + $this->hidden, + fn (mixed $hiddenId) => $id($hiddenId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + public function assertOpenedCount(int $expected): void { - PHPUnit::assertContains($id, $this->opened); + PHPUnit::assertCount($expected, $this->opened); } - public function assertClosed(?string $id): void + public function assertClosedCount(int $expected): void { - PHPUnit::assertContains($id, $this->closed); + PHPUnit::assertCount($expected, $this->closed); } - public function assertHidden(?string $id): void + public function assertHiddenCount(int $expected): void { - PHPUnit::assertContains($id, $this->hidden); + PHPUnit::assertCount($expected, $this->hidden); } private function ensureForceReturnWindowsProvided(): void { - PHPUnit::assertNotEmpty($this->forcedWindowReturnValues); + Assert::notEmpty($this->forcedWindowReturnValues, 'No windows were provided to return'); } } diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php index 5f0fbb1..b2f4af2 100644 --- a/tests/Fakes/FakeWindowManagerTest.php +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -1,10 +1,13 @@ Http::response(status: 200)]); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); app(WindowManagerContract::class)->open('main'); app(WindowManagerContract::class)->open('secondary'); @@ -26,8 +34,30 @@ try { $fake->assertOpened('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was opened using callable', function () { + Http::fake(['*' => Http::response(status: 200)]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open('secondary'); + $fake->assertOpened(fn (string $id) => $id === 'main'); + $fake->assertOpened(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertOpened(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { return; } @@ -35,7 +65,7 @@ }); it('asserts that a window was closed', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->close('main'); app(WindowManagerContract::class)->close('secondary'); @@ -46,8 +76,24 @@ try { $fake->assertClosed('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was closed using callable', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close('secondary'); + + $fake->assertClosed(fn (string $id) => $id === 'main'); + $fake->assertClosed(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertClosed(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { return; } @@ -55,7 +101,7 @@ }); it('asserts that a window was hidden', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->hide('main'); app(WindowManagerContract::class)->hide('secondary'); @@ -66,8 +112,84 @@ try { $fake->assertHidden('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was hidden using callable', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide('secondary'); + + $fake->assertHidden(fn (string $id) => $id === 'main'); + $fake->assertHidden(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertHidden(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts opened count', function () { + Http::fake(['*' => Http::response(status: 200)]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open(); + app(WindowManagerContract::class)->open(); + + $fake->assertOpenedCount(3); + + try { + $fake->assertOpenedCount(4); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts closed count', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close(); + app(WindowManagerContract::class)->close(); + + $fake->assertClosedCount(3); + + try { + $fake->assertClosedCount(4); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts hidden count', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide(); + app(WindowManagerContract::class)->hide(); + + $fake->assertHiddenCount(3); + + try { + $fake->assertHiddenCount(4); + } catch (AssertionFailedError) { return; } @@ -75,7 +197,7 @@ }); it('forces the return value of current window', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -86,7 +208,7 @@ }); it('forces the return value of all windows', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -97,7 +219,7 @@ }); it('forces the return value of a specific window', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -109,7 +231,7 @@ }); test('that the get method throws an exception if multiple matching window ids exist', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -117,26 +239,79 @@ ]); app(WindowManagerContract::class)->get('testA'); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the get method throws an exception if no matching window id exists', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), ]); app(WindowManagerContract::class)->get('testB'); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the current method throws an exception if no forced window return values are provided', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->current(); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the all method throws an exception if no forced window return values are provided', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->all(); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); + +test('that the open method throws an exception if no forced window return values are provided', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->open('test'); +})->throws(InvalidArgumentException::class); + +test('that the open method throws an exception if multiple matching window ids exist', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->open('testA'); +})->throws(InvalidArgumentException::class); + +test('that the open method returns a random window if none match the id provided', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new PendingOpenWindow('testA'), + ]); + + expect($windows)->toContain(app(WindowManagerContract::class)->open('testC')); +}); + +test('that the open method returns a window if a matching window id exists', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new PendingOpenWindow('testA'), + ]); + + expect(app(WindowManagerContract::class)->open('testA'))->toBe($windows[0]); +}); From 7b9096955bd033bd57e1665d01372a4c65ac812f Mon Sep 17 00:00:00 2001 From: simonhamp Date: Fri, 22 Nov 2024 11:26:17 +0000 Subject: [PATCH 024/105] Fix styling --- tests/Fakes/FakeChildProcessTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Fakes/FakeChildProcessTest.php b/tests/Fakes/FakeChildProcessTest.php index 8fece8a..57b0c35 100644 --- a/tests/Fakes/FakeChildProcessTest.php +++ b/tests/Fakes/FakeChildProcessTest.php @@ -215,5 +215,3 @@ $this->fail('Expected assertion to fail'); }); - - From 98405aee00259d2ee80251f5cd6c204ef7eb9042 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Sat, 23 Nov 2024 11:27:07 +0100 Subject: [PATCH 025/105] fix: child process cmd: option except iterable array (#429) * fix: child process cmd: option except iterable array TypeError: settings.cmd is not iterable at startPhpProcess * fix: throw an exception if $cmd is not an indexed array * fix: array_values but with a hint for static analyzer --- src/ChildProcess.php | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/ChildProcess.php b/src/ChildProcess.php index 37cff65..634e3f4 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -52,6 +52,10 @@ public function all(): array return $hydrated; } + /** + * @param string|string[] $cmd + * @return $this + */ public function start( string|array $cmd, string $alias, @@ -59,10 +63,11 @@ public function start( ?array $env = null, bool $persistent = false ): static { + $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; $process = $this->client->post('child-process/start', [ 'alias' => $alias, - 'cmd' => (array) $cmd, + 'cmd' => $cmd, 'cwd' => $cwd ?? base_path(), 'env' => $env, 'persistent' => $persistent, @@ -71,11 +76,17 @@ public function start( return $this->fromRuntimeProcess($process); } + /** + * @param string|string[] $cmd + * @return $this + */ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self { + $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; + $process = $this->client->post('child-process/start-php', [ 'alias' => $alias, - 'cmd' => (array) $cmd, + 'cmd' => $cmd, 'cwd' => $cwd ?? base_path(), 'env' => $env, 'persistent' => $persistent, @@ -84,9 +95,15 @@ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool return $this->fromRuntimeProcess($process); } + /** + * @param string|string[] $cmd + * @return $this + */ public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self { - $cmd = ['artisan', ...(array) $cmd]; + $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; + + $cmd = ['artisan', ...$cmd]; return $this->php($cmd, $alias, env: $env, persistent: $persistent); } From 41459c2a9df696741febf960cfd46f669f89f636 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 25 Nov 2024 14:56:20 +0100 Subject: [PATCH 026/105] feature: improve Settings (#432) --- src/Facades/Settings.php | 6 ++++-- src/Settings.php | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Facades/Settings.php b/src/Facades/Settings.php index 2fc2fdd..527c0f9 100644 --- a/src/Facades/Settings.php +++ b/src/Facades/Settings.php @@ -5,8 +5,10 @@ use Illuminate\Support\Facades\Facade; /** - * @method static void set($key, $value) - * @method static mixed get($key, $default = null) + * @method static void set(string $key, $value) + * @method static mixed get(string $key, $default = null) + * @method static void forget(string $key) + * @method static void clear() */ class Settings extends Facade { diff --git a/src/Settings.php b/src/Settings.php index 68e65b5..e849c72 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -8,15 +8,25 @@ class Settings { public function __construct(protected Client $client) {} - public function set($key, $value): void + public function set(string $key, $value): void { $this->client->post('settings/'.$key, [ 'value' => $value, ]); } - public function get($key, $default = null): mixed + public function get(string $key, $default = null): mixed { return $this->client->get('settings/'.$key)->json('value') ?? $default; } + + public function forget(string $key): void + { + $this->client->delete('settings/'.$key); + } + + public function clear(): void + { + $this->client->delete('settings/'); + } } From 79f8966d6d711844e1917877cd61989710c72526 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 26 Nov 2024 14:15:03 +0000 Subject: [PATCH 027/105] Dock goodies (#421) * Dock goodies * Fix styling * fix --------- Co-authored-by: simonhamp --- src/Dock.php | 34 ++++++++++++++++++++++++++++++++++ src/Facades/Dock.php | 6 ++++++ 2 files changed, 40 insertions(+) diff --git a/src/Dock.php b/src/Dock.php index dbd091d..bdbb298 100644 --- a/src/Dock.php +++ b/src/Dock.php @@ -17,4 +17,38 @@ public function menu(Menu $menu) 'items' => $items, ]); } + + public function show() + { + $this->client->post('dock/show'); + } + + public function hide() + { + $this->client->post('dock/hide'); + } + + public function icon(string $path) + { + $this->client->post('dock/icon', ['path' => $path]); + } + + public function bounce(string $type = 'informational') + { + $this->client->post('dock/bounce', ['type' => $type]); + } + + public function cancelBounce() + { + $this->client->post('dock/cancel-bounce'); + } + + public function badge(?string $label = null): void|string + { + if (is_null($label)) { + return $this->client->get('dock/badge'); + } + + $this->client->post('dock/badge', ['label' => $label]); + } } diff --git a/src/Facades/Dock.php b/src/Facades/Dock.php index f938643..b088219 100644 --- a/src/Facades/Dock.php +++ b/src/Facades/Dock.php @@ -6,7 +6,13 @@ use Native\Laravel\Menu\Menu; /** + * @method static void bounce() + * @method static void|string badge(string $type = null) + * @method static void cancelBounce() + * @method static void hide() + * @method static void icon(string $Path) * @method static void menu(Menu $menu) + * @method static void show() */ class Dock extends Facade { From 49dee31a662aa7208c4b51745471978fad21fd41 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 26 Nov 2024 14:16:16 +0000 Subject: [PATCH 028/105] MenuBars continued (#420) * Remove `event` prop * Standardise events * Fix styling * Fix class name --------- Co-authored-by: simonhamp --- src/Events/MenuBar/MenuBarClicked.php | 23 +++++++++++++++++++ src/Events/MenuBar/MenuBarDoubleClicked.php | 23 +++++++++++++++++++ ...MenuOpened.php => MenuBarRightClicked.php} | 4 +++- src/MenuBar/MenuBar.php | 10 -------- 4 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 src/Events/MenuBar/MenuBarClicked.php create mode 100644 src/Events/MenuBar/MenuBarDoubleClicked.php rename src/Events/MenuBar/{MenuBarContextMenuOpened.php => MenuBarRightClicked.php} (77%) diff --git a/src/Events/MenuBar/MenuBarClicked.php b/src/Events/MenuBar/MenuBarClicked.php new file mode 100644 index 0000000..4ac8f9e --- /dev/null +++ b/src/Events/MenuBar/MenuBarClicked.php @@ -0,0 +1,23 @@ +event = $event; - - return $this; - } - public function withContextMenu(Menu $menu): self { $this->contextMenu = $menu; @@ -131,7 +122,6 @@ public function toArray(): array 'onlyShowContextMenu' => $this->onlyShowContextMenu, 'contextMenu' => ! is_null($this->contextMenu) ? $this->contextMenu->toArray()['submenu'] : null, 'alwaysOnTop' => $this->alwaysOnTop, - 'event' => $this->event, ]; } } From 7f4994d0c08208ea2df297ad355232927e4b3516 Mon Sep 17 00:00:00 2001 From: A G Date: Sun, 1 Dec 2024 18:15:00 -0500 Subject: [PATCH 029/105] Global shortcut test double: (#436) - GlobalShortcut::class now implements Contracts\GlobalShortcut::class interface - Facades\GlobalShortcut::fake() swaps implementations with a test double concrete - Implement new binding in NativeServiceProvider::clas - Implement GlobalShortcutFake::class with assertion helpers - Test that GlobalShortcutFake::class assertions work --- src/Contracts/GlobalShortcut.php | 14 +++ src/Facades/GlobalShortcut.php | 11 ++- src/Fakes/GlobalShortcutFake.php | 100 ++++++++++++++++++++ src/GlobalShortcut.php | 3 +- src/NativeServiceProvider.php | 6 ++ tests/Fakes/FakeGlobalShortcutTest.php | 124 +++++++++++++++++++++++++ 6 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 src/Contracts/GlobalShortcut.php create mode 100644 src/Fakes/GlobalShortcutFake.php create mode 100644 tests/Fakes/FakeGlobalShortcutTest.php diff --git a/src/Contracts/GlobalShortcut.php b/src/Contracts/GlobalShortcut.php new file mode 100644 index 0000000..2fff35e --- /dev/null +++ b/src/Contracts/GlobalShortcut.php @@ -0,0 +1,14 @@ +make(GlobalShortcutFake::class), function ($fake) { + static::swap($fake); + }); + } + protected static function getFacadeAccessor() { - return \Native\Laravel\GlobalShortcut::class; + return GlobalShortcutContract::class; } } diff --git a/src/Fakes/GlobalShortcutFake.php b/src/Fakes/GlobalShortcutFake.php new file mode 100644 index 0000000..13efa1e --- /dev/null +++ b/src/Fakes/GlobalShortcutFake.php @@ -0,0 +1,100 @@ + + */ + public array $keys = []; + + /** + * @var array + */ + public array $events = []; + + public int $registeredCount = 0; + + public int $unregisteredCount = 0; + + public function key(string $key): self + { + $this->keys[] = $key; + + return $this; + } + + public function event(string $event): self + { + $this->events[] = $event; + + return $this; + } + + public function register(): void + { + $this->registeredCount++; + } + + public function unregister(): void + { + $this->unregisteredCount++; + } + + /** + * @param string|Closure(string): bool $key + */ + public function assertKey(string|Closure $key): void + { + if (is_callable($key) === false) { + PHPUnit::assertContains($key, $this->keys); + + return; + } + + $hit = empty( + array_filter( + $this->keys, + fn (string $keyIteration) => $key($keyIteration) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $event + */ + public function assertEvent(string|Closure $event): void + { + if (is_callable($event) === false) { + PHPUnit::assertContains($event, $this->events); + + return; + } + + $hit = empty( + array_filter( + $this->events, + fn (string $eventIteration) => $event($eventIteration) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + public function assertRegisteredCount(int $count): void + { + PHPUnit::assertSame($count, $this->registeredCount); + } + + public function assertUnregisteredCount(int $count): void + { + PHPUnit::assertSame($count, $this->unregisteredCount); + } +} diff --git a/src/GlobalShortcut.php b/src/GlobalShortcut.php index 991c341..3859e6a 100644 --- a/src/GlobalShortcut.php +++ b/src/GlobalShortcut.php @@ -3,8 +3,9 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Native\Laravel\Contracts\GlobalShortcut as GlobalShortcutContract; -class GlobalShortcut +class GlobalShortcut implements GlobalShortcutContract { protected string $key; diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index a17d953..9701766 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -15,9 +15,11 @@ use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; +use Native\Laravel\Contracts\GlobalShortcut as GlobalShortcutContract; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; +use Native\Laravel\GlobalShortcut as GlobalShortcutImplementation; use Native\Laravel\Logging\LogWatcher; use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; use Spatie\LaravelPackageTools\Package; @@ -60,6 +62,10 @@ public function packageRegistered() return $app->make(ChildProcessImplementation::class); }); + $this->app->bind(GlobalShortcutContract::class, function (Foundation $app) { + return $app->make(GlobalShortcutImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/tests/Fakes/FakeGlobalShortcutTest.php b/tests/Fakes/FakeGlobalShortcutTest.php new file mode 100644 index 0000000..07bba84 --- /dev/null +++ b/tests/Fakes/FakeGlobalShortcutTest.php @@ -0,0 +1,124 @@ +toBeInstanceOf(GlobalShortcutFake::class); +}); + +it('asserts key using string', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->key('testA'); + $fake->key('testB'); + + $fake->assertKey('testA'); + $fake->assertKey('testB'); + + try { + $fake->assertKey('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts key using callable', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->key('testA'); + $fake->key('testB'); + + $fake->assertKey(fn (string $key) => $key === 'testA'); + $fake->assertKey(fn (string $key) => $key === 'testB'); + + try { + $fake->assertKey(fn (string $key) => $key === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts event using string', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->event('testA'); + $fake->event('testB'); + + $fake->assertEvent('testA'); + $fake->assertEvent('testB'); + + try { + $fake->assertEvent('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts event using callable', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->event('testA'); + $fake->event('testB'); + + $fake->assertEvent(fn (string $event) => $event === 'testA'); + $fake->assertEvent(fn (string $event) => $event === 'testB'); + + try { + $fake->assertEvent(fn (string $event) => $event === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts registered count', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->register(); + $fake->register(); + $fake->register(); + + $fake->assertRegisteredCount(3); + + try { + $fake->assertRegisteredCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts unregistered count', function () { + swap(GlobalShortcutContract::class, $fake = app(GlobalShortcutFake::class)); + + $fake->unregister(); + $fake->unregister(); + $fake->unregister(); + + $fake->assertUnregisteredCount(3); + + try { + $fake->assertUnregisteredCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + From a30505f842e0a906d1d8e059071698791c999ca7 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Sun, 1 Dec 2024 23:15:21 +0000 Subject: [PATCH 030/105] Fix styling --- tests/Fakes/FakeGlobalShortcutTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Fakes/FakeGlobalShortcutTest.php b/tests/Fakes/FakeGlobalShortcutTest.php index 07bba84..0847cb9 100644 --- a/tests/Fakes/FakeGlobalShortcutTest.php +++ b/tests/Fakes/FakeGlobalShortcutTest.php @@ -1,9 +1,8 @@ fail('Expected assertion to fail'); }); - From 53c7fa2f882aaee55bb678ba656e5394bc28ff7e Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 1 Dec 2024 23:38:14 +0000 Subject: [PATCH 031/105] Menu improvements (#423) * Extract MenuBuilder * Add more Electron MenuItem roles * Allow MenuItems to have submenus * enabled/disabled * Add GoToUrl convenience item * Fix styling * Add help and hide roles * Receive combo key data * Add id's to menu items * Support radio items * Style * Remove custom event menu item type * Support custom event firing on all menu items * Fix label * Type hints and consistency * Fix styling * Get rid of the yucky GoTo* stuff Fold it all into Link instead * Fix test * Add hotkey alias method * Update docblock * Make Menu JsonSerializable * Fix styling --------- Co-authored-by: simonhamp --- src/Enums/RolesEnum.php | 14 ++- src/Events/Menu/MenuItemClicked.php | 2 +- src/Facades/Menu.php | 51 +++++++++ src/Menu/Items/Checkbox.php | 7 +- src/Menu/Items/Event.php | 17 --- src/Menu/Items/Link.php | 10 ++ src/Menu/Items/MenuItem.php | 50 ++++++++- src/Menu/Items/Radio.php | 7 +- src/Menu/Menu.php | 103 +++-------------- src/Menu/MenuBuilder.php | 165 ++++++++++++++++++++++++++++ tests/MenuBar/MenuBarTest.php | 7 +- 11 files changed, 316 insertions(+), 117 deletions(-) create mode 100644 src/Facades/Menu.php delete mode 100644 src/Menu/Items/Event.php create mode 100644 src/Menu/MenuBuilder.php diff --git a/src/Enums/RolesEnum.php b/src/Enums/RolesEnum.php index 337c479..0039d72 100644 --- a/src/Enums/RolesEnum.php +++ b/src/Enums/RolesEnum.php @@ -4,12 +4,24 @@ enum RolesEnum: string { - case APP_MENU = 'appMenu'; + case APP_MENU = 'appMenu'; // macOS case FILE_MENU = 'fileMenu'; case EDIT_MENU = 'editMenu'; case VIEW_MENU = 'viewMenu'; case WINDOW_MENU = 'windowMenu'; + case HELP = 'help'; // macOS + case UNDO = 'undo'; + case REDO = 'redo'; + case CUT = 'cut'; + case COPY = 'copy'; + case PASTE = 'paste'; + case PASTE_STYLE = 'pasteAndMatchStyle'; + case RELOAD = 'reload'; + case HIDE = 'hide'; // macOS + case MINIMIZE = 'minimize'; + case CLOSE = 'close'; case QUIT = 'quit'; case TOGGLE_FULL_SCREEN = 'togglefullscreen'; case TOGGLE_DEV_TOOLS = 'toggleDevTools'; + case ABOUT = 'about'; } diff --git a/src/Events/Menu/MenuItemClicked.php b/src/Events/Menu/MenuItemClicked.php index d9daa74..961ed1c 100644 --- a/src/Events/Menu/MenuItemClicked.php +++ b/src/Events/Menu/MenuItemClicked.php @@ -12,7 +12,7 @@ class MenuItemClicked implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public array $item) {} + public function __construct(public array $item, public array $combo = []) {} public function broadcastOn() { diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php new file mode 100644 index 0000000..e12c2cb --- /dev/null +++ b/src/Facades/Menu.php @@ -0,0 +1,51 @@ +label = $label; } } diff --git a/src/Menu/Items/Event.php b/src/Menu/Items/Event.php deleted file mode 100644 index 02af55e..0000000 --- a/src/Menu/Items/Event.php +++ /dev/null @@ -1,17 +0,0 @@ - 'event', - 'event' => $this->event, - 'label' => $this->label, - ]); - } -} diff --git a/src/Menu/Items/Link.php b/src/Menu/Items/Link.php index ec5a1cd..3655574 100644 --- a/src/Menu/Items/Link.php +++ b/src/Menu/Items/Link.php @@ -6,12 +6,22 @@ class Link extends MenuItem { protected string $type = 'link'; + protected bool $openInBrowser = false; + public function __construct(protected string $url, protected ?string $label, protected ?string $accelerator = null) {} + public function openInBrowser(bool $openInBrowser = true): self + { + $this->openInBrowser = $openInBrowser; + + return $this; + } + public function toArray(): array { return array_merge(parent::toArray(), [ 'url' => $this->url, + 'openInBrowser' => $this->openInBrowser, ]); } } diff --git a/src/Menu/Items/MenuItem.php b/src/Menu/Items/MenuItem.php index 34d41ba..7fe9051 100644 --- a/src/Menu/Items/MenuItem.php +++ b/src/Menu/Items/MenuItem.php @@ -3,11 +3,15 @@ namespace Native\Laravel\Menu\Items; use Native\Laravel\Contracts\MenuItem as MenuItemContract; +use Native\Laravel\Facades\Menu as MenuFacade; +use Native\Laravel\Menu\Menu; abstract class MenuItem implements MenuItemContract { protected string $type = 'normal'; + protected ?string $id = null; + protected ?string $label = null; protected ?string $sublabel = null; @@ -18,15 +22,33 @@ abstract class MenuItem implements MenuItemContract protected ?string $toolTip = null; + protected ?Menu $submenu = null; + protected bool $isEnabled = true; protected bool $isVisible = true; protected bool $isChecked = false; - public function enabled($enabled = true): self + protected ?string $event = null; + + public function enabled(): self + { + $this->isEnabled = true; + + return $this; + } + + public function disabled(): self + { + $this->isEnabled = false; + + return $this; + } + + public function id(string $id): self { - $this->isEnabled = $enabled; + $this->id = $id; return $this; } @@ -66,6 +88,11 @@ public function accelerator(string $accelerator): self return $this; } + public function hotkey(string $hotkey): self + { + return $this->accelerator($hotkey); + } + public function checked($checked = true): self { $this->isChecked = $checked; @@ -73,18 +100,34 @@ public function checked($checked = true): self return $this; } - public function toolTip(string $toolTip): self + public function tooltip(string $toolTip): self { $this->toolTip = $toolTip; return $this; } + public function submenu(MenuItemContract ...$items): self + { + $this->submenu = MenuFacade::make(...$items); + + return $this; + } + + public function event(string $event): self + { + $this->event = $event; + + return $this; + } + public function toArray(): array { return array_filter([ 'type' => $this->type, + 'id' => $this->id, 'label' => $this->label, + 'event' => $this->event, 'sublabel' => $this->sublabel, 'toolTip' => $this->toolTip, 'enabled' => $this->isEnabled, @@ -92,6 +135,7 @@ public function toArray(): array 'checked' => $this->isChecked, 'accelerator' => $this->accelerator, 'icon' => $this->icon, + 'submenu' => $this->submenu?->toArray(), ], fn ($value) => $value !== null); } } diff --git a/src/Menu/Items/Radio.php b/src/Menu/Items/Radio.php index 63587a2..0751522 100644 --- a/src/Menu/Items/Radio.php +++ b/src/Menu/Items/Radio.php @@ -6,8 +6,11 @@ class Radio extends MenuItem { protected string $type = 'radio'; - public function __construct(string $label) - { + public function __construct( + string $label, + protected bool $isChecked = false, + protected ?string $accelerator = null + ) { $this->label = $label; } } diff --git a/src/Menu/Menu.php b/src/Menu/Menu.php index 47a81b0..c97a30d 100644 --- a/src/Menu/Menu.php +++ b/src/Menu/Menu.php @@ -3,31 +3,20 @@ namespace Native\Laravel\Menu; use Illuminate\Support\Traits\Conditionable; +use JsonSerializable; use Native\Laravel\Client\Client; use Native\Laravel\Contracts\MenuItem; -use Native\Laravel\Enums\RolesEnum; -use Native\Laravel\Menu\Items\Checkbox; -use Native\Laravel\Menu\Items\Event; -use Native\Laravel\Menu\Items\Label; -use Native\Laravel\Menu\Items\Link; -use Native\Laravel\Menu\Items\Role; -use Native\Laravel\Menu\Items\Separator; -class Menu implements MenuItem +class Menu implements JsonSerializable, MenuItem { use Conditionable; protected array $items = []; - protected string $prepend = ''; + protected string $label = ''; public function __construct(protected Client $client) {} - public static function new(): static - { - return new static(new Client); - } - public function register(): void { $items = $this->toArray()['submenu']; @@ -37,81 +26,11 @@ public function register(): void ]); } - public function prepend(string $prepend): self - { - $this->prepend = $prepend; - - return $this; - } - - public function submenu(string $header, Menu $submenu): static - { - return $this->add($submenu->prepend($header)); - } - - public function separator(): static - { - return $this->add(new Separator); - } - - public function quit(): static - { - return $this->add(new Role(RolesEnum::QUIT)); - } - public function label(string $label): self { - return $this->add(new Label($label)); - } - - public function checkbox(string $label, bool $checked = false, ?string $hotkey = null): self - { - return $this->add(new Checkbox($label, $checked, $hotkey)); - } - - public function event(string $event, string $text, ?string $hotkey = null): self - { - return $this->add(new Event($event, $text, $hotkey)); - } - - public function link(string $url, string $text, ?string $hotkey = null): self - { - return $this->add(new Link($url, $text, $hotkey)); - } + $this->label = $label; - public function appMenu(): static - { - return $this->add(new Role(RolesEnum::APP_MENU)); - } - - public function fileMenu($label = 'File'): static - { - return $this->add(new Role(RolesEnum::FILE_MENU, $label)); - } - - public function editMenu($label = 'Edit'): static - { - return $this->add(new Role(RolesEnum::EDIT_MENU, $label)); - } - - public function viewMenu($label = 'View'): static - { - return $this->add(new Role(RolesEnum::VIEW_MENU, $label)); - } - - public function windowMenu($label = 'Window'): static - { - return $this->add(new Role(RolesEnum::WINDOW_MENU, $label)); - } - - public function toggleFullscreen(): static - { - return $this->add(new Role(RolesEnum::TOGGLE_FULL_SCREEN)); - } - - public function toggleDevTools(): static - { - return $this->add(new Role(RolesEnum::TOGGLE_DEV_TOOLS)); + return $this; } public function add(MenuItem $item): self @@ -123,12 +42,18 @@ public function add(MenuItem $item): self public function toArray(): array { - $items = collect($this->items)->map(fn (MenuItem $item) => $item->toArray())->toArray(); - $label = $this->prepend; + $items = collect($this->items) + ->map(fn (MenuItem $item) => $item->toArray()) + ->toArray(); return [ - 'label' => $label, + 'label' => $this->label, 'submenu' => $items, ]; } + + public function jsonSerialize(): array + { + return $this->toArray(); + } } diff --git a/src/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php new file mode 100644 index 0000000..52ca3c9 --- /dev/null +++ b/src/Menu/MenuBuilder.php @@ -0,0 +1,165 @@ +client); + + foreach ($items as $item) { + $menu->add($item); + } + + return $menu; + } + + public function create(MenuItem ...$items): void + { + $this->make(...$items) + ->register(); + } + + public function default(): void + { + $this->create( + $this->app(), + $this->file(), + $this->edit(), + $this->view(), + $this->window(), + ); + } + + public function label(string $label): Items\Label + { + return new Items\Label($label); + } + + public function checkbox(string $label, bool $checked = false, ?string $hotkey = null): Items\Checkbox + { + return new Items\Checkbox($label, $checked, $hotkey); + } + + public function radio(string $label, bool $checked = false, ?string $hotkey = null): Items\Radio + { + return new Items\Radio($label, $checked, $hotkey); + } + + public function link(string $url, ?string $label = null, ?string $hotkey = null): Items\Link + { + return new Items\Link($url, $label, $hotkey); + } + + public function route(string $route, ?string $label = null, ?string $hotkey = null): Items\Link + { + return new Items\Link(route($route), $label, $hotkey); + } + + public function app(): Items\Role + { + return new Items\Role(RolesEnum::APP_MENU); + } + + public function file(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::FILE_MENU, $label); + } + + public function edit(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::EDIT_MENU, $label); + } + + public function view(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::VIEW_MENU, $label); + } + + public function window(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::WINDOW_MENU, $label); + } + + public function separator(): Items\Separator + { + return new Items\Separator; + } + + public function fullscreen(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::TOGGLE_FULL_SCREEN, $label); + } + + public function devTools(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::TOGGLE_DEV_TOOLS, $label); + } + + public function undo(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::UNDO, $label); + } + + public function redo(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::REDO, $label); + } + + public function cut(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::CUT, $label); + } + + public function copy(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::COPY, $label); + } + + public function paste(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::PASTE, $label); + } + + public function pasteAndMatchStyle(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::PASTE_STYLE, $label); + } + + public function reload(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::RELOAD, $label); + } + + public function minimize(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::MINIMIZE, $label); + } + + public function close(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::CLOSE, $label); + } + + public function quit(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::QUIT, $label); + } + + public function help(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::HELP, $label); + } + + public function hide(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::HIDE, $label); + } +} diff --git a/tests/MenuBar/MenuBarTest.php b/tests/MenuBar/MenuBarTest.php index ec1c022..4b86aa9 100644 --- a/tests/MenuBar/MenuBarTest.php +++ b/tests/MenuBar/MenuBarTest.php @@ -1,7 +1,7 @@ set('nativephp-internal.api_url', 'https://jsonplaceholder.typicode.com/todos/1'); @@ -13,7 +13,10 @@ ->icon('nativephp.png') ->url('https://github.com/milwad-dev') ->withContextMenu( - Menu::new()->label('My Application')->quit(), + Menu::make( + Menu::label('My Application'), + Menu::quit(), + ), ); $menuBarArray = $menuBar->toArray(); From 3dae3bc891b6d59fb9493211ae489e7dc3476d65 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 3 Dec 2024 12:45:46 +0000 Subject: [PATCH 032/105] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index e84c04c..262bbd9 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -31,7 +31,7 @@ body: placeholder: When I do X I see Y. validations: required: true - - type: input + - type: markdown id: package-version attributes: label: Package Versions From 8bbef24d5889b789da220e6070acb4d750d68316 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 3 Dec 2024 12:49:34 +0000 Subject: [PATCH 033/105] Update bug.yml --- .github/ISSUE_TEMPLATE/bug.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 262bbd9..f454785 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,12 +1,20 @@ name: Bug Report description: | - Found a bug in NativePHP? Before submitting your report, please make sure you've been through the section "Debugging" in the docs: https://nativephp.com/docs/getting-started/debugging. + Found a bug in NativePHP? You're in the right place! labels: ["bug"] body: - type: markdown attributes: value: | - We're sorry to hear you have a problem. Please help us solve it by providing the following details. + We're sorry to hear you have a problem. + + Before submitting your report, please make sure you've been through the section "[Debugging](https://nativephp.com/docs/getting-started/debugging)" in the docs. + + If nothing here has helped you, please provide as much useful context as you can here to help us solve help you. + + Note that reams and reams of logs isn't helpful - please share only relevant errors. + + If possible, please prepare a reproduction repo and link to it in the Notes field. - type: textarea id: what-doing attributes: @@ -31,7 +39,7 @@ body: placeholder: When I do X I see Y. validations: required: true - - type: markdown + - type: textarea id: package-version attributes: label: Package Versions @@ -84,6 +92,6 @@ body: id: notes attributes: label: Notes - description: Use this field to provide any other notes that you feel might be relevant to the issue. + description: Use this field to provide any other notes that you feel might be relevant to the issue. Include links to any reproduction repos you've created here. validations: required: false From 5bb2e17507a6ab93832028f50c7302e9ee63892a Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 18 Dec 2024 19:21:54 +0100 Subject: [PATCH 034/105] fix: database migration on first launch (#439) --- src/NativeServiceProvider.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 9701766..89c0740 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -84,6 +84,13 @@ public function packageRegistered() } } + public function bootingPackage() + { + if (config('nativephp-internal.running')) { + $this->rewriteDatabase(); + } + } + protected function configureApp() { if (config('app.debug')) { @@ -94,8 +101,6 @@ protected function configureApp() $this->rewriteStoragePath(); - $this->rewriteDatabase(); - $this->configureDisks(); config(['session.driver' => 'file']); From c96a7c5121ba830c4d24b4f77f031dc5461f6223 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 18 Dec 2024 19:25:08 +0100 Subject: [PATCH 035/105] Fixes and improvements to powerMonitor (#445) * fix: powerMonitor couldn't pass arguments with get methods * feat: additional events * fix: PowerMonitor Facade docblock * feat: added fake class for tests --- src/Client/Client.php | 4 +- src/Contracts/PowerMonitor.php | 17 +++ src/Events/PowerMonitor/ScreenLocked.php | 23 ++++ src/Events/PowerMonitor/ScreenUnlocked.php | 23 ++++ src/Events/PowerMonitor/Shutdown.php | 23 ++++ .../PowerMonitor/UserDidBecomeActive.php | 23 ++++ .../PowerMonitor/UserDidResignActive.php | 23 ++++ src/Facades/PowerMonitor.php | 15 ++- src/Fakes/PowerMonitorFake.php | 93 +++++++++++++ src/NativeServiceProvider.php | 6 + src/PowerMonitor.php | 3 +- tests/Fakes/FakePowerMonitorTest.php | 123 ++++++++++++++++++ 12 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 src/Contracts/PowerMonitor.php create mode 100644 src/Events/PowerMonitor/ScreenLocked.php create mode 100644 src/Events/PowerMonitor/ScreenUnlocked.php create mode 100644 src/Events/PowerMonitor/Shutdown.php create mode 100644 src/Events/PowerMonitor/UserDidBecomeActive.php create mode 100644 src/Events/PowerMonitor/UserDidResignActive.php create mode 100644 src/Fakes/PowerMonitorFake.php create mode 100644 tests/Fakes/FakePowerMonitorTest.php diff --git a/src/Client/Client.php b/src/Client/Client.php index e444ea4..9a8ec81 100644 --- a/src/Client/Client.php +++ b/src/Client/Client.php @@ -21,9 +21,9 @@ public function __construct() ->asJson(); } - public function get(string $endpoint): Response + public function get(string $endpoint, array|string|null $query = null): Response { - return $this->client->get($endpoint); + return $this->client->get($endpoint, $query); } public function post(string $endpoint, array $data = []): Response diff --git a/src/Contracts/PowerMonitor.php b/src/Contracts/PowerMonitor.php new file mode 100644 index 0000000..e8ec3d5 --- /dev/null +++ b/src/Contracts/PowerMonitor.php @@ -0,0 +1,17 @@ +make(PowerMonitorFake::class), function ($fake) { + static::swap($fake); + }); + } + + protected static function getFacadeAccessor(): string + { + return PowerMonitorContract::class; } } diff --git a/src/Fakes/PowerMonitorFake.php b/src/Fakes/PowerMonitorFake.php new file mode 100644 index 0000000..2af9916 --- /dev/null +++ b/src/Fakes/PowerMonitorFake.php @@ -0,0 +1,93 @@ +getSystemIdleStateCount++; + + $this->getSystemIdleStateCalls[] = $threshold; + + return SystemIdleStatesEnum::UNKNOWN; + } + + public function getSystemIdleTime(): int + { + $this->getSystemIdleTimeCount++; + + return 0; + } + + public function getCurrentThermalState(): ThermalStatesEnum + { + $this->getCurrentThermalStateCount++; + + return ThermalStatesEnum::UNKNOWN; + } + + public function isOnBatteryPower(): bool + { + $this->isOnBatteryPowerCount++; + + return false; + } + + /** + * @param int|Closure(int): bool $key + */ + public function assertGetSystemIdleState(int|Closure $key): void + { + if (is_callable($key) === false) { + PHPUnit::assertContains($key, $this->getSystemIdleStateCalls); + + return; + } + + $hit = empty( + array_filter( + $this->getSystemIdleStateCalls, + fn (string $keyIteration) => $key($keyIteration) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + public function assertGetSystemIdleStateCount(int $count): void + { + PHPUnit::assertSame($count, $this->getSystemIdleStateCount); + } + + public function assertGetSystemIdleTimeCount(int $count): void + { + PHPUnit::assertSame($count, $this->getSystemIdleTimeCount); + } + + public function assertGetCurrentThermalStateCount(int $count): void + { + PHPUnit::assertSame($count, $this->getCurrentThermalStateCount); + } + + public function assertIsOnBatteryPowerCount(int $count): void + { + PHPUnit::assertSame($count, $this->isOnBatteryPowerCount); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 89c0740..079bb0a 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -16,11 +16,13 @@ use Native\Laravel\Commands\SeedDatabaseCommand; use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; use Native\Laravel\Contracts\GlobalShortcut as GlobalShortcutContract; +use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; use Native\Laravel\GlobalShortcut as GlobalShortcutImplementation; use Native\Laravel\Logging\LogWatcher; +use Native\Laravel\PowerMonitor as PowerMonitorImplementation; use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -66,6 +68,10 @@ public function packageRegistered() return $app->make(GlobalShortcutImplementation::class); }); + $this->app->bind(PowerMonitorContract::class, function (Foundation $app) { + return $app->make(PowerMonitorImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/src/PowerMonitor.php b/src/PowerMonitor.php index ea0d587..c0307b1 100644 --- a/src/PowerMonitor.php +++ b/src/PowerMonitor.php @@ -3,10 +3,11 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract; use Native\Laravel\Enums\SystemIdleStatesEnum; use Native\Laravel\Enums\ThermalStatesEnum; -class PowerMonitor +class PowerMonitor implements PowerMonitorContract { public function __construct(protected Client $client) {} diff --git a/tests/Fakes/FakePowerMonitorTest.php b/tests/Fakes/FakePowerMonitorTest.php new file mode 100644 index 0000000..4761877 --- /dev/null +++ b/tests/Fakes/FakePowerMonitorTest.php @@ -0,0 +1,123 @@ +toBeInstanceOf(PowerMonitorFake::class); +}); + +it('asserts getSystemIdleState using int', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->getSystemIdleState(10); + $fake->getSystemIdleState(60); + + $fake->assertGetSystemIdleState(10); + $fake->assertGetSystemIdleState(60); + + try { + $fake->assertGetSystemIdleState(20); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts getSystemIdleState using callable', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->getSystemIdleState(10); + $fake->getSystemIdleState(60); + + $fake->assertGetSystemIdleState(fn (int $key) => $key === 10); + $fake->assertGetSystemIdleState(fn (int $key) => $key === 60); + + try { + $fake->assertGetSystemIdleState(fn (int $key) => $key === 20); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts getSystemIdleState count', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->getSystemIdleState(10); + $fake->getSystemIdleState(20); + $fake->getSystemIdleState(60); + + $fake->assertGetSystemIdleStateCount(3); + + try { + $fake->assertGetSystemIdleStateCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts getSystemIdleTime count', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->getSystemIdleTime(); + $fake->getSystemIdleTime(); + $fake->getSystemIdleTime(); + + $fake->assertGetSystemIdleTimeCount(3); + + try { + $fake->assertGetSystemIdleTimeCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts getCurrentThermalState count', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->getCurrentThermalState(); + $fake->getCurrentThermalState(); + $fake->getCurrentThermalState(); + + $fake->assertGetCurrentThermalStateCount(3); + + try { + $fake->assertGetCurrentThermalStateCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts isOnBatteryPower count', function () { + swap(PowerMonitorContract::class, $fake = app(PowerMonitorFake::class)); + + $fake->isOnBatteryPower(); + $fake->isOnBatteryPower(); + $fake->isOnBatteryPower(); + + $fake->assertIsOnBatteryPowerCount(3); + + try { + $fake->assertIsOnBatteryPowerCount(2); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); From 1a8151f9efddf339bcf70acf6c11ae53ae7b84df Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Thu, 19 Dec 2024 15:44:09 +0100 Subject: [PATCH 036/105] feat: phpstan level 5 (#446) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: phpstan level 5 * fix: $database undefined * fix: Unsafe usage of new static() * fix: ignore NativeAppServiceProvider not existing * fix: FreshCommand constructor invoked with 1 parameter, 0 required * fix: MenuBuilder facade function duplicates and arguments * fix: Type void cannot be part of a union type declaration. * fix: Php compactor missing imports and return statement * fix: missing SeedDatabaseCommand@handle return statement * Fix: cannot assign a value to a public readonly property outside of the constructor * Fix: PowerMonitor invoked Client::get() with 2 parameters, 1 required * fix: alternative for FreshCommand migrator argument * Revert "fix: alternative for FreshCommand migrator argument" This reverts commit cac9ea1442e5a8a4019e97aa58fdc39b9b3aa4c9. * Revert "fix: FreshCommand constructor invoked with 1 parameter, 0 required" This reverts commit cc1cb879145df52c11751f2370471a298f25b0a2. * fix: trying something * fix: phpstan.yml * Revert "fix: trying something" This reverts commit 6b88d133254bcb8881df7b4fc88a4aa5f4edc72a. * fix: trying to lower the minimum laravel 10 dependency * fix: final fix 🎉 * Revert "Fix: cannot assign a value to a public readonly property outside of the constructor" This reverts commit 585fb4727ced16a729f18a32a188a99a7b1cd1ea. * fix: put back previous fixes and ignore phpstan errors --- .github/workflows/phpstan.yml | 40 +++++++++++++++++++--------- .gitignore | 1 - composer.json | 10 ++++--- phpstan-baseline.neon | 0 phpstan.neon | 18 +++++++++++++ phpstan.neon.dist | 13 --------- src/ChildProcess.php | 15 ++++++----- src/Commands/SeedDatabaseCommand.php | 2 +- src/Compactor/Php.php | 5 ++-- src/Dialog.php | 2 +- src/Dock.php | 4 ++- src/Facades/Menu.php | 5 ++-- src/NativeServiceProvider.php | 33 +++++++++++++---------- src/Notification.php | 2 +- src/ProgressBar.php | 2 +- 15 files changed, 92 insertions(+), 60 deletions(-) delete mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon delete mode 100644 phpstan.neon.dist diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 3855a08..84219d8 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -1,26 +1,42 @@ name: PHPStan on: + workflow_dispatch: push: - paths: - - '**.php' - - 'phpstan.neon.dist' + branches-ignore: + - 'dependabot/npm_and_yarn/*' jobs: phpstan: - name: phpstan - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + php: [8.3] + steps: - - uses: actions/checkout@v4 + + - name: Checkout code + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' - coverage: none + php-version: ${{ matrix.php }} + + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}-composer- - - name: Install composer dependencies - uses: ramsey/composer-install@v3 + - name: Install Dependencies + run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - - name: Run PHPStan - run: ./vendor/bin/phpstan --error-format=github + - name: Run analysis + run: ./vendor/bin/phpstan analyse --error-format=github diff --git a/.gitignore b/.gitignore index e26945a..3dd7896 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ composer.lock coverage docs phpunit.xml -phpstan.neon testbench.yaml vendor node_modules diff --git a/composer.json b/composer.json index c3950b1..9f7b760 100644 --- a/composer.json +++ b/composer.json @@ -38,9 +38,9 @@ }, "require-dev": { "guzzlehttp/guzzle": "^7.0", + "larastan/larastan": "^2.0|^3.0", "laravel/pint": "^1.0", "nunomaduro/collision": "^7.9", - "nunomaduro/larastan": "^2.0.1", "orchestra/testbench": "^8.0", "pestphp/pest": "^2.0", "pestphp/pest-plugin-arch": "^2.0", @@ -52,8 +52,7 @@ }, "autoload": { "psr-4": { - "Native\\Laravel\\": "src/", - "Native\\Laravel\\Database\\Factories\\": "database/factories/" + "Native\\Laravel\\": "src/" } }, "autoload-dev": { @@ -62,6 +61,11 @@ } }, "scripts": { + "qa" : [ + "@composer format", + "@composer analyse", + "@composer test" + ], "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", "analyse": "vendor/bin/phpstan analyse", "test": "vendor/bin/pest", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon deleted file mode 100644 index e69de29..0000000 diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..33be1e3 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,18 @@ +parameters: + + paths: + - src/ + - config/ +# - tests/ + + + # Level 9 is the highest level + level: 5 + + ignoreErrors: + - '#Class App\\Providers\\NativeAppServiceProvider not found#' + - '#Class Native\\Laravel\\ChildProcess has an uninitialized readonly property#' + +# +# excludePaths: +# - ./*/*/FileToBeExcluded.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist deleted file mode 100644 index 260b5e1..0000000 --- a/phpstan.neon.dist +++ /dev/null @@ -1,13 +0,0 @@ -includes: - - phpstan-baseline.neon - -parameters: - level: 4 - paths: - - src - - config - - database - tmpDir: build/phpstan - checkOctaneCompatibility: true - checkModelProperties: true - diff --git a/src/ChildProcess.php b/src/ChildProcess.php index 634e3f4..f524370 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -19,9 +19,9 @@ class ChildProcess implements ChildProcessContract public readonly bool $persistent; - public function __construct(protected Client $client) {} + final public function __construct(protected Client $client) {} - public function get(?string $alias = null): ?static + public function get(?string $alias = null): ?self { $alias = $alias ?? $this->alias; @@ -62,7 +62,7 @@ public function start( ?string $cwd = null, ?array $env = null, bool $persistent = false - ): static { + ): self { $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; $process = $this->client->post('child-process/start', [ @@ -87,7 +87,7 @@ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $process = $this->client->post('child-process/start-php', [ 'alias' => $alias, 'cmd' => $cmd, - 'cwd' => $cwd ?? base_path(), + 'cwd' => base_path(), 'env' => $env, 'persistent' => $persistent, ])->json(); @@ -115,7 +115,7 @@ public function stop(?string $alias = null): void ])->json(); } - public function restart(?string $alias = null): ?static + public function restart(?string $alias = null): ?self { $process = $this->client->post('child-process/restart', [ 'alias' => $alias ?? $this->alias, @@ -128,7 +128,7 @@ public function restart(?string $alias = null): ?static return $this->fromRuntimeProcess($process); } - public function message(string $message, ?string $alias = null): static + public function message(string $message, ?string $alias = null): self { $this->client->post('child-process/message', [ 'alias' => $alias ?? $this->alias, @@ -138,9 +138,10 @@ public function message(string $message, ?string $alias = null): static return $this; } - protected function fromRuntimeProcess($process): static + protected function fromRuntimeProcess($process) { if (isset($process['pid'])) { + // @phpstan-ignore-next-line $this->pid = $process['pid']; } diff --git a/src/Commands/SeedDatabaseCommand.php b/src/Commands/SeedDatabaseCommand.php index c83879e..cdc032e 100644 --- a/src/Commands/SeedDatabaseCommand.php +++ b/src/Commands/SeedDatabaseCommand.php @@ -15,6 +15,6 @@ public function handle() { (new NativeServiceProvider($this->laravel))->rewriteDatabase(); - parent::handle(); + return parent::handle(); } } diff --git a/src/Compactor/Php.php b/src/Compactor/Php.php index f4838bf..7b5d3ad 100644 --- a/src/Compactor/Php.php +++ b/src/Compactor/Php.php @@ -3,6 +3,8 @@ namespace Native\Laravel\Compactor; use PhpToken; +use RuntimeException; +use Webmozart\Assert\Assert; class Php { @@ -17,7 +19,7 @@ public function compact(string $file, string $contents): string return $this->compactContent($contents); } - $this->compactContent($contents); + return $this->compactContent($contents); } protected function compactContent(string $contents): string @@ -145,7 +147,6 @@ private function retokenizeAttribute(array &$tokens, int $opener): ?array { Assert::keyExists($tokens, $opener); - /** @var PhpToken $token */ $token = $tokens[$opener]; $attributeBody = mb_substr($token->text, 2); $subTokens = PhpToken::tokenize('client->post('dock/cancel-bounce'); } - public function badge(?string $label = null): void|string + public function badge(?string $label = null): ?string { if (is_null($label)) { return $this->client->get('dock/badge'); } $this->client->post('dock/badge', ['label' => $label]); + + return null; } } diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php index e12c2cb..332de24 100644 --- a/src/Facades/Menu.php +++ b/src/Facades/Menu.php @@ -3,6 +3,7 @@ namespace Native\Laravel\Facades; use Illuminate\Support\Facades\Facade; +use Native\Laravel\Contracts\MenuItem; use Native\Laravel\Menu\Items\Checkbox; use Native\Laravel\Menu\Items\Label; use Native\Laravel\Menu\Items\Link; @@ -11,7 +12,7 @@ use Native\Laravel\Menu\Items\Separator; /** - * @method static \Native\Laravel\Menu\Menu make(\Native\Laravel\Menu\Items\MenuItem ...$items) + * @method static \Native\Laravel\Menu\Menu make(MenuItem ...$items) * @method static Checkbox checkbox(string $label, bool $checked = false, ?string $hotkey = null) * @method static Label label(string $label) * @method static Link link(string $url, string $label = null, ?string $hotkey = null) @@ -23,7 +24,6 @@ * @method static Role view() * @method static Role window() * @method static Role help() - * @method static Role window() * @method static Role fullscreen() * @method static Role separator() * @method static Role devTools() @@ -37,7 +37,6 @@ * @method static Role minimize() * @method static Role close() * @method static Role quit() - * @method static Role help() * @method static Role hide() * @method static void create(MenuItem ...$items) * @method static void default() diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 079bb0a..22e3991 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -49,6 +49,7 @@ public function packageRegistered() $this->mergeConfigFrom($this->package->basePath('/../config/nativephp-internal.php'), 'nativephp-internal'); $this->app->singleton(FreshCommand::class, function ($app) { + /* @phpstan-ignore-next-line (beacause we support Laravel 10 & 11) */ return new FreshCommand($app['migrator']); }); @@ -148,13 +149,15 @@ public function rewriteDatabase() } } - config(['database.connections.nativephp' => [ - 'driver' => 'sqlite', - 'url' => env('DATABASE_URL'), - 'database' => $databasePath, - 'prefix' => '', - 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), - ]]); + config([ + 'database.connections.nativephp' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => $databasePath, + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + ]); config(['database.default' => 'nativephp']); @@ -174,7 +177,7 @@ public function removeDatabase() @unlink($databasePath); @unlink($databasePath.'-shm'); - @unlink($database.'-wal'); + @unlink($databasePath.'-wal'); } protected function configureDisks(): void @@ -197,12 +200,14 @@ protected function configureDisks(): void continue; } - config(['filesystems.disks.'.$disk => [ - 'driver' => 'local', - 'root' => env($env, ''), - 'throw' => false, - 'links' => 'skip', - ]]); + config([ + 'filesystems.disks.'.$disk => [ + 'driver' => 'local', + 'root' => env($env, ''), + 'throw' => false, + 'links' => 'skip', + ], + ]); } } } diff --git a/src/Notification.php b/src/Notification.php index 85c7a73..82d13d8 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -12,7 +12,7 @@ class Notification protected string $event = ''; - public function __construct(protected Client $client) {} + final public function __construct(protected Client $client) {} public static function new() { diff --git a/src/ProgressBar.php b/src/ProgressBar.php index 74ca9d7..c9e318f 100644 --- a/src/ProgressBar.php +++ b/src/ProgressBar.php @@ -16,7 +16,7 @@ class ProgressBar protected float $maxSecondsBetweenRedraws = 1; - public function __construct(protected int $maxSteps, protected Client $client) {} + final public function __construct(protected int $maxSteps, protected Client $client) {} public static function create(int $maxSteps): static { From 1cf8438a85bacf565b175d048fff5a3e8424ce4d Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 19 Dec 2024 14:49:06 +0000 Subject: [PATCH 037/105] Consistency --- src/Menu/Items/Checkbox.php | 6 ++---- src/Menu/Items/Label.php | 8 ++++---- src/Menu/Items/Link.php | 6 +++++- src/Menu/Items/Radio.php | 6 ++---- src/Menu/Items/Role.php | 5 ++++- src/Menu/MenuBuilder.php | 9 +++++++-- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Menu/Items/Checkbox.php b/src/Menu/Items/Checkbox.php index afc550e..baa44fc 100644 --- a/src/Menu/Items/Checkbox.php +++ b/src/Menu/Items/Checkbox.php @@ -7,10 +7,8 @@ class Checkbox extends MenuItem protected string $type = 'checkbox'; public function __construct( - string $label, + protected ?string $label, protected bool $isChecked = false, protected ?string $accelerator = null - ) { - $this->label = $label; - } + ) {} } diff --git a/src/Menu/Items/Label.php b/src/Menu/Items/Label.php index 571c5ae..cd03d09 100644 --- a/src/Menu/Items/Label.php +++ b/src/Menu/Items/Label.php @@ -4,8 +4,8 @@ class Label extends MenuItem { - public function __construct(string $label) - { - $this->label = $label; - } + public function __construct( + protected ?string $label, + protected ?string $accelerator = null + ) {} } diff --git a/src/Menu/Items/Link.php b/src/Menu/Items/Link.php index 3655574..25bc3c1 100644 --- a/src/Menu/Items/Link.php +++ b/src/Menu/Items/Link.php @@ -8,7 +8,11 @@ class Link extends MenuItem protected bool $openInBrowser = false; - public function __construct(protected string $url, protected ?string $label, protected ?string $accelerator = null) {} + public function __construct( + protected string $url, + protected ?string $label, + protected ?string $accelerator = null + ) {} public function openInBrowser(bool $openInBrowser = true): self { diff --git a/src/Menu/Items/Radio.php b/src/Menu/Items/Radio.php index 0751522..759af45 100644 --- a/src/Menu/Items/Radio.php +++ b/src/Menu/Items/Radio.php @@ -7,10 +7,8 @@ class Radio extends MenuItem protected string $type = 'radio'; public function __construct( - string $label, + protected ?string $label, protected bool $isChecked = false, protected ?string $accelerator = null - ) { - $this->label = $label; - } + ) {} } diff --git a/src/Menu/Items/Role.php b/src/Menu/Items/Role.php index 3fa3b24..bde40a4 100644 --- a/src/Menu/Items/Role.php +++ b/src/Menu/Items/Role.php @@ -8,7 +8,10 @@ class Role extends MenuItem { protected string $type = 'role'; - public function __construct(protected RolesEnum $role, protected ?string $label = '') {} + public function __construct( + protected RolesEnum $role, + protected ?string $label = '' + ) {} public function toArray(): array { diff --git a/src/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php index 52ca3c9..f0627f7 100644 --- a/src/Menu/MenuBuilder.php +++ b/src/Menu/MenuBuilder.php @@ -38,9 +38,9 @@ public function default(): void ); } - public function label(string $label): Items\Label + public function label(string $label, ?string $hotkey = null): Items\Label { - return new Items\Label($label); + return new Items\Label($label, $hotkey); } public function checkbox(string $label, bool $checked = false, ?string $hotkey = null): Items\Checkbox @@ -162,4 +162,9 @@ public function hide(?string $label = null): Items\Role { return new Items\Role(RolesEnum::HIDE, $label); } + + public function about(?string $label = null): Items\Role + { + return new Items\Role(RolesEnum::ABOUT, $label); + } } From 8267087b21b322f4d26954bacbba048f3e311ae1 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Thu, 19 Dec 2024 14:53:56 +0000 Subject: [PATCH 038/105] Update CHANGELOG --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a866e6..7c33f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 0.7.0 - 2024-12-19 + +### What's Changed + +* Fix Settings facade DocBloc by @SRWieZ in https://github.com/NativePHP/laravel/pull/419 +* Fake test double for WindowManager::Class by @XbNz in https://github.com/NativePHP/laravel/pull/422 +* Child process test double by @XbNz in https://github.com/NativePHP/laravel/pull/430 +* fix: Notification facade docbloc by @SRWieZ in https://github.com/NativePHP/laravel/pull/428 +* Improvements to window test doubles by @XbNz in https://github.com/NativePHP/laravel/pull/426 +* fix: child process cmd: option except iterable array by @SRWieZ in https://github.com/NativePHP/laravel/pull/429 +* feat: improve Settings by @SRWieZ in https://github.com/NativePHP/laravel/pull/432 +* Dock goodies by @simonhamp in https://github.com/NativePHP/laravel/pull/421 +* MenuBars continued by @simonhamp in https://github.com/NativePHP/laravel/pull/420 +* Global shortcut test double by @XbNz in https://github.com/NativePHP/laravel/pull/436 +* Menu improvements by @simonhamp in https://github.com/NativePHP/laravel/pull/423 +* fix: database migration on first launch by @SRWieZ in https://github.com/NativePHP/laravel/pull/439 +* Fixes and improvements to powerMonitor by @SRWieZ in https://github.com/NativePHP/laravel/pull/445 +* feat: phpstan level 5 by @SRWieZ in https://github.com/NativePHP/laravel/pull/446 + +### New Contributors + +* @SRWieZ made their first contribution in https://github.com/NativePHP/laravel/pull/419 +* @XbNz made their first contribution in https://github.com/NativePHP/laravel/pull/422 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.6.4...0.7.0 + ## 0.6.4 - 2024-11-17 ### What's Changed From 101ddf0947c07d43ab71d5864b8a3e3806928a98 Mon Sep 17 00:00:00 2001 From: A G Date: Sun, 29 Dec 2024 10:20:24 -0500 Subject: [PATCH 039/105] Child process queue workers (#450) * Child process queue workers - Remove `ShouldBroadcast` in favor of `ShouldBroadcastNow` in `Events\\ChildProcess` namespace - Implement `QueueWorker::class` - Add new binding to `NativeServiceProvider::class` - Fire up workers: iterate through queue worker config in `NativeServiceProvider::configureApp()` - Test `QueueWorker::up()`, `QueueWorker::down()` - Test `QueueWorkerFake::class` assertions work as expected * Prevent attempting to boot workers on CLI calls * Remove creating workers just by string Rely on keys of the config array to assert uniqueness of worker aliases * Allow workers to be instantiated directly by alias * Fix styling * Fix tests --------- Co-authored-by: Simon Hamp Co-authored-by: simonhamp --- config/nativephp.php | 8 +++ src/Contracts/QueueWorker.php | 12 ++++ src/DTOs/QueueConfig.php | 35 +++++++++++ src/Events/ChildProcess/ErrorReceived.php | 4 +- src/Events/ChildProcess/MessageReceived.php | 4 +- src/Events/ChildProcess/ProcessExited.php | 4 +- src/Events/ChildProcess/ProcessSpawned.php | 4 +- src/Facades/QueueWorker.php | 29 +++++++++ src/Fakes/QueueWorkerFake.php | 61 ++++++++++++++++++ src/NativeServiceProvider.php | 20 ++++++ src/QueueWorker.php | 47 ++++++++++++++ tests/DTOs/QueueWorkerTest.php | 66 ++++++++++++++++++++ tests/Fakes/FakeQueueWorkerTest.php | 69 +++++++++++++++++++++ tests/QueueWorker/QueueWorkerTest.php | 39 ++++++++++++ 14 files changed, 394 insertions(+), 8 deletions(-) create mode 100644 src/Contracts/QueueWorker.php create mode 100644 src/DTOs/QueueConfig.php create mode 100644 src/Facades/QueueWorker.php create mode 100644 src/Fakes/QueueWorkerFake.php create mode 100644 src/QueueWorker.php create mode 100644 tests/DTOs/QueueWorkerTest.php create mode 100644 tests/Fakes/FakeQueueWorkerTest.php create mode 100644 tests/QueueWorker/QueueWorkerTest.php diff --git a/config/nativephp.php b/config/nativephp.php index b24afec..5c6e7a8 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -114,4 +114,12 @@ ], ], ], + + 'queue_workers' => [ + 'default' => [ + 'queues' => ['default'], + 'memory_limit' => 128, + 'timeout' => 60, + ], + ], ]; diff --git a/src/Contracts/QueueWorker.php b/src/Contracts/QueueWorker.php new file mode 100644 index 0000000..a2c4cf9 --- /dev/null +++ b/src/Contracts/QueueWorker.php @@ -0,0 +1,12 @@ + $queuesToConsume + */ + public function __construct( + public readonly string $alias, + public readonly array $queuesToConsume, + public readonly int $memoryLimit, + public readonly int $timeout, + ) {} + + /** + * @return array + */ + public static function fromConfigArray(array $config): array + { + return array_map( + function (array|string $worker, string $alias) { + return new self( + $alias, + $worker['queues'] ?? ['default'], + $worker['memory_limit'] ?? 128, + $worker['timeout'] ?? 60, + ); + }, + $config, + array_keys($config), + ); + } +} diff --git a/src/Events/ChildProcess/ErrorReceived.php b/src/Events/ChildProcess/ErrorReceived.php index 65db9c6..334e9cc 100644 --- a/src/Events/ChildProcess/ErrorReceived.php +++ b/src/Events/ChildProcess/ErrorReceived.php @@ -3,11 +3,11 @@ namespace Native\Laravel\Events\ChildProcess; use Illuminate\Broadcasting\Channel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class ErrorReceived implements ShouldBroadcast +class ErrorReceived implements ShouldBroadcastNow { use Dispatchable, SerializesModels; diff --git a/src/Events/ChildProcess/MessageReceived.php b/src/Events/ChildProcess/MessageReceived.php index 5f7a432..04a51c2 100644 --- a/src/Events/ChildProcess/MessageReceived.php +++ b/src/Events/ChildProcess/MessageReceived.php @@ -3,11 +3,11 @@ namespace Native\Laravel\Events\ChildProcess; use Illuminate\Broadcasting\Channel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class MessageReceived implements ShouldBroadcast +class MessageReceived implements ShouldBroadcastNow { use Dispatchable, SerializesModels; diff --git a/src/Events/ChildProcess/ProcessExited.php b/src/Events/ChildProcess/ProcessExited.php index bf570d8..0dcd589 100644 --- a/src/Events/ChildProcess/ProcessExited.php +++ b/src/Events/ChildProcess/ProcessExited.php @@ -3,11 +3,11 @@ namespace Native\Laravel\Events\ChildProcess; use Illuminate\Broadcasting\Channel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class ProcessExited implements ShouldBroadcast +class ProcessExited implements ShouldBroadcastNow { use Dispatchable, SerializesModels; diff --git a/src/Events/ChildProcess/ProcessSpawned.php b/src/Events/ChildProcess/ProcessSpawned.php index 91fc917..a49b31b 100644 --- a/src/Events/ChildProcess/ProcessSpawned.php +++ b/src/Events/ChildProcess/ProcessSpawned.php @@ -3,11 +3,11 @@ namespace Native\Laravel\Events\ChildProcess; use Illuminate\Broadcasting\Channel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class ProcessSpawned implements ShouldBroadcast +class ProcessSpawned implements ShouldBroadcastNow { use Dispatchable, SerializesModels; diff --git a/src/Facades/QueueWorker.php b/src/Facades/QueueWorker.php new file mode 100644 index 0000000..3b504be --- /dev/null +++ b/src/Facades/QueueWorker.php @@ -0,0 +1,29 @@ +make(QueueWorkerFake::class), function ($fake) { + static::swap($fake); + }); + } + + protected static function getFacadeAccessor(): string + { + self::clearResolvedInstance(QueueWorkerContract::class); + + return QueueWorkerContract::class; + } +} diff --git a/src/Fakes/QueueWorkerFake.php b/src/Fakes/QueueWorkerFake.php new file mode 100644 index 0000000..6482dd9 --- /dev/null +++ b/src/Fakes/QueueWorkerFake.php @@ -0,0 +1,61 @@ + + */ + public array $ups = []; + + /** + * @var array + */ + public array $downs = []; + + public function up(QueueConfig $config): void + { + $this->ups[] = $config; + } + + public function down(string $alias): void + { + $this->downs[] = $alias; + } + + public function assertUp(Closure $callback): void + { + $hit = empty( + array_filter( + $this->ups, + fn (QueueConfig $up) => $callback($up) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + public function assertDown(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->downs); + + return; + } + + $hit = empty( + array_filter( + $this->downs, + fn (string $down) => $alias($down) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 22e3991..6ca8d62 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -17,7 +17,9 @@ use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; use Native\Laravel\Contracts\GlobalShortcut as GlobalShortcutContract; use Native\Laravel\Contracts\PowerMonitor as PowerMonitorContract; +use Native\Laravel\Contracts\QueueWorker as QueueWorkerContract; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; +use Native\Laravel\DTOs\QueueConfig; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; use Native\Laravel\GlobalShortcut as GlobalShortcutImplementation; @@ -73,6 +75,10 @@ public function packageRegistered() return $app->make(PowerMonitorImplementation::class); }); + $this->app->bind(QueueWorkerContract::class, function (Foundation $app) { + return $app->make(QueueWorker::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, @@ -112,6 +118,11 @@ protected function configureApp() config(['session.driver' => 'file']); config(['queue.default' => 'database']); + + // XXX: This logic may need to change when we ditch the internal web server + if (! $this->app->runningInConsole()) { + $this->fireUpQueueWorkers(); + } } protected function rewriteStoragePath() @@ -210,4 +221,13 @@ protected function configureDisks(): void ]); } } + + protected function fireUpQueueWorkers(): void + { + $queueConfigs = QueueConfig::fromConfigArray(config('nativephp.queue_workers')); + + foreach ($queueConfigs as $queueConfig) { + $this->app->make(QueueWorkerContract::class)->up($queueConfig); + } + } } diff --git a/src/QueueWorker.php b/src/QueueWorker.php new file mode 100644 index 0000000..1eb0c00 --- /dev/null +++ b/src/QueueWorker.php @@ -0,0 +1,47 @@ +has("nativephp.queue_workers.{$config}")) { + $config = QueueConfig::fromConfigArray([ + $config => config("nativephp.queue_workers.{$config}"), + ])[0]; + } + + if (! $config instanceof QueueConfig) { + throw new \InvalidArgumentException("Invalid queue configuration alias [$config]"); + } + + $this->childProcess->php( + [ + '-d', + "memory_limit={$config->memoryLimit}M", + 'artisan', + 'queue:work', + "--name={$config->alias}", + '--queue='.implode(',', $config->queuesToConsume), + "--memory={$config->memoryLimit}", + "--timeout={$config->timeout}", + ], + $config->alias, + persistent: true, + ); + } + + public function down(string $alias): void + { + $this->childProcess->stop($alias); + } +} diff --git a/tests/DTOs/QueueWorkerTest.php b/tests/DTOs/QueueWorkerTest.php new file mode 100644 index 0000000..bc1b764 --- /dev/null +++ b/tests/DTOs/QueueWorkerTest.php @@ -0,0 +1,66 @@ +toBeArray(); + expect($configObject)->toHaveCount(count($config)); + + foreach ($config as $alias => $worker) { + if (is_string($worker)) { + expect( + Arr::first( + array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)) + )->queuesToConsume->toBe(['default'] + ); + + expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)))->memoryLimit->toBe(128); + expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)))->timeout->toBe(60); + + continue; + } + + expect( + Arr::first( + array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)) + )->queuesToConsume->toBe($worker['queues'] ?? ['default'] + ); + + expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)))->memoryLimit->toBe($worker['memory_limit'] ?? 128); + expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)))->timeout->toBe($worker['timeout'] ?? 60); + } +})->with([ + [ + 'queue_workers' => [ + 'some_worker' => [ + 'queues' => ['default'], + 'memory_limit' => 64, + 'timeout' => 60, + ], + ], + ], + [ + 'queue_workers' => [ + 'some_worker' => [], + 'another_worker' => [], + ], + ], + [ + 'queue_workers' => [ + 'some_worker' => [ + ], + 'another_worker' => [ + 'queues' => ['default', 'another'], + ], + 'yet_another_worker' => [ + 'memory_limit' => 256, + ], + 'one_more_worker' => [ + 'timeout' => 120, + ], + ], + ], +]); diff --git a/tests/Fakes/FakeQueueWorkerTest.php b/tests/Fakes/FakeQueueWorkerTest.php new file mode 100644 index 0000000..4b22f34 --- /dev/null +++ b/tests/Fakes/FakeQueueWorkerTest.php @@ -0,0 +1,69 @@ +toBeInstanceOf(QueueWorkerFake::class); +}); + +it('asserts up using callable', function () { + swap(QueueWorkerContract::class, $fake = app(QueueWorkerFake::class)); + + $fake->up(new QueueConfig('testA', ['default'], 123, 123)); + $fake->up(new QueueConfig('testB', ['default'], 123, 123)); + + $fake->assertUp(fn (QueueConfig $up) => $up->alias === 'testA'); + $fake->assertUp(fn (QueueConfig $up) => $up->alias === 'testB'); + + try { + $fake->assertUp(fn (QueueConfig $up) => $up->alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts down using string', function () { + swap(QueueWorkerContract::class, $fake = app(QueueWorkerFake::class)); + + $fake->down('testA'); + $fake->down('testB'); + + $fake->assertDown('testA'); + $fake->assertDown('testB'); + + try { + $fake->assertDown('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts down using callable', function () { + swap(QueueWorkerContract::class, $fake = app(QueueWorkerFake::class)); + + $fake->down('testA'); + $fake->down('testB'); + + $fake->assertDown(fn (string $alias) => $alias === 'testA'); + $fake->assertDown(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertDown(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); diff --git a/tests/QueueWorker/QueueWorkerTest.php b/tests/QueueWorker/QueueWorkerTest.php new file mode 100644 index 0000000..a3fbd57 --- /dev/null +++ b/tests/QueueWorker/QueueWorkerTest.php @@ -0,0 +1,39 @@ +toBe([ + '-d', + 'memory_limit=128M', + 'artisan', + 'queue:work', + "--name={$alias}", + '--queue=default', + '--memory=128', + '--timeout=61', + ]); + + expect($alias)->toBe('some_worker'); + expect($env)->toBeNull(); + expect($persistent)->toBeTrue(); + + return true; + }); +}); + +it('hits the child process with relevant alias spin down a queue worker', function () { + ChildProcess::fake(); + + QueueWorker::down('some_worker'); + + ChildProcess::assertStop('some_worker'); +}); From b719cccf84d426795b5e3418f4775cefc11104c9 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Sun, 29 Dec 2024 16:21:38 +0100 Subject: [PATCH 040/105] fix: static analysis (#452) * fix: static analysis * fix: removed warnings * fix: migrated phpunit.xml.dist --- .github/workflows/phpstan.yml | 5 +- phpunit.xml.dist | 67 ++++++++++++------------- src/Fakes/PowerMonitorFake.php | 2 +- tests/ChildProcess/ChildProcessTest.php | 1 - 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 84219d8..871c602 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -3,8 +3,9 @@ name: PHPStan on: workflow_dispatch: push: - branches-ignore: - - 'dependabot/npm_and_yarn/*' + branches: [main] + pull_request: + branches: [main] jobs: phpstan: diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8b18acd..266bd0e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,36 +1,35 @@ - - - - tests - - - - - ./src - - - - - - - - - - + + + + tests + + + + + + + + + + + + + + + ./src + + diff --git a/src/Fakes/PowerMonitorFake.php b/src/Fakes/PowerMonitorFake.php index 2af9916..caaf282 100644 --- a/src/Fakes/PowerMonitorFake.php +++ b/src/Fakes/PowerMonitorFake.php @@ -64,7 +64,7 @@ public function assertGetSystemIdleState(int|Closure $key): void $hit = empty( array_filter( $this->getSystemIdleStateCalls, - fn (string $keyIteration) => $key($keyIteration) === true + fn (int $keyIteration) => $key($keyIteration) === true ) ) === false; diff --git a/tests/ChildProcess/ChildProcessTest.php b/tests/ChildProcess/ChildProcessTest.php index 7bd13b7..c78db8c 100644 --- a/tests/ChildProcess/ChildProcessTest.php +++ b/tests/ChildProcess/ChildProcessTest.php @@ -2,7 +2,6 @@ use Illuminate\Http\Client\Request; use Illuminate\Support\Facades\Http; -use Mockery; use Native\Laravel\ChildProcess as ChildProcessImplement; use Native\Laravel\Client\Client; use Native\Laravel\Facades\ChildProcess; From a0cc73263a1a8b3c6a01d6e8b9781da315d09b92 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Sun, 29 Dec 2024 16:21:56 +0100 Subject: [PATCH 041/105] feat: default notification title (#451) --- src/Notification.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Notification.php b/src/Notification.php index 82d13d8..fe290e7 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -12,7 +12,9 @@ class Notification protected string $event = ''; - final public function __construct(protected Client $client) {} + final public function __construct(protected Client $client) { + $this->title = config('app.name'); + } public static function new() { From 4ec5c58b2fe261c6422c9e8bb0bf28d7e5b8a535 Mon Sep 17 00:00:00 2001 From: simonhamp Date: Sun, 29 Dec 2024 15:22:57 +0000 Subject: [PATCH 042/105] Fix styling --- src/Notification.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Notification.php b/src/Notification.php index fe290e7..8bde4d5 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -12,7 +12,8 @@ class Notification protected string $event = ''; - final public function __construct(protected Client $client) { + final public function __construct(protected Client $client) + { $this->title = config('app.name'); } From e700c62587b3b534d5be46834804d61cfe6a9c9b Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Sun, 29 Dec 2024 16:46:30 +0100 Subject: [PATCH 043/105] Fix menubar not ready (#453) * fix: menubar not ready * fix: menubar not ready * feat: new menubar event * Revert "fix: menubar not ready" This reverts commit 4c89091307cd69b21de429d77ee4fabc432448f9. * Revert "fix: menubar not ready" This reverts commit 345b37a291cdd442cf58d497a49467da32501786. --- src/Events/MenuBar/MenuBarCreated.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/Events/MenuBar/MenuBarCreated.php diff --git a/src/Events/MenuBar/MenuBarCreated.php b/src/Events/MenuBar/MenuBarCreated.php new file mode 100644 index 0000000..07891a1 --- /dev/null +++ b/src/Events/MenuBar/MenuBarCreated.php @@ -0,0 +1,21 @@ + Date: Sun, 29 Dec 2024 15:47:25 -0600 Subject: [PATCH 044/105] Add support for Window::show() (#454) --- src/Fakes/WindowManagerFake.php | 33 ++++++++++++++++++++++++ src/Windows/WindowManager.php | 7 ++++++ tests/Fakes/FakeWindowManagerTest.php | 36 +++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php index 8604224..64112b3 100644 --- a/src/Fakes/WindowManagerFake.php +++ b/src/Fakes/WindowManagerFake.php @@ -18,6 +18,8 @@ class WindowManagerFake implements WindowManagerContract public array $hidden = []; + public array $shown = []; + public array $forcedWindowReturnValues = []; public function __construct( @@ -64,6 +66,11 @@ public function hide($id = null) $this->hidden[] = $id; } + public function show($id = null) + { + $this->shown[] = $id; + } + public function current(): Window { $this->ensureForceReturnWindowsProvided(); @@ -156,6 +163,27 @@ public function assertHidden(string|Closure $id): void PHPUnit::assertTrue($hit); } + /** + * @param string|Closure(string): bool $id + */ + public function assertShown(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->shown); + + return; + } + + $hit = empty( + array_filter( + $this->shown, + fn (mixed $shownId) => $id($shownId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + public function assertOpenedCount(int $expected): void { PHPUnit::assertCount($expected, $this->opened); @@ -171,6 +199,11 @@ public function assertHiddenCount(int $expected): void PHPUnit::assertCount($expected, $this->hidden); } + public function assertShownCount(int $expected): void + { + PHPUnit::assertCount($expected, $this->shown); + } + private function ensureForceReturnWindowsProvided(): void { Assert::notEmpty($this->forcedWindowReturnValues, 'No windows were provided to return'); diff --git a/src/Windows/WindowManager.php b/src/Windows/WindowManager.php index 6404686..bdd7f90 100644 --- a/src/Windows/WindowManager.php +++ b/src/Windows/WindowManager.php @@ -31,6 +31,13 @@ public function hide($id = null) ]); } + public function show($id = null) + { + $this->client->post('window/show', [ + 'id' => $id ?? $this->detectId(), + ]); + } + public function current(): Window { $window = (object) $this->client->get('window/current')->json(); diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php index b2f4af2..f6c4804 100644 --- a/tests/Fakes/FakeWindowManagerTest.php +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -136,6 +136,24 @@ $this->fail('Expected assertion to fail'); }); +it('asserts that a window was shown', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->show('main'); + app(WindowManagerContract::class)->show('secondary'); + + $fake->assertShown('main'); + $fake->assertShown('secondary'); + + try { + $fake->assertShown('tertiary'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + it('asserts opened count', function () { Http::fake(['*' => Http::response(status: 200)]); @@ -196,6 +214,24 @@ $this->fail('Expected assertion to fail'); }); +it('asserts shown count', function () { + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->show('main'); + app(WindowManagerContract::class)->show(); + app(WindowManagerContract::class)->show(); + + $fake->assertShownCount(3); + + try { + $fake->assertShownCount(4); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + it('forces the return value of current window', function () { swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); From 15b01f51d2fbbe849cba0d5c9d7619b1e9f9f1d0 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Mon, 30 Dec 2024 20:53:34 +0000 Subject: [PATCH 045/105] Remove sponsor --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4de29bb..a233cdd 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Thanks to the following sponsors for funding NativePHP development. Please consi - [Laradevs](https://laradevs.com/?ref=nativephp-docs) - Connecting the best Laravel Developers with the best Laravel Teams. - [RedGalaxy](https://www.redgalaxy.co.uk) - A web application development studio based in Cambridgeshire, building solutions to help businesses improve efficiency and profitability. - [Sevalla](https://sevalla.com/?utm_source=nativephp&utm_medium=Referral&utm_campaign=homepage) - Host and manage your applications, databases, and static sites in a single, intuitive platform. -- [ServerAuth](https://serverauth.com) - Website Deployment & Server Management, made simple! - [KaasHosting](https://www.kaashosting.nl/?lang=en) - Minecraft Server and VPS hosting from The Netherlands. ## Changelog From 2bd28872d07f7b2066d4af18a55e41f562b08ed4 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sat, 11 Jan 2025 23:00:50 +0000 Subject: [PATCH 046/105] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a233cdd..45e26d3 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Thanks to the following sponsors for funding NativePHP development. Please consi - [RedGalaxy](https://www.redgalaxy.co.uk) - A web application development studio based in Cambridgeshire, building solutions to help businesses improve efficiency and profitability. - [Sevalla](https://sevalla.com/?utm_source=nativephp&utm_medium=Referral&utm_campaign=homepage) - Host and manage your applications, databases, and static sites in a single, intuitive platform. - [KaasHosting](https://www.kaashosting.nl/?lang=en) - Minecraft Server and VPS hosting from The Netherlands. +- [Borah Digital Labs](https://borah.digital/) - An MVP building studio from the sunny Canary Islands focusing on AI, SaaS and online platforms. ## Changelog From 36abbb6953dcb325f0ec5b498fc0650db6d12b18 Mon Sep 17 00:00:00 2001 From: Chikondi Kamwendo Date: Sun, 12 Jan 2025 01:12:32 +0200 Subject: [PATCH 047/105] Fix: return type mismatch between screen facade and screen class methods. (#463) --- src/Screen.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Screen.php b/src/Screen.php index c15d4e1..9f34c10 100644 --- a/src/Screen.php +++ b/src/Screen.php @@ -18,12 +18,12 @@ public function displays(): array return $this->client->get('screen/displays')->json('displays'); } - public function primary(): object + public function primary(): array { return $this->client->get('screen/primary-display')->json('primaryDisplay'); } - public function active(): object + public function active(): array { return $this->client->get('screen/active')->json(); } From 34bcfd84b1b74ed6d35cbea86578577b8dfe9e62 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Thu, 16 Jan 2025 17:44:03 +0100 Subject: [PATCH 048/105] fix: Dock facade (#470) --- src/Facades/Dock.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Facades/Dock.php b/src/Facades/Dock.php index b088219..ddeaffb 100644 --- a/src/Facades/Dock.php +++ b/src/Facades/Dock.php @@ -6,8 +6,8 @@ use Native\Laravel\Menu\Menu; /** - * @method static void bounce() - * @method static void|string badge(string $type = null) + * @method static void bounce(string $type = 'informational') + * @method static void|string badge(?string $type = null) * @method static void cancelBounce() * @method static void hide() * @method static void icon(string $Path) From 4235d2c17cd177ac031efb3e4e81405077cda140 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Thu, 16 Jan 2025 17:44:29 +0100 Subject: [PATCH 049/105] fix: child process facade (#471) --- src/Facades/ChildProcess.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facades/ChildProcess.php b/src/Facades/ChildProcess.php index 0436507..d6793c8 100644 --- a/src/Facades/ChildProcess.php +++ b/src/Facades/ChildProcess.php @@ -8,7 +8,7 @@ /** * @method static \Native\Laravel\ChildProcess[] all() - * @method static \Native\Laravel\ChildProcess get(string $alias = null) + * @method static \Native\Laravel\ChildProcess|null get(string $alias = null) * @method static \Native\Laravel\ChildProcess message(string $message, string $alias = null) * @method static \Native\Laravel\ChildProcess restart(string $alias = null) * @method static \Native\Laravel\ChildProcess start(string|array $cmd, string $alias, string $cwd = null, array $env = null, bool $persistent = false) From d643e194b92f47369dd348de1d7baa29976914a4 Mon Sep 17 00:00:00 2001 From: simonhamp <31628+simonhamp@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:38:19 +0000 Subject: [PATCH 050/105] Update CHANGELOG --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c33f8d..1e89890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to `nativephp-laravel` will be documented in this file. +## 1.0.0-beta.1 - 2025-01-21 + +### What's Changed + +* Child process queue workers by @XbNz in https://github.com/NativePHP/laravel/pull/450 +* fix: static analysis by @SRWieZ in https://github.com/NativePHP/laravel/pull/452 +* feat: default notification title by @SRWieZ in https://github.com/NativePHP/laravel/pull/451 +* Fix menubar not ready by @SRWieZ in https://github.com/NativePHP/laravel/pull/453 +* Add support for Window::show() by @curtisblackwell in https://github.com/NativePHP/laravel/pull/454 +* Fix: Return type mismatch between screen facade and screen class methods. by @kondi3 in https://github.com/NativePHP/laravel/pull/463 + +### New Contributors + +* @kondi3 made their first contribution in https://github.com/NativePHP/laravel/pull/463 + +**Full Changelog**: https://github.com/NativePHP/laravel/compare/0.7.0...0.8.0 + ## 0.7.0 - 2024-12-19 ### What's Changed From 1fe65da9dba54d2d5e7e959b50fc2a9a93a5546b Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Wed, 22 Jan 2025 08:42:50 +0100 Subject: [PATCH 051/105] Add queue worker config docblock --- config/nativephp.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/nativephp.php b/config/nativephp.php index 5c6e7a8..da17b0b 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -115,6 +115,9 @@ ], ], + /** + * The queue workers that get auto-started on your application start. + */ 'queue_workers' => [ 'default' => [ 'queues' => ['default'], From 597e0dc0056e6356cf38149cc3e8e2c450604c52 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 23 Jan 2025 10:58:18 +0000 Subject: [PATCH 052/105] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45e26d3..4308c95 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Please review [our security policy](../../security/policy) on how to report secu ## Credits - [Marcel Pociot](https://github.com/mpociot) +- [Simon Hamp](https://github.com/simonhamp) - [All Contributors](../../contributors) ## License From 3ccd85ed1c294ca76bd50c60b9d539fce1bbce5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:35:48 +0000 Subject: [PATCH 053/105] Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.2.0 to 2.3.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.2.0...v2.3.0) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index eb537d8..2cb1d5e 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.2.0 + uses: dependabot/fetch-metadata@v2.3.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" From 7eae6d8e115608cfa27c2cc571675dc1c3f241eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 00:36:22 +0000 Subject: [PATCH 054/105] Bump aglipanci/laravel-pint-action from 2.4 to 2.5 Bumps [aglipanci/laravel-pint-action](https://github.com/aglipanci/laravel-pint-action) from 2.4 to 2.5. - [Release notes](https://github.com/aglipanci/laravel-pint-action/releases) - [Commits](https://github.com/aglipanci/laravel-pint-action/compare/2.4...2.5) --- updated-dependencies: - dependency-name: aglipanci/laravel-pint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 255506b..0edbd38 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -19,7 +19,7 @@ jobs: ref: ${{ github.head_ref }} - name: Fix PHP code style issues - uses: aglipanci/laravel-pint-action@2.4 + uses: aglipanci/laravel-pint-action@2.5 - name: Commit changes uses: stefanzweifel/git-auto-commit-action@v5 From 1f8831baabf8d169bd139a6877814a9f6d9eaba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20K=C3=A4llstrand=20Modig?= Date: Thu, 13 Feb 2025 15:39:54 +0100 Subject: [PATCH 055/105] Add endpoint to resize MenuBar window (#490) --- src/Facades/MenuBar.php | 4 ++++ src/MenuBar/MenuBarManager.php | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Facades/MenuBar.php b/src/Facades/MenuBar.php index 97abad9..15d9879 100644 --- a/src/Facades/MenuBar.php +++ b/src/Facades/MenuBar.php @@ -10,6 +10,9 @@ * @method static void show() * @method static void hide() * @method static void label(string $label) + * @method static void tooltip(string $label) + * @method static void icon(string $icon) + * @method static void resize(int $width, int $height) * @method static void contextMenu(Menu $contextMenu) */ class MenuBar extends Facade @@ -19,3 +22,4 @@ protected static function getFacadeAccessor() return \Native\Laravel\MenuBar\MenuBarManager::class; } } + diff --git a/src/MenuBar/MenuBarManager.php b/src/MenuBar/MenuBarManager.php index b10992d..f9c2bde 100644 --- a/src/MenuBar/MenuBarManager.php +++ b/src/MenuBar/MenuBarManager.php @@ -45,6 +45,14 @@ public function icon(string $icon) ]); } + public function resize(int $width, int $height) + { + $this->client->post('menu-bar/resize', [ + 'width' => $width, + 'height' => $height, + ]); + } + public function contextMenu(Menu $contextMenu) { $this->client->post('menu-bar/context-menu', [ From 182c5d86fa272df01b7b9c8345419f7ef6c7bfe0 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 17 Feb 2025 16:52:09 +0100 Subject: [PATCH 056/105] feat: add missing facades into composer.json (#495) --- composer.json | 16 ++++++++++++++-- src/Facades/MenuBar.php | 1 - 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 9f7b760..850270b 100644 --- a/composer.json +++ b/composer.json @@ -85,11 +85,23 @@ "Native\\Laravel\\NativeServiceProvider" ], "aliases": { + "App": "Native\\Laravel\\Facades\\App", + "ChildProcess": "Native\\Laravel\\Facades\\ChildProcess", + "Clipboard": "Native\\Laravel\\Facades\\Clipboard", "ContextMenu": "Native\\Laravel\\Facades\\ContextMenu", "Dock": "Native\\Laravel\\Facades\\Dock", + "GlobalShortcut": "Native\\Laravel\\Facades\\GlobalShortcut", + "Menu": "Native\\Laravel\\Facades\\Menu", + "MenuBar": "Native\\Laravel\\Facades\\MenuBar", + "Notification": "Native\\Laravel\\Facades\\Notification", + "PowerMonitor": "Native\\Laravel\\Facades\\PowerMonitor", "Process": "Native\\Laravel\\Facades\\Process", - "Window": "Native\\Laravel\\Facades\\Window", - "Clipboard": "Native\\Laravel\\Facades\\Clipboard" + "QueueWorker": "Native\\Laravel\\Facades\\QueueWorker", + "Screen": "Native\\Laravel\\Facades\\Screen", + "Settings": "Native\\Laravel\\Facades\\Settings", + "Shell": "Native\\Laravel\\Facades\\Shell", + "System": "Native\\Laravel\\Facades\\System", + "Window": "Native\\Laravel\\Facades\\Window" } } }, diff --git a/src/Facades/MenuBar.php b/src/Facades/MenuBar.php index 15d9879..b1d4972 100644 --- a/src/Facades/MenuBar.php +++ b/src/Facades/MenuBar.php @@ -22,4 +22,3 @@ protected static function getFacadeAccessor() return \Native\Laravel\MenuBar\MenuBarManager::class; } } - From dfcd9e29958ade01c693d2264a2e96b91e3a4a6a Mon Sep 17 00:00:00 2001 From: Andreas Creten Date: Fri, 21 Feb 2025 15:24:51 +0100 Subject: [PATCH 057/105] Notification improvements (#498) * Add support for notification reference * Add reference to Notification clicked class * Support for more notification actions * Make reference public * Support for actions and reply * Add missing methods to the facade * Properly send the actions --- .../NotificationActionClicked.php | 23 ++++++++++ .../Notifications/NotificationClicked.php | 2 + .../Notifications/NotificationClosed.php | 23 ++++++++++ .../Notifications/NotificationReply.php | 23 ++++++++++ src/Facades/Notification.php | 3 ++ src/Notification.php | 45 ++++++++++++++++++- 6 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/Events/Notifications/NotificationActionClicked.php create mode 100644 src/Events/Notifications/NotificationClosed.php create mode 100644 src/Events/Notifications/NotificationReply.php diff --git a/src/Events/Notifications/NotificationActionClicked.php b/src/Events/Notifications/NotificationActionClicked.php new file mode 100644 index 0000000..de658b3 --- /dev/null +++ b/src/Events/Notifications/NotificationActionClicked.php @@ -0,0 +1,23 @@ +title = config('app.name'); @@ -22,6 +30,13 @@ public static function new() return new static(new Client); } + public function reference(string $reference): self + { + $this->reference = $reference; + + return $this; + } + public function title(string $title): self { $this->title = $title; @@ -36,6 +51,21 @@ public function event(string $event): self return $this; } + public function hasReply(string $placeholder = ''): self + { + $this->hasReply = true; + $this->replyPlaceholder = $placeholder; + + return $this; + } + + public function addAction(string $label): self + { + $this->actions[] = $label; + + return $this; + } + public function message(string $body): self { $this->body = $body; @@ -43,12 +73,23 @@ public function message(string $body): self return $this; } - public function show(): void + public function show(): self { - $this->client->post('notification', [ + $response = $this->client->post('notification', [ + 'reference' => $this->reference, 'title' => $this->title, 'body' => $this->body, 'event' => $this->event, + 'hasReply' => $this->hasReply, + 'replyPlaceholder' => $this->replyPlaceholder, + 'actions' => array_map(fn(string $label) => [ + 'type' => 'button', + 'text' => $label + ], $this->actions), ]); + + $this->reference = $response->json('reference'); + + return $this; } } From 2844fe831b4fb2b1b9021a7b7ef169ecb2f6597e Mon Sep 17 00:00:00 2001 From: Willem Leuverink Date: Fri, 21 Feb 2025 20:52:59 +0100 Subject: [PATCH 058/105] Update cleanup directory defaults (#467) * update cleanup files defaults * add default cleanup_exclude_files to internal config * remove vendor/bin & add */tests to exclude list * remove MinifyApplication command * remove php compactor * moved internal cleanup_exclude files back again This is hard to test when the config I'm asserting on lives on a different repo from the test itself. --- config/nativephp.php | 4 +- src/Commands/MinifyApplicationCommand.php | 111 ------------- src/Compactor/Php.php | 189 ---------------------- src/NativeServiceProvider.php | 2 - 4 files changed, 2 insertions(+), 304 deletions(-) delete mode 100644 src/Commands/MinifyApplicationCommand.php delete mode 100644 src/Compactor/Php.php diff --git a/config/nativephp.php b/config/nativephp.php index da17b0b..45f8225 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -61,8 +61,8 @@ */ 'cleanup_exclude_files' => [ 'content', - 'storage/app/framework/{sessions,testing,cache}', - 'storage/logs/laravel.log', + 'node_modules', + '*/tests' ], /** diff --git a/src/Commands/MinifyApplicationCommand.php b/src/Commands/MinifyApplicationCommand.php deleted file mode 100644 index 37d8378..0000000 --- a/src/Commands/MinifyApplicationCommand.php +++ /dev/null @@ -1,111 +0,0 @@ -argument('app')); - - if (! is_dir($appPath)) { - $this->error('The app path is not a directory'); - - return; - } - - $this->info('Minifying application…'); - - $this->cleanUpEnvFile($appPath); - $this->removeIgnoredFilesAndFolders($appPath); - - $compactor = new Php; - - $phpFiles = Finder::create() - ->files() - ->name('*.php') - ->in($appPath); - - foreach ($phpFiles as $phpFile) { - $minifiedContent = $compactor->compact($phpFile->getRealPath(), $phpFile->getContents()); - file_put_contents($phpFile->getRealPath(), $minifiedContent); - } - } - - protected function cleanUpEnvFile(string $appPath): void - { - $envFile = $appPath.'/.env'; - - if (! file_exists($envFile)) { - return; - } - - $this->info('Cleaning up .env file…'); - - $cleanUpKeys = config('nativephp.cleanup_env_keys', []); - - $envContent = file_get_contents($envFile); - $envValues = collect(explode("\n", $envContent)) - ->filter(function (string $line) use ($cleanUpKeys) { - $key = Str::before($line, '='); - - return ! Str::is($cleanUpKeys, $key); - }) - ->join("\n"); - - file_put_contents($envFile, $envValues); - } - - protected function removeIgnoredFilesAndFolders(string $appPath): void - { - $this->info('Cleaning up ignored files and folders…'); - - $itemsToRemove = config('nativephp.cleanup_exclude_files', []); - - foreach ($itemsToRemove as $item) { - $fullPath = $appPath.'/'.$item; - - if (file_exists($fullPath)) { - if (is_dir($fullPath)) { - $this->deleteDirectoryRecursive($fullPath); - } else { - array_map('unlink', glob($fullPath)); - } - } else { - foreach (glob($item) as $pathFound) { - unlink($pathFound); - } - } - } - } - - private function deleteDirectoryRecursive(string $directory): bool - { - if (! file_exists($directory)) { - return true; - } - - if (! is_dir($directory)) { - return unlink($directory); - } - - foreach (scandir($directory) as $item) { - if ($item == '.' || $item == '..') { - continue; - } - - if (! $this->deleteDirectoryRecursive($directory.'/'.$item)) { - return false; - } - } - - return rmdir($directory); - } -} diff --git a/src/Compactor/Php.php b/src/Compactor/Php.php deleted file mode 100644 index 7b5d3ad..0000000 --- a/src/Compactor/Php.php +++ /dev/null @@ -1,189 +0,0 @@ -canProcessFile($file)) { - return $this->compactContent($contents); - } - - return $this->compactContent($contents); - } - - protected function compactContent(string $contents): string - { - $output = ''; - $tokens = PhpToken::tokenize($contents); - $tokenCount = count($tokens); - - for ($index = 0; $index < $tokenCount; $index++) { - $token = $tokens[$index]; - $tokenText = $token->text; - - if ($token->is([T_COMMENT, T_DOC_COMMENT])) { - if (str_starts_with($tokenText, '#[')) { - // This is, in all likelihood, the start of a PHP >= 8.0 attribute. - // Note: $tokens may be updated by reference as well! - $retokenized = $this->retokenizeAttribute($tokens, $index); - - if ($retokenized !== null) { - array_splice($tokens, $index, 1, $retokenized); - $tokenCount = count($tokens); - } - - $attributeCloser = self::findAttributeCloser($tokens, $index); - - if (is_int($attributeCloser)) { - $output .= '#['; - } else { - // Turns out this was not an attribute. Treat it as a plain comment. - $output .= str_repeat("\n", mb_substr_count($tokenText, "\n")); - } - } elseif (str_contains($tokenText, '@')) { - try { - $output .= $this->compactAnnotations($tokenText); - } catch (RuntimeException) { - $output .= $tokenText; - } - } else { - $output .= str_repeat("\n", mb_substr_count($tokenText, "\n")); - } - } elseif ($token->is(T_WHITESPACE)) { - $whitespace = $tokenText; - $previousIndex = ($index - 1); - - // Handle whitespace potentially being split into two tokens after attribute retokenization. - $nextToken = $tokens[$index + 1] ?? null; - - if ($nextToken !== null - && $nextToken->is(T_WHITESPACE) - ) { - $whitespace .= $nextToken->text; - $index++; - } - - // reduce wide spaces - $whitespace = preg_replace('{[ \t]+}', ' ', $whitespace); - - // normalize newlines to \n - $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); - - // If the new line was split off from the whitespace token due to it being included in - // the previous (comment) token (PHP < 8), remove leading spaces. - - $previousToken = $tokens[$previousIndex]; - - if ($previousToken->is(T_COMMENT) - && str_contains($previousToken->text, "\n") - ) { - $whitespace = ltrim($whitespace, ' '); - } - - // trim leading spaces - $whitespace = preg_replace('{\n +}', "\n", $whitespace); - - $output .= $whitespace; - } else { - $output .= $tokenText; - } - } - - return $output; - } - - private function compactAnnotations(string $docblock): string - { - return $docblock; - } - - /** - * @param list $tokens - */ - private static function findAttributeCloser(array $tokens, int $opener): ?int - { - $tokenCount = count($tokens); - $brackets = [$opener]; - $closer = null; - - for ($i = ($opener + 1); $i < $tokenCount; $i++) { - $tokenText = $tokens[$i]->text; - - // Allow for short arrays within attributes. - if ($tokenText === '[') { - $brackets[] = $i; - - continue; - } - - if ($tokenText === ']') { - array_pop($brackets); - - if (count($brackets) === 0) { - $closer = $i; - break; - } - } - } - - return $closer; - } - - /** - * @param non-empty-list $tokens - */ - private function retokenizeAttribute(array &$tokens, int $opener): ?array - { - Assert::keyExists($tokens, $opener); - - $token = $tokens[$opener]; - $attributeBody = mb_substr($token->text, 2); - $subTokens = PhpToken::tokenize('text; - } - - $subTokens = PhpToken::tokenize('hasConfigFile() ->hasRoute('api') From dc1d2f1d69a13bf50829dd055b2c21f43465c2c9 Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Sat, 22 Feb 2025 14:41:53 +0000 Subject: [PATCH 059/105] Implement default config for pre and post build keys (#496) --- config/nativephp.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/nativephp.php b/config/nativephp.php index 45f8225..8c62e00 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -125,4 +125,15 @@ 'timeout' => 60, ], ], + + /** + * Define your own scripts to run before and after the build process. + */ + 'prebuild' => [ + // 'npm run build', + ], + + 'postbuild' => [ + // 'rm -rf public/build', + ], ]; From a2183f2f1196286edc101ae5334c6f67a1c949f3 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 24 Feb 2025 14:04:19 +0100 Subject: [PATCH 060/105] chore: delete outdated tests (#500) --- config/nativephp.php | 2 +- src/Notification.php | 4 +- tests/Command/IgnoreFilesAndFoldersTest.php | 88 --------------------- 3 files changed, 3 insertions(+), 91 deletions(-) delete mode 100644 tests/Command/IgnoreFilesAndFoldersTest.php diff --git a/config/nativephp.php b/config/nativephp.php index 8c62e00..6e4edcf 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -62,7 +62,7 @@ 'cleanup_exclude_files' => [ 'content', 'node_modules', - '*/tests' + '*/tests', ], /** diff --git a/src/Notification.php b/src/Notification.php index a1da484..578d860 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -82,9 +82,9 @@ public function show(): self 'event' => $this->event, 'hasReply' => $this->hasReply, 'replyPlaceholder' => $this->replyPlaceholder, - 'actions' => array_map(fn(string $label) => [ + 'actions' => array_map(fn (string $label) => [ 'type' => 'button', - 'text' => $label + 'text' => $label, ], $this->actions), ]); diff --git a/tests/Command/IgnoreFilesAndFoldersTest.php b/tests/Command/IgnoreFilesAndFoldersTest.php deleted file mode 100644 index e805cc0..0000000 --- a/tests/Command/IgnoreFilesAndFoldersTest.php +++ /dev/null @@ -1,88 +0,0 @@ -artisan('native:minify resources/app'); - $this->assertFalse(file_exists($laravelLog)); - - // Clean up after ourselves - if (file_exists($laravelLog)) { - unlink($laravelLog); - } - if (file_exists('resources/app/storage/logs')) { - rmdir('resources/app/storage/logs'); - } - if (file_exists('resources/app/storage')) { - rmdir('resources/app/storage'); - } - removeAppFolder(); -}); - -it('will remove the content folder by default before building', function () { - $contentPath = 'resources/app/content'; - - // Create a dummy copy of the folder - if (! file_exists($contentPath)) { - mkdir($contentPath, 0755, true); - } - - // Run the test - $this->artisan('native:minify resources/app'); - $this->assertFalse(file_exists($contentPath)); - - // Clean up after ourselves - if (file_exists($contentPath)) { - unlink($contentPath); - } - removeAppFolder(); -}); - -it('will remove only files that match a globbed path', function () { - $wildcardPath = 'resources/app/wildcardPath'; - $yes1DeletePath = $wildcardPath.'/YES1.txt'; - $yes2DeletePath = $wildcardPath.'/YES2.txt'; - $noDeletePath = $wildcardPath.'/NO.txt'; - - config()->set('nativephp.cleanup_exclude_files', [$wildcardPath.'/YES*']); - - // Create some dummy files - if (! file_exists($wildcardPath)) { - mkdir($wildcardPath, 0755, true); - } - file_put_contents($yes1DeletePath, 'PLEASE DELETE ME'); - file_put_contents($yes2DeletePath, 'PLEASE DELETE ME TOO'); - file_put_contents($noDeletePath, 'DO NOT DELETE ME'); - - // Run the test - $this->artisan('native:minify resources/app'); - $this->assertFalse(file_exists($yes1DeletePath)); - $this->assertFalse(file_exists($yes2DeletePath)); - $this->assertTrue(file_exists($noDeletePath)); - - // Clean up after ourselves - foreach ([$yes1DeletePath, $yes2DeletePath, $noDeletePath] as $remove) { - if (file_exists($remove)) { - unlink($remove); - } - } - if (file_exists($wildcardPath)) { - rmdir($wildcardPath); - } - removeAppFolder(); -}); - -function removeAppFolder() -{ - if (file_exists('resources/app')) { - rmdir('resources/app'); - } -} From cea2b0d606d16db831c42eed33a9e376e86dfcdc Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 24 Feb 2025 16:11:28 +0100 Subject: [PATCH 061/105] fix: linting workflow (#499) --- .../workflows/fix-php-code-style-issues.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 0edbd38..eb79b6e 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -1,9 +1,15 @@ -name: Fix PHP code style issues +# Check and fix PHP code style issues +# Pull request: automatically fix PHP code style issues +# Main branch: only check PHP code style issues since we don't have write permission +name: Check and fix PHP code style issues on: push: paths: - '**.php' + pull_request: + paths: + - '**.php' permissions: contents: write @@ -18,10 +24,20 @@ jobs: with: ref: ${{ github.head_ref }} + - name: Check PHP code style issues + if: github.event_name == 'push' + uses: aglipanci/laravel-pint-action@2.5 + with: + verboseMode: true + testMode: true + - name: Fix PHP code style issues + if: github.event_name == 'pull_request' uses: aglipanci/laravel-pint-action@2.5 - name: Commit changes + if: github.event_name == 'pull_request' uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Fix styling + From 0ff6ae6042a60450d3086d21073f902e5ad3d726 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 24 Feb 2025 16:11:38 +0100 Subject: [PATCH 062/105] fix: deprecated error in tests (#501) --- tests/DTOs/QueueWorkerTest.php | 56 ++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/tests/DTOs/QueueWorkerTest.php b/tests/DTOs/QueueWorkerTest.php index bc1b764..76209c8 100644 --- a/tests/DTOs/QueueWorkerTest.php +++ b/tests/DTOs/QueueWorkerTest.php @@ -17,8 +17,10 @@ )->queuesToConsume->toBe(['default'] ); - expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)))->memoryLimit->toBe(128); - expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)))->timeout->toBe(60); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $worker)))->memoryLimit->toBe(128); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $worker)))->timeout->toBe(60); continue; } @@ -29,37 +31,45 @@ )->queuesToConsume->toBe($worker['queues'] ?? ['default'] ); - expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)))->memoryLimit->toBe($worker['memory_limit'] ?? 128); - expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)))->timeout->toBe($worker['timeout'] ?? 60); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $alias)))->memoryLimit->toBe($worker['memory_limit'] ?? 128); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $alias)))->timeout->toBe($worker['timeout'] ?? 60); } })->with([ [ - 'queue_workers' => [ - 'some_worker' => [ - 'queues' => ['default'], - 'memory_limit' => 64, - 'timeout' => 60, + [ + 'queue_workers' => [ + 'some_worker' => [ + 'queues' => ['default'], + 'memory_limit' => 64, + 'timeout' => 60, + ], ], ], ], [ - 'queue_workers' => [ - 'some_worker' => [], - 'another_worker' => [], + [ + 'queue_workers' => [ + 'some_worker' => [], + 'another_worker' => [], + ], ], ], [ - 'queue_workers' => [ - 'some_worker' => [ - ], - 'another_worker' => [ - 'queues' => ['default', 'another'], - ], - 'yet_another_worker' => [ - 'memory_limit' => 256, - ], - 'one_more_worker' => [ - 'timeout' => 120, + [ + 'queue_workers' => [ + 'some_worker' => [ + ], + 'another_worker' => [ + 'queues' => ['default', 'another'], + ], + 'yet_another_worker' => [ + 'memory_limit' => 256, + ], + 'one_more_worker' => [ + 'timeout' => 120, + ], ], ], ], From 0102a92c7eec291bf12e1477a31a59f67698034b Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 24 Feb 2025 17:00:58 +0100 Subject: [PATCH 063/105] Support Laravel 12.x & PHP 8.4 (#494) --- .github/workflows/run-tests.yml | 27 ++++++---------- composer.json | 18 +++++------ phpstan.neon | 9 ++++-- src/Commands/LoadPHPConfigurationCommand.php | 2 ++ src/Concerns/InteractsWithNativeApp.php | 34 -------------------- 5 files changed, 27 insertions(+), 63 deletions(-) delete mode 100644 src/Concerns/InteractsWithNativeApp.php diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 107761b..a1909f6 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,28 +10,18 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: - fail-fast: true + fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - php: [8.3, 8.2, 8.1] - laravel: [11.*, 10.*] + php: [8.4, 8.3, 8.2, 8.1] + laravel: [12.*, 11.*, 10.*] stability: [prefer-lowest, prefer-stable] - include: - - laravel: 11.* - testbench: 9.* - carbon: ^3.2 - laravel-package-tools: ^1.16.4 - collision: ^8.1.1 - - - laravel: 10.* - testbench: 8.* - carbon: ^2.63 - laravel-package-tools: ^1.16.4 - collision: 7.* exclude: - laravel: 11.* php: 8.1 + - laravel: 12.* + php: 8.1 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} @@ -53,11 +43,14 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" "spatie/laravel-package-tools:${{ matrix.laravel-package-tools }}" "nunomaduro/collision:${{ matrix.collision }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction - name: List Installed Dependencies - run: composer show -D + run: composer show + +# - name: Debug PhpUnit version +# run: composer why phpunit/phpunit -t - name: Execute tests run: vendor/bin/pest diff --git a/composer.json b/composer.json index 850270b..71336ed 100644 --- a/composer.json +++ b/composer.json @@ -32,22 +32,22 @@ ], "require": { "php": "^8.1", - "illuminate/contracts": "^10.0|^11.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", "spatie/laravel-package-tools": "^1.16.4", "symfony/finder": "^6.2|^7.0" }, "require-dev": { "guzzlehttp/guzzle": "^7.0", - "larastan/larastan": "^2.0|^3.0", "laravel/pint": "^1.0", - "nunomaduro/collision": "^7.9", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^2.0", - "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", + "larastan/larastan": "^2.0|^3.1", + "nunomaduro/collision": "^7.11|^8.1.1", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "pestphp/pest": "^v2.30|^3.0", + "pestphp/pest-plugin-arch": "^2.0|^3.0", + "pestphp/pest-plugin-laravel": "^2.0|^3.1", "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", "spatie/laravel-ray": "^1.26" }, "autoload": { diff --git a/phpstan.neon b/phpstan.neon index 33be1e3..b6543b0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,10 +9,13 @@ parameters: # Level 9 is the highest level level: 5 + + noEnvCallsOutsideOfConfig: false # Don't know why he doesn't consider our config/ directory as config + ignoreErrors: - '#Class App\\Providers\\NativeAppServiceProvider not found#' - '#Class Native\\Laravel\\ChildProcess has an uninitialized readonly property#' -# -# excludePaths: -# - ./*/*/FileToBeExcluded.php + + excludePaths: + - ./src/NativeServiceProvider.php diff --git a/src/Commands/LoadPHPConfigurationCommand.php b/src/Commands/LoadPHPConfigurationCommand.php index b3b7a71..1a2ba57 100644 --- a/src/Commands/LoadPHPConfigurationCommand.php +++ b/src/Commands/LoadPHPConfigurationCommand.php @@ -14,6 +14,8 @@ public function handle() /** @var ProvidesPhpIni $provider */ $provider = app(config('nativephp.provider')); $phpIni = []; + + /* * @phpstan-ignore-next-line */ if (method_exists($provider, 'phpIni')) { $phpIni = $provider->phpIni(); } diff --git a/src/Concerns/InteractsWithNativeApp.php b/src/Concerns/InteractsWithNativeApp.php deleted file mode 100644 index 7dd6ee7..0000000 --- a/src/Concerns/InteractsWithNativeApp.php +++ /dev/null @@ -1,34 +0,0 @@ -start(); - - if (is_iterable($totalSteps)) { - foreach ($totalSteps as $value) { - $callback($value, $bar); - - $bar->advance(); - } - } else { - $callback($bar); - } - - $bar->finish(); - - if (is_iterable($totalSteps)) { - return $totalSteps; - } - } -} From 212f11b7b9590c6cb4a0ed16ef11f27310a3677d Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 4 Mar 2025 14:12:45 +0100 Subject: [PATCH 064/105] feat: zephpyr configs --- config/nativephp-internal.php | 9 +++++++++ config/nativephp.php | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/config/nativephp-internal.php b/config/nativephp-internal.php index 4210df9..a9d97c7 100644 --- a/config/nativephp-internal.php +++ b/config/nativephp-internal.php @@ -29,4 +29,13 @@ * The URL to the NativePHP API. */ 'api_url' => env('NATIVEPHP_API_URL', 'http://localhost:4000/api/'), + + /** + * Configuration for the Zephpyr API. + */ + 'zephpyr' => [ + 'host' => env('ZEPHPYR_HOST', 'https://zephpyr.com'), + 'token' => env('ZEPHPYR_TOKEN'), + 'key' => env('ZEPHPYR_KEY'), + ], ]; diff --git a/config/nativephp.php b/config/nativephp.php index 6e4edcf..49eacd8 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -48,6 +48,7 @@ 'GITHUB_*', 'DO_SPACES_*', '*_SECRET', + 'ZEPHPYR_*', 'NATIVEPHP_UPDATER_PATH', 'NATIVEPHP_APPLE_ID', 'NATIVEPHP_APPLE_ID_PASS', @@ -60,6 +61,8 @@ * You may use glob / wildcard patterns here. */ 'cleanup_exclude_files' => [ + 'build', + 'temp', 'content', 'node_modules', '*/tests', @@ -136,4 +139,9 @@ 'postbuild' => [ // 'rm -rf public/build', ], + + /** + * Custom PHP binary path. + */ + 'binary_path' => env('NATIVEPHP_BINARY_PATH', null), ]; From 334b5dedfeb901763906a1ff128c432bb51f546e Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 4 Mar 2025 14:14:12 +0100 Subject: [PATCH 065/105] fix: don't create database inside a phar --- src/NativeServiceProvider.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index b5b3225..1c13403 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -25,6 +25,7 @@ use Native\Laravel\Logging\LogWatcher; use Native\Laravel\PowerMonitor as PowerMonitorImplementation; use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; +use Phar; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -148,7 +149,8 @@ public function rewriteDatabase() { $databasePath = config('nativephp-internal.database_path'); - if (config('app.debug')) { + // Automatically create the database in development mode but not if we are running in a Phar + if (config('app.debug') && ! Phar::running()) { $databasePath = database_path('nativephp.sqlite'); if (! file_exists($databasePath)) { From 6103ed50222679e48e8672625c59e6bf63b9530c Mon Sep 17 00:00:00 2001 From: SRWieZ <1408020+SRWieZ@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:14:59 +0000 Subject: [PATCH 066/105] Fix styling --- src/NativeServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 1c13403..526c8cf 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -150,7 +150,7 @@ public function rewriteDatabase() $databasePath = config('nativephp-internal.database_path'); // Automatically create the database in development mode but not if we are running in a Phar - if (config('app.debug') && ! Phar::running()) { + if (config('app.debug') && ! Phar::running()) { $databasePath = database_path('nativephp.sqlite'); if (! file_exists($databasePath)) { From 15d04bd9705abad0349ace2453ac0a72d5964536 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Mon, 10 Mar 2025 12:46:53 +0100 Subject: [PATCH 067/105] feat: isRunningBundled --- src/App.php | 6 ++++++ src/Facades/App.php | 1 + 2 files changed, 7 insertions(+) diff --git a/src/App.php b/src/App.php index 81f1a26..b45dd11 100644 --- a/src/App.php +++ b/src/App.php @@ -3,6 +3,7 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Phar; class App { @@ -57,4 +58,9 @@ public function clearRecentDocuments(): void { $this->client->delete('app/recent-documents'); } + + public function isRunningBundled(): bool + { + return Phar::running() !== ''; + } } diff --git a/src/Facades/App.php b/src/Facades/App.php index 6f3d99a..4bbf331 100644 --- a/src/Facades/App.php +++ b/src/Facades/App.php @@ -13,6 +13,7 @@ * @method static void addRecentDocument(string $path) * @method static array recentDocuments() * @method static void clearRecentDocuments() + * @method static bool isRunningBundled() */ class App extends Facade { From 35ab017a6e5c54ed75d766413695b92b75211a26 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 11 Mar 2025 14:16:56 +0100 Subject: [PATCH 068/105] wip: fix running artisan commands in bundle --- src/QueueWorker.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QueueWorker.php b/src/QueueWorker.php index 1eb0c00..3a820f1 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -24,11 +24,11 @@ public function up(string|QueueConfig $config): void throw new \InvalidArgumentException("Invalid queue configuration alias [$config]"); } - $this->childProcess->php( + $this->childProcess->artisan( [ - '-d', - "memory_limit={$config->memoryLimit}M", - 'artisan', + // '-d', + // "memory_limit={$config->memoryLimit}M", + // 'artisan', 'queue:work', "--name={$config->alias}", '--queue='.implode(',', $config->queuesToConsume), From f2584d00b45405064d458e30f7f50d63c3cb41bf Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 11 Mar 2025 14:39:53 +0100 Subject: [PATCH 069/105] feat: artisan commands in bundles --- src/ChildProcess.php | 7 ++++--- src/Contracts/ChildProcess.php | 4 ++-- src/Events/ChildProcess/StartupError.php | 22 ++++++++++++++++++++++ src/Fakes/ChildProcessFake.php | 8 ++++++-- src/QueueWorker.php | 3 --- 5 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 src/Events/ChildProcess/StartupError.php diff --git a/src/ChildProcess.php b/src/ChildProcess.php index f524370..19a9420 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -80,7 +80,7 @@ public function start( * @param string|string[] $cmd * @return $this */ - public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self + public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false, ?array $iniSettings = null): self { $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; @@ -90,6 +90,7 @@ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool 'cwd' => base_path(), 'env' => $env, 'persistent' => $persistent, + 'iniSettings' => $iniSettings, ])->json(); return $this->fromRuntimeProcess($process); @@ -99,13 +100,13 @@ public function php(string|array $cmd, string $alias, ?array $env = null, ?bool * @param string|string[] $cmd * @return $this */ - public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self + public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false, ?array $iniSettings = null): self { $cmd = is_array($cmd) ? array_values($cmd) : [$cmd]; $cmd = ['artisan', ...$cmd]; - return $this->php($cmd, $alias, env: $env, persistent: $persistent); + return $this->php($cmd, $alias, env: $env, persistent: $persistent, iniSettings: $iniSettings); } public function stop(?string $alias = null): void diff --git a/src/Contracts/ChildProcess.php b/src/Contracts/ChildProcess.php index 9859e3e..0a4f877 100644 --- a/src/Contracts/ChildProcess.php +++ b/src/Contracts/ChildProcess.php @@ -16,9 +16,9 @@ public function start( bool $persistent = false ): self; - public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self; + public function php(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false, ?array $iniSettings = null): self; - public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false): self; + public function artisan(string|array $cmd, string $alias, ?array $env = null, ?bool $persistent = false, ?array $iniSettings = null): self; public function stop(?string $alias = null): void; diff --git a/src/Events/ChildProcess/StartupError.php b/src/Events/ChildProcess/StartupError.php new file mode 100644 index 0000000..8adb2ed --- /dev/null +++ b/src/Events/ChildProcess/StartupError.php @@ -0,0 +1,22 @@ +phps[] = [ 'cmd' => $cmd, 'alias' => $alias, 'env' => $env, 'persistent' => $persistent, + 'iniSettings' => $iniSettings, ]; return $this; @@ -93,13 +95,15 @@ public function artisan( array|string $cmd, string $alias, ?array $env = null, - ?bool $persistent = false + ?bool $persistent = false, + ?array $iniSettings = null ): self { $this->artisans[] = [ 'cmd' => $cmd, 'alias' => $alias, 'env' => $env, 'persistent' => $persistent, + 'iniSettings' => $iniSettings, ]; return $this; diff --git a/src/QueueWorker.php b/src/QueueWorker.php index 3a820f1..e39803f 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -26,9 +26,6 @@ public function up(string|QueueConfig $config): void $this->childProcess->artisan( [ - // '-d', - // "memory_limit={$config->memoryLimit}M", - // 'artisan', 'queue:work', "--name={$config->alias}", '--queue='.implode(',', $config->queuesToConsume), From 3228b9880ada90469531d30208843a98c6923cdc Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 11 Mar 2025 14:49:09 +0100 Subject: [PATCH 070/105] feat: artisan commands in bundles --- src/QueueWorker.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/QueueWorker.php b/src/QueueWorker.php index e39803f..875a5c2 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -32,13 +32,16 @@ public function up(string|QueueConfig $config): void "--memory={$config->memoryLimit}", "--timeout={$config->timeout}", ], - $config->alias, + 'queue_'.$config->alias, persistent: true, + iniSettings: [ + 'memory_limit' => "{$config->memoryLimit}M", + ] ); } public function down(string $alias): void { - $this->childProcess->stop($alias); + $this->childProcess->stop('queue_'.$alias); } } From f7b8f528a7b497022266d7d57b8f32436cb15652 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 12 Mar 2025 14:01:14 +0100 Subject: [PATCH 071/105] refactor: this should not be necessary anymore --- src/NativeServiceProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 526c8cf..1adad86 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -149,8 +149,8 @@ public function rewriteDatabase() { $databasePath = config('nativephp-internal.database_path'); - // Automatically create the database in development mode but not if we are running in a Phar - if (config('app.debug') && ! Phar::running()) { + // Automatically create the database in development mode + if (config('app.debug')) { $databasePath = database_path('nativephp.sqlite'); if (! file_exists($databasePath)) { From fbf7b236d7b69820d4442b7c222119f12df12be8 Mon Sep 17 00:00:00 2001 From: SRWieZ <1408020+SRWieZ@users.noreply.github.com> Date: Wed, 12 Mar 2025 13:01:42 +0000 Subject: [PATCH 072/105] Fix styling --- src/NativeServiceProvider.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 1adad86..ce1537f 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -25,7 +25,6 @@ use Native\Laravel\Logging\LogWatcher; use Native\Laravel\PowerMonitor as PowerMonitorImplementation; use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; -use Phar; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; From ee3945576e5e78645877dfbae9f7670168cf8f6a Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Sat, 15 Mar 2025 14:06:33 +0100 Subject: [PATCH 073/105] fix: NATIVEPHP_PHP_BINARY_PATH --- config/nativephp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/nativephp.php b/config/nativephp.php index 49eacd8..1ecd30a 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -143,5 +143,5 @@ /** * Custom PHP binary path. */ - 'binary_path' => env('NATIVEPHP_BINARY_PATH', null), + 'binary_path' => env('NATIVEPHP_PHP_BINARY_PATH', null), ]; From 3e27e0b186664b05be4a2a09cf0198e3665beb2e Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 18 Mar 2025 03:20:39 +0000 Subject: [PATCH 074/105] Test fixes --- tests/Fakes/FakeChildProcessTest.php | 12 ++++++------ tests/QueueWorker/QueueWorkerTest.php | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/Fakes/FakeChildProcessTest.php b/tests/Fakes/FakeChildProcessTest.php index 57b0c35..aae9d26 100644 --- a/tests/Fakes/FakeChildProcessTest.php +++ b/tests/Fakes/FakeChildProcessTest.php @@ -82,18 +82,18 @@ $fake->php('cmdA', 'aliasA', ['envA'], true); $fake->php('cmdB', 'aliasB', ['envB'], false); - $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasA' && $cmd === 'cmdA' && $env === ['envA'] && $persistent === true); - $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasB' && $cmd === 'cmdB' && $env === ['envB'] && $persistent === false); try { - $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasC'); } catch (AssertionFailedError) { return; } @@ -107,18 +107,18 @@ $fake->artisan('cmdA', 'aliasA', ['envA'], true); $fake->artisan('cmdB', 'aliasB', ['envB'], false); - $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasA' && $cmd === 'cmdA' && $env === ['envA'] && $persistent === true); - $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasB' && $cmd === 'cmdB' && $env === ['envB'] && $persistent === false); try { - $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent, $iniSettings) => $alias === 'aliasC'); } catch (AssertionFailedError) { return; } diff --git a/tests/QueueWorker/QueueWorkerTest.php b/tests/QueueWorker/QueueWorkerTest.php index a3fbd57..2a6c137 100644 --- a/tests/QueueWorker/QueueWorkerTest.php +++ b/tests/QueueWorker/QueueWorkerTest.php @@ -6,23 +6,27 @@ it('hits the child process with relevant queue config to spin up a new queue worker', function () { ChildProcess::fake(); - $config = new QueueConfig('some_worker', ['default'], 128, 61); + + $workerName = 'some_worker'; + + $config = new QueueConfig($workerName, ['default'], 128, 61); QueueWorker::up($config); - ChildProcess::assertPhp(function (array $cmd, string $alias, $env, $persistent) { + ChildProcess::assertArtisan(function (array $cmd, string $alias, $env, $persistent, $iniSettings) use ($workerName) { expect($cmd)->toBe([ - '-d', - 'memory_limit=128M', - 'artisan', 'queue:work', - "--name={$alias}", + "--name={$workerName}", '--queue=default', '--memory=128', '--timeout=61', ]); - expect($alias)->toBe('some_worker'); + expect($iniSettings)->toBe([ + 'memory_limit' => '128M', + ]); + + expect($alias)->toBe('queue_some_worker'); expect($env)->toBeNull(); expect($persistent)->toBeTrue(); @@ -35,5 +39,5 @@ QueueWorker::down('some_worker'); - ChildProcess::assertStop('some_worker'); + ChildProcess::assertStop('queue_some_worker'); }); From 60ac44a4693e6524ff3a6f92f7481c0b8b70e0e9 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Tue, 18 Mar 2025 10:35:10 +0100 Subject: [PATCH 075/105] feat: add quit method to App class (#519) * feat: add quit method to App class * Method for facade added --- src/App.php | 5 +++++ src/Facades/App.php | 1 + 2 files changed, 6 insertions(+) diff --git a/src/App.php b/src/App.php index 81f1a26..3239026 100644 --- a/src/App.php +++ b/src/App.php @@ -8,6 +8,11 @@ class App { public function __construct(protected Client $client) {} + public function quit(): void + { + $this->client->post('app/quit'); + } + public function focus(): void { $this->client->post('app/focus'); diff --git a/src/Facades/App.php b/src/Facades/App.php index 6f3d99a..41817cf 100644 --- a/src/Facades/App.php +++ b/src/Facades/App.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Facade; /** + * @method static void quit() * @method static void focus() * @method static void hide() * @method static bool isHidden() From 372614dff197401aee651bc772bd88846826c7ea Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Tue, 18 Mar 2025 10:37:28 +0100 Subject: [PATCH 076/105] feat: add openAtLogin method to manage app startup behavior (#520) * feat: add openAtLogin method to manage app startup behavior * Method for facade added --- src/App.php | 13 +++++++++++++ src/Facades/App.php | 1 + 2 files changed, 14 insertions(+) diff --git a/src/App.php b/src/App.php index 3239026..bb8ef2e 100644 --- a/src/App.php +++ b/src/App.php @@ -62,4 +62,17 @@ public function clearRecentDocuments(): void { $this->client->delete('app/recent-documents'); } + + public function openAtLogin(?bool $open = null): bool + { + if ($open === null) { + return (bool) $this->client->get('app/open-at-login')->json('open'); + } + + $this->client->post('app/open-at-login', [ + 'open' => $open, + ]); + + return $open; + } } diff --git a/src/Facades/App.php b/src/Facades/App.php index 41817cf..dbc2591 100644 --- a/src/Facades/App.php +++ b/src/Facades/App.php @@ -14,6 +14,7 @@ * @method static void addRecentDocument(string $path) * @method static array recentDocuments() * @method static void clearRecentDocuments() + * @method static bool openAtLogin(?bool $open = null) */ class App extends Facade { From b8a753a4084df43207dff52a8555b186cfc8d6c3 Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Wed, 19 Mar 2025 13:04:06 +0000 Subject: [PATCH 077/105] Implement Debug Command (#516) * Implement Debug Command * Fix styling * Output to Clipboard support * Windows support for path detection * Fix styling * z vs s - z wins * Remove error log parsing for now * Implement config vars for notarization * Add PHP Binary Path to config --- config/nativephp-internal.php | 14 +++ src/Commands/DebugCommand.php | 165 ++++++++++++++++++++++++++++++++++ src/NativeServiceProvider.php | 4 +- 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/Commands/DebugCommand.php diff --git a/config/nativephp-internal.php b/config/nativephp-internal.php index 4210df9..2fbb4a3 100644 --- a/config/nativephp-internal.php +++ b/config/nativephp-internal.php @@ -29,4 +29,18 @@ * The URL to the NativePHP API. */ 'api_url' => env('NATIVEPHP_API_URL', 'http://localhost:4000/api/'), + + /** + * The credentials to use Apples Notarization service. + */ + 'notarization' => [ + 'apple_id' => env('NATIVEPHP_APPLE_ID'), + 'apple_id_pass' => env('NATIVEPHP_APPLE_ID_PASS'), + 'apple_team_id' => env('NATIVEPHP_APPLE_TEAM_ID'), + ], + + /** + * The binary path of PHP for NativePHP to use at build. + */ + 'php_binary_path' => env('NATIVEPHP_PHP_BINARY_PATH'), ]; diff --git a/src/Commands/DebugCommand.php b/src/Commands/DebugCommand.php new file mode 100644 index 0000000..3900d3c --- /dev/null +++ b/src/Commands/DebugCommand.php @@ -0,0 +1,165 @@ +debugInfo = collect(); + intro('Generating Debug Information...'); + + $this->processEnvironment() + ->processNativePHP(); + + switch ($this->argument('output')) { + case 'File': + $this->outputToFile(); + break; + case 'Clipboard': + $this->outputToClipboard(); + break; + case 'Console': + $this->outputToConsole(); + break; + default: + error('Invalid output option specified.'); + } + + outro('Debug Information Generated.'); + } + + private function processEnvironment(): static + { + $locationCommand = 'which'; + + if (PHP_OS_FAMILY === 'Windows') { + $locationCommand = 'where'; + } + + info('Generating Environment Data...'); + $environment = [ + 'PHP' => [ + 'Version' => phpversion(), + 'Path' => PHP_BINARY, + ], + 'Laravel' => [ + 'Version' => app()->version(), + 'ConfigCached' => file_exists($this->laravel->getCachedConfigPath()), + 'DebugEnabled' => $this->laravel->hasDebugModeEnabled(), + ], + 'Node' => [ + 'Version' => trim(Process::run('node -v')->output()), + 'Path' => trim(Process::run("$locationCommand node")->output()), + ], + 'NPM' => [ + 'Version' => trim(Process::run('npm -v')->output()), + 'Path' => trim(Process::run("$locationCommand npm")->output()), + ], + 'OperatingSystem' => PHP_OS, + ]; + + $this->debugInfo->put('Environment', $environment); + + return $this; + } + + private function processNativePHP(): static + { + info('Processing NativePHP Data...'); + // Get composer versions + $versions = collect([ + 'nativephp/electron' => null, + 'nativephp/laravel' => null, + 'nativephp/php-bin' => null, + ])->mapWithKeys(function ($version, $key) { + try { + $version = InstalledVersions::getVersion($key); + } catch (\OutOfBoundsException) { + $version = 'Not Installed'; + } + + return [$key => $version]; + }); + + $isNotarizationConfigured = config('nativephp-internal.notarization.apple_id') + && config('nativephp-internal.notarization.apple_id_pass') + && config('nativephp-internal.notarization.apple_team_id'); + + $this->debugInfo->put( + 'NativePHP', + [ + 'Versions' => $versions, + 'Configuration' => [ + 'Provider' => config('nativephp.provider'), + 'BuildHooks' => [ + 'Pre' => config('nativephp.prebuild'), + 'Post' => config('nativephp.postbuild'), + ], + 'NotarizationEnabled' => $isNotarizationConfigured, + 'CustomPHPBinary' => config('nativephp-internal.php_binary_path') ?? false, + ], + ] + ); + + return $this; + } + + protected function promptForMissingArgumentsUsing(): array + { + return [ + 'output' => fn () => select( + 'Where would you like to output the debug information?', + ['File', 'Clipboard', 'Console'], + 'File' + ), + ]; + } + + private function outputToFile(): void + { + File::put(base_path('nativephp_debug.json'), json_encode($this->debugInfo->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + note('Debug information saved to '.base_path('nativephp_debug.json')); + } + + private function outputToConsole(): void + { + $this->output->writeln( + print_r($this->debugInfo->toArray(), true) + ); + } + + private function outputToClipboard(): void + { + $json = json_encode($this->debugInfo->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + + // Copy json to clipboard + if (PHP_OS_FAMILY === 'Windows') { + Process::run('echo '.escapeshellarg($json).' | clip'); + } elseif (PHP_OS_FAMILY === 'Linux') { + Process::run('echo '.escapeshellarg($json).' | xclip -selection clipboard'); + } else { + Process::run('echo '.escapeshellarg($json).' | pbcopy'); + } + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index b5b3225..c425e56 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; use Native\Laravel\ChildProcess as ChildProcessImplementation; +use Native\Laravel\Commands\DebugCommand; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; @@ -35,8 +36,9 @@ public function configurePackage(Package $package): void $package ->name('nativephp') ->hasCommands([ - MigrateCommand::class, + DebugCommand::class, FreshCommand::class, + MigrateCommand::class, SeedDatabaseCommand::class, ]) ->hasConfigFile() From 6899c2cb5ab784e6ee1ebd46397c98df0a18e905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20S=C3=A1nchez=20Palma?= <40817962+JA-Developer@users.noreply.github.com> Date: Mon, 24 Mar 2025 06:24:12 -0600 Subject: [PATCH 078/105] Fixed error "method_exists(): Argument #1 ($object_or_class) must be of type object|string, array given" when using Livewire and AdminLTE. (#524) * Fixed error 'method_exists(): Argument #1 ($object_or_class) must be of type object|string, array given' when using Darryldecode\Cart\Cart. * Check if the $event variable is an object before checking if it was dispatched by NativePHP. --- src/Events/EventWatcher.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Events/EventWatcher.php b/src/Events/EventWatcher.php index bae9ae7..8163e1a 100644 --- a/src/Events/EventWatcher.php +++ b/src/Events/EventWatcher.php @@ -14,6 +14,10 @@ public function register(): void Event::listen('*', function (string $eventName, array $data) { $event = $data[0] ?? (object) null; + if(! is_object($event)) { + return; + } + if (! method_exists($event, 'broadcastOn')) { return; } From 5992b012af721d3fb48cfdcc105753c123a1b012 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Tue, 25 Mar 2025 06:56:45 +0100 Subject: [PATCH 079/105] feat: implement Alert class and facade for alert management (#523) * feat: implement Alert class and facade for alert management * refactor: remove unused methods from Alert class --- src/Alert.php | 94 +++++++++++++++++++++++++++++++++++++++++++ src/Facades/Alert.php | 23 +++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/Alert.php create mode 100644 src/Facades/Alert.php diff --git a/src/Alert.php b/src/Alert.php new file mode 100644 index 0000000..4ddcb8f --- /dev/null +++ b/src/Alert.php @@ -0,0 +1,94 @@ +type = $type; + + return $this; + } + + public function title(string $title): self + { + $this->title = $title; + + return $this; + } + + public function detail(string $detail): self + { + $this->detail = $detail; + + return $this; + } + + public function buttons(array $buttons): self + { + $this->buttons = $buttons; + + return $this; + } + + public function defaultId(int $defaultId): self + { + $this->defaultId = $defaultId; + + return $this; + } + + public function cancelId(int $cancelId): self + { + $this->cancelId = $cancelId; + + return $this; + } + + public function show(string $message): int + { + $response = $this->client->post('alert/message', [ + 'message' => $message, + 'type' => $this->type, + 'title' => $this->title, + 'detail' => $this->detail, + 'buttons' => $this->buttons, + 'defaultId' => $this->defaultId, + 'cancelId' => $this->cancelId + ]); + + return (int) $response->json('result'); + } + + public function error(string $title, string $message): bool + { + $response = $this->client->post('alert/error', [ + 'title' => $title, + 'message' => $message, + ]); + + return (bool) $response->json('result'); + } +} diff --git a/src/Facades/Alert.php b/src/Facades/Alert.php new file mode 100644 index 0000000..a4b151f --- /dev/null +++ b/src/Facades/Alert.php @@ -0,0 +1,23 @@ + Date: Thu, 27 Mar 2025 16:57:44 +0000 Subject: [PATCH 080/105] Environment Helper (#527) * Add Environment Class with OS Helpers * Abstract all usages of PHP_OS_FAMILY to Environment class * Fix styling --------- Co-authored-by: Pete Bishop --- src/Alert.php | 14 +++++++------- src/Commands/DebugCommand.php | 7 ++++--- src/Events/EventWatcher.php | 2 +- src/Support/Environment.php | 31 +++++++++++++++++++++++++++++++ src/System.php | 3 ++- 5 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 src/Support/Environment.php diff --git a/src/Alert.php b/src/Alert.php index 4ddcb8f..d00e7ef 100644 --- a/src/Alert.php +++ b/src/Alert.php @@ -2,23 +2,23 @@ namespace Native\Laravel; -use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Macroable; use Native\Laravel\Client\Client; -use Native\Laravel\Facades\Window; class Alert { protected ?string $type; + protected ?string $title; + protected ?string $detail; + protected ?array $buttons; + protected ?int $defaultId; + protected ?int $cancelId; - final public function __construct(protected Client $client) - { - } + final public function __construct(protected Client $client) {} public static function new() { @@ -76,7 +76,7 @@ public function show(string $message): int 'detail' => $this->detail, 'buttons' => $this->buttons, 'defaultId' => $this->defaultId, - 'cancelId' => $this->cancelId + 'cancelId' => $this->cancelId, ]); return (int) $response->json('result'); diff --git a/src/Commands/DebugCommand.php b/src/Commands/DebugCommand.php index 3900d3c..ad79c3b 100644 --- a/src/Commands/DebugCommand.php +++ b/src/Commands/DebugCommand.php @@ -8,6 +8,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Process; +use Native\Laravel\Support\Environment; use function Laravel\Prompts\error; use function Laravel\Prompts\info; @@ -53,7 +54,7 @@ private function processEnvironment(): static { $locationCommand = 'which'; - if (PHP_OS_FAMILY === 'Windows') { + if (Environment::isWindows()) { $locationCommand = 'where'; } @@ -154,9 +155,9 @@ private function outputToClipboard(): void $json = json_encode($this->debugInfo->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); // Copy json to clipboard - if (PHP_OS_FAMILY === 'Windows') { + if (Environment::isWindows()) { Process::run('echo '.escapeshellarg($json).' | clip'); - } elseif (PHP_OS_FAMILY === 'Linux') { + } elseif (Environment::isLinux()) { Process::run('echo '.escapeshellarg($json).' | xclip -selection clipboard'); } else { Process::run('echo '.escapeshellarg($json).' | pbcopy'); diff --git a/src/Events/EventWatcher.php b/src/Events/EventWatcher.php index 8163e1a..6df7e24 100644 --- a/src/Events/EventWatcher.php +++ b/src/Events/EventWatcher.php @@ -14,7 +14,7 @@ public function register(): void Event::listen('*', function (string $eventName, array $data) { $event = $data[0] ?? (object) null; - if(! is_object($event)) { + if (! is_object($event)) { return; } diff --git a/src/Support/Environment.php b/src/Support/Environment.php new file mode 100644 index 0000000..70f2036 --- /dev/null +++ b/src/Support/Environment.php @@ -0,0 +1,31 @@ +translateFromWindowsString(exec('tzutil /g')); } else { $timezone = $timezones->translateFromAbbreviatedString(exec('date +%Z')); From 29b877947e75a09339b54a943df3909a7423ccf5 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Mon, 31 Mar 2025 10:59:10 +0200 Subject: [PATCH 081/105] fix: initialize properties in Alert class and update new method signature (#529) --- src/Alert.php | 14 +++++++------- src/Facades/Alert.php | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Alert.php b/src/Alert.php index d00e7ef..1303721 100644 --- a/src/Alert.php +++ b/src/Alert.php @@ -6,21 +6,21 @@ class Alert { - protected ?string $type; + protected ?string $type = null; - protected ?string $title; + protected ?string $title = null; - protected ?string $detail; + protected ?string $detail = null; - protected ?array $buttons; + protected ?array $buttons = null; - protected ?int $defaultId; + protected ?int $defaultId = null; - protected ?int $cancelId; + protected ?int $cancelId = null; final public function __construct(protected Client $client) {} - public static function new() + public static function new(): self { return new static(new Client); } diff --git a/src/Facades/Alert.php b/src/Facades/Alert.php index a4b151f..2d5e1f2 100644 --- a/src/Facades/Alert.php +++ b/src/Facades/Alert.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Facade; /** + * @method static static new() * @method static static type(string $type) * @method static static title(string $title) * @method static static detail(string $detail) From afe7316242a51789e5e943ebac749dddc7b78240 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Mon, 31 Mar 2025 11:01:42 +0200 Subject: [PATCH 082/105] feat: add theme method to System class and create SystemThemesEnum (#530) --- src/Enums/SystemThemesEnum.php | 10 ++++++++++ src/Facades/System.php | 2 ++ src/System.php | 14 ++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/Enums/SystemThemesEnum.php diff --git a/src/Enums/SystemThemesEnum.php b/src/Enums/SystemThemesEnum.php new file mode 100644 index 0000000..e290dbc --- /dev/null +++ b/src/Enums/SystemThemesEnum.php @@ -0,0 +1,10 @@ +client->post('system/theme', [ + 'theme' => $theme, + ])->json('result'); + } else { + $result = $this->client->get('system/theme')->json('result'); + } + + return SystemThemesEnum::from($result); + } } From 15b6d75b98d4ea9bf69b0fb8aab9001061fa571d Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:47:23 +0100 Subject: [PATCH 083/105] PHP Support Sync (#532) * Set PHP support to min 8.3 * Drop PHP 8.1 and 8.2 support from workflows --- .github/workflows/run-tests.yml | 8 +------- composer.json | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a1909f6..18fb032 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -13,16 +13,10 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - php: [8.4, 8.3, 8.2, 8.1] + php: [8.4, 8.3] laravel: [12.*, 11.*, 10.*] stability: [prefer-lowest, prefer-stable] - exclude: - - laravel: 11.* - php: 8.1 - - laravel: 12.* - php: 8.1 - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} steps: diff --git a/composer.json b/composer.json index 71336ed..73e68c5 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ } ], "require": { - "php": "^8.1", + "php": "^8.3", "illuminate/contracts": "^10.0|^11.0|^12.0", "spatie/laravel-package-tools": "^1.16.4", "symfony/finder": "^6.2|^7.0" From c1f334dc268103b5772d46f455de93a846c0c042 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Mon, 31 Mar 2025 17:20:40 +0200 Subject: [PATCH 084/105] feat: add copyright notice configuration to nativephp.php (#534) --- config/nativephp.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/nativephp.php b/config/nativephp.php index 1ecd30a..817bc12 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -31,6 +31,11 @@ */ 'author' => env('NATIVEPHP_APP_AUTHOR'), + /** + * The copyright notice for your application. + */ + 'copyright' => env('NATIVEPHP_APP_COPYRIGHT'), + /** * The default service provider for your application. This provider * takes care of bootstrapping your application and configuring From 5ca07c4450233a397d4258abf7764a61ba6da129 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 1 Apr 2025 14:57:07 +0200 Subject: [PATCH 085/105] fix: $iniSettings on ChildProcess.php (#535) * fix: $iniSettings on ChildProcess.php * fix: ensure properties are set only if they exist * fix: throw exception for non-existent properties in ChildProcess --- src/ChildProcess.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ChildProcess.php b/src/ChildProcess.php index 19a9420..6ba9972 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -19,6 +19,8 @@ class ChildProcess implements ChildProcessContract public readonly bool $persistent; + public readonly ?array $iniSettings; + final public function __construct(protected Client $client) {} public function get(?string $alias = null): ?self @@ -147,6 +149,10 @@ protected function fromRuntimeProcess($process) } foreach ($process['settings'] as $key => $value) { + if (! property_exists($this, $key)) { + throw new \RuntimeException("Property {$key} does not exist on ".__CLASS__); + } + $this->{$key} = $value; } From 1e525896b0af75dec8cd1e6cd74035a0983d99f7 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 2 Apr 2025 14:52:13 +0200 Subject: [PATCH 086/105] fix: menu facade + better default for quit (#536) --- src/Facades/Menu.php | 37 +++++++++++++++++++------------------ src/Menu/MenuBuilder.php | 4 ++++ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php index 332de24..d197305 100644 --- a/src/Facades/Menu.php +++ b/src/Facades/Menu.php @@ -19,25 +19,26 @@ * @method static Link route(string $url, string $label = null, ?string $hotkey = null) * @method static Radio radio(string $label, bool $checked = false, ?string $hotkey = null) * @method static Role app() - * @method static Role file() - * @method static Role edit() - * @method static Role view() - * @method static Role window() - * @method static Role help() - * @method static Role fullscreen() + * @method static Role about(?string $label = null) + * @method static Role file(?string $label = null) + * @method static Role edit(?string $label = null) + * @method static Role view(?string $label = null) + * @method static Role window(?string $label = null) + * @method static Role help(?string $label = null) + * @method static Role fullscreen(?string $label = null) * @method static Role separator() - * @method static Role devTools() - * @method static Role undo() - * @method static Role redo() - * @method static Role cut() - * @method static Role copy() - * @method static Role paste() - * @method static Role pasteAndMatchStyle() - * @method static Role reload() - * @method static Role minimize() - * @method static Role close() - * @method static Role quit() - * @method static Role hide() + * @method static Role devTools(?string $label = null) + * @method static Role undo(?string $label = null) + * @method static Role redo(?string $label = null) + * @method static Role cut(?string $label = null) + * @method static Role copy(?string $label = null) + * @method static Role paste(?string $label = null) + * @method static Role pasteAndMatchStyle(?string $label = null) + * @method static Role reload(?string $label = null) + * @method static Role minimize(?string $label = null) + * @method static Role close(?string $label = null) + * @method static Role quit(?string $label = null) + * @method static Role hide(?string $label = null) * @method static void create(MenuItem ...$items) * @method static void default() */ diff --git a/src/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php index f0627f7..f2ef764 100644 --- a/src/Menu/MenuBuilder.php +++ b/src/Menu/MenuBuilder.php @@ -150,6 +150,10 @@ public function close(?string $label = null): Items\Role public function quit(?string $label = null): Items\Role { + if (is_null($label)) { + $label = __('Quit').' '.config('app.name'); + } + return new Items\Role(RolesEnum::QUIT, $label); } From 91f487cb40fdea5ce737fc7128f4f17dc2a2e92f Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Thu, 3 Apr 2025 14:37:25 +0200 Subject: [PATCH 087/105] fix: failed jobs on the nativephp database (#538) * fix: failed jobs on the nativephp database * fix: add batching database configuration for nativephp queue * fix: add database connection configuration for nativephp queue --- src/NativeServiceProvider.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 7e000a7..a1910a4 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -172,6 +172,9 @@ public function rewriteDatabase() ]); config(['database.default' => 'nativephp']); + config(['queue.failed.database' => 'nativephp']); + config(['queue.batching.database' => 'nativephp']); + config(['queue.connections.database.connection' => 'nativephp']); if (file_exists($databasePath)) { DB::statement('PRAGMA journal_mode=WAL;'); From 9e9166d698e55e1877ba1726ba7d25999c85065f Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Mon, 7 Apr 2025 15:52:57 +0100 Subject: [PATCH 088/105] Remove L10 from test matrix (#541) * Remove L10 from test matrix * Remove PHPStan ignore line... --- .github/workflows/run-tests.yml | 2 +- src/NativeServiceProvider.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 18fb032..68e6ca9 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] php: [8.4, 8.3] - laravel: [12.*, 11.*, 10.*] + laravel: [12.*, 11.*] stability: [prefer-lowest, prefer-stable] name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index a1910a4..ad222f9 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -51,7 +51,6 @@ public function packageRegistered() $this->mergeConfigFrom($this->package->basePath('/../config/nativephp-internal.php'), 'nativephp-internal'); $this->app->singleton(FreshCommand::class, function ($app) { - /* @phpstan-ignore-next-line (beacause we support Laravel 10 & 11) */ return new FreshCommand($app['migrator']); }); From e4b8e0accf74395670828c6dea4c17c8fbad196e Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:09:12 +0100 Subject: [PATCH 089/105] Reduce number of fields on bug report, replacing many with native:debug (#545) --- .github/ISSUE_TEMPLATE/bug.yml | 47 ++++++++-------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f454785..29e7381 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -7,9 +7,11 @@ body: attributes: value: | We're sorry to hear you have a problem. - + Before submitting your report, please make sure you've been through the section "[Debugging](https://nativephp.com/docs/getting-started/debugging)" in the docs. + Please also ensure that you have the latest version of NativePHP packages installed, and are using [supported versions](https://nativephp.com/docs/desktop/1/getting-started/support-policy) of PHP and Laravel. + If nothing here has helped you, please provide as much useful context as you can here to help us solve help you. Note that reams and reams of logs isn't helpful - please share only relevant errors. @@ -23,6 +25,7 @@ body: placeholder: Trying to build my app for production validations: required: true + - type: textarea id: what-happened attributes: @@ -31,6 +34,7 @@ body: placeholder: I cannot currently do X thing because when I do, it breaks X thing. validations: required: true + - type: textarea id: how-to-reproduce attributes: @@ -39,37 +43,15 @@ body: placeholder: When I do X I see Y. validations: required: true + - type: textarea - id: package-version - attributes: - label: Package Versions - description: What versions of the NativePHP packages are you running? Output of `composer show "nativephp/*" --format=json` - validations: - required: true - - type: input - id: php-version + id: debug attributes: - label: PHP Version - description: What version of PHP are you running? Please be as specific as possible - placeholder: 8.2.0 + label: Debug Output + description: Please provide output from the NativePHP Debug command. This will help us understand your environment and the issue you're facing. (`php artisan native:debug`) validations: required: true - - type: input - id: laravel-version - attributes: - label: Laravel Version - description: What version of Laravel are you running? Please be as specific as possible - placeholder: 9.0.0 - validations: - required: true - - type: input - id: node-version - attributes: - label: Node Version - description: What version of Node are you running? Please be as specific as possible - placeholder: '18.17' - validations: - required: true + - type: dropdown id: operating-systems attributes: @@ -80,14 +62,7 @@ body: - macOS - Windows - Linux - - type: input - id: os-version - attributes: - label: OS version - description: Which version of these OSes are you using? - placeholder: 'win11 (23H2), macos14.1 (23B74)' - validations: - required: true + - type: textarea id: notes attributes: From 31e630b9ac315a1e5b0779b1203eafa58e0cac33 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Wed, 9 Apr 2025 02:33:19 +0100 Subject: [PATCH 090/105] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4308c95..c1284be 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Thanks to the following sponsors for funding NativePHP development. Please consi - [RedGalaxy](https://www.redgalaxy.co.uk) - A web application development studio based in Cambridgeshire, building solutions to help businesses improve efficiency and profitability. - [Sevalla](https://sevalla.com/?utm_source=nativephp&utm_medium=Referral&utm_campaign=homepage) - Host and manage your applications, databases, and static sites in a single, intuitive platform. - [KaasHosting](https://www.kaashosting.nl/?lang=en) - Minecraft Server and VPS hosting from The Netherlands. -- [Borah Digital Labs](https://borah.digital/) - An MVP building studio from the sunny Canary Islands focusing on AI, SaaS and online platforms. ## Changelog From 540e9a474cb9248a941b7dbe7b5c22bb13a45fc6 Mon Sep 17 00:00:00 2001 From: Justas Raudonius <10882793+justRau@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:30:09 +0300 Subject: [PATCH 091/105] Update DebugCommand.php (#550) --- src/Commands/DebugCommand.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Commands/DebugCommand.php b/src/Commands/DebugCommand.php index ad79c3b..2bb7633 100644 --- a/src/Commands/DebugCommand.php +++ b/src/Commands/DebugCommand.php @@ -66,7 +66,8 @@ private function processEnvironment(): static ], 'Laravel' => [ 'Version' => app()->version(), - 'ConfigCached' => file_exists($this->laravel->getCachedConfigPath()), + 'ConfigCached' => $this->laravel->configurationIsCached(), + 'RoutesCached' => $this->laravel->routesAreCached(), 'DebugEnabled' => $this->laravel->hasDebugModeEnabled(), ], 'Node' => [ From ff9adc83be4120268c659e5dde0e3cc83da88cde Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Mon, 28 Apr 2025 12:13:19 +0200 Subject: [PATCH 092/105] Add 'description' and 'website' fields to nativephp config (#571) * Add 'description' and 'website' fields to nativephp config These new fields allow developers to define a description and website for their application via environment variables. This enhances configurability and provides more application metadata. * Added Defaults - config/nativephp.php Co-authored-by: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> * Added Defaults - config/nativephp.php Co-authored-by: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> --------- Co-authored-by: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> --- config/nativephp.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/nativephp.php b/config/nativephp.php index 817bc12..96b6b1e 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -36,6 +36,16 @@ */ 'copyright' => env('NATIVEPHP_APP_COPYRIGHT'), + /** + * The description of your application. + */ + 'description' => env('NATIVEPHP_APP_DESCRIPTION', 'An awesome app built with NativePHP'), + + /** + * The Website of your application. + */ + 'website' => env('NATIVEPHP_APP_WEBSITE', 'https://nativephp.com'), + /** * The default service provider for your application. This provider * takes care of bootstrapping your application and configuring From e3d5974dafc0d4d51e426db0eb7fd5a0a0d67475 Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Mon, 28 Apr 2025 14:12:29 +0200 Subject: [PATCH 093/105] Add relaunch functionality to App and its facade (#569) --- src/App.php | 5 +++++ src/Facades/App.php | 1 + 2 files changed, 6 insertions(+) diff --git a/src/App.php b/src/App.php index 039579e..f7fa1c3 100644 --- a/src/App.php +++ b/src/App.php @@ -14,6 +14,11 @@ public function quit(): void $this->client->post('app/quit'); } + public function relaunch(): void + { + $this->client->post('app/relaunch'); + } + public function focus(): void { $this->client->post('app/focus'); diff --git a/src/Facades/App.php b/src/Facades/App.php index 69abb2e..14e42f8 100644 --- a/src/Facades/App.php +++ b/src/Facades/App.php @@ -6,6 +6,7 @@ /** * @method static void quit() + * @method static void relaunch() * @method static void focus() * @method static void hide() * @method static bool isHidden() From 5af2e3ee8c416d4cd0cfba460420ed8866dc063e Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Tue, 29 Apr 2025 12:38:32 +0200 Subject: [PATCH 094/105] Add "showOnAllWorkspaces" option to MenuBar (#568) Introduce a new `showOnAllWorkspaces` property and its corresponding method to configure this behavior. Update the `toArray` method to include this property and adjust tests to validate its functionality. --- src/MenuBar/MenuBar.php | 10 ++++++++++ tests/MenuBar/MenuBarTest.php | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/MenuBar/MenuBar.php b/src/MenuBar/MenuBar.php index 13a5709..c3b6ce8 100644 --- a/src/MenuBar/MenuBar.php +++ b/src/MenuBar/MenuBar.php @@ -32,6 +32,8 @@ class MenuBar protected bool $showDockIcon = false; + protected bool $showOnAllWorkspaces = false; + protected Client $client; public function __construct() @@ -95,6 +97,13 @@ public function alwaysOnTop($alwaysOnTop = true): self return $this; } + public function showOnAllWorkspaces($showOnAllWorkspaces = true): self + { + $this->showOnAllWorkspaces = $showOnAllWorkspaces; + + return $this; + } + public function withContextMenu(Menu $menu): self { $this->contextMenu = $menu; @@ -122,6 +131,7 @@ public function toArray(): array 'onlyShowContextMenu' => $this->onlyShowContextMenu, 'contextMenu' => ! is_null($this->contextMenu) ? $this->contextMenu->toArray()['submenu'] : null, 'alwaysOnTop' => $this->alwaysOnTop, + 'showOnAllWorkspaces' => $this->showOnAllWorkspaces, ]; } } diff --git a/tests/MenuBar/MenuBarTest.php b/tests/MenuBar/MenuBarTest.php index 4b86aa9..79dbb80 100644 --- a/tests/MenuBar/MenuBarTest.php +++ b/tests/MenuBar/MenuBarTest.php @@ -9,6 +9,7 @@ $menuBar = MenuBar::create() ->showDockIcon() ->alwaysOnTop() + ->showOnAllWorkspaces() ->label('milwad') ->icon('nativephp.png') ->url('https://github.com/milwad-dev') @@ -22,6 +23,7 @@ $this->assertTrue($menuBarArray['showDockIcon']); $this->assertTrue($menuBarArray['alwaysOnTop']); + $this->assertTrue($menuBarArray['showOnAllWorkspaces']); $this->assertEquals('milwad', $menuBarArray['label']); $this->assertEquals('https://github.com/milwad-dev', $menuBarArray['url']); $this->assertEquals('nativephp.png', $menuBarArray['icon']); From 949c1ac15d75a3c5f0c619f9731cfb17b211a583 Mon Sep 17 00:00:00 2001 From: Peter Bishop <9081809+PeteBishwhip@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:19:53 +0100 Subject: [PATCH 095/105] Update fix-php-code-style-issues.yml (#573) --- .github/workflows/fix-php-code-style-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index eb79b6e..4ae67bf 100644 --- a/.github/workflows/fix-php-code-style-issues.yml +++ b/.github/workflows/fix-php-code-style-issues.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + ref: ${{ github.head_ref || github.sha }} - name: Check PHP code style issues if: github.event_name == 'push' From a19e54fa778182c25786051ded8bf46afd831fac Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Thu, 8 May 2025 10:15:27 +0200 Subject: [PATCH 096/105] Add AutoUpdater feature with events and facade support (#570) * Add AutoUpdater feature with events and facade support Introduces the `AutoUpdater` class to manage update processes via the client. Adds events such as `UpdateAvailable`, `UpdateDownloaded`, and others for broadcasting update states. A facade is also provided for convenient usage. * Add DownloadProgress event for real-time update broadcasting This event handles broadcasting download progress updates through a 'nativephp' channel. It includes details such as total size, delta, transferred bytes, percentage completed, and speed. * fix: update property types in DownloadProgress constructor Adjusted the types of total, delta, and bytesPerSecond from float to int to ensure type consistency and accuracy. This change prevents potential precision issues and aligns with expected data formats. --- src/AutoUpdater.php | 20 ++++++++++++++++ src/Events/AutoUpdater/CheckingForUpdate.php | 23 +++++++++++++++++++ src/Events/AutoUpdater/DownloadProgress.php | 23 +++++++++++++++++++ src/Events/AutoUpdater/Error.php | 23 +++++++++++++++++++ src/Events/AutoUpdater/UpdateAvailable.php | 23 +++++++++++++++++++ src/Events/AutoUpdater/UpdateDownloaded.php | 23 +++++++++++++++++++ src/Events/AutoUpdater/UpdateNotAvailable.php | 21 +++++++++++++++++ src/Facades/AutoUpdater.php | 17 ++++++++++++++ 8 files changed, 173 insertions(+) create mode 100644 src/AutoUpdater.php create mode 100644 src/Events/AutoUpdater/CheckingForUpdate.php create mode 100644 src/Events/AutoUpdater/DownloadProgress.php create mode 100644 src/Events/AutoUpdater/Error.php create mode 100644 src/Events/AutoUpdater/UpdateAvailable.php create mode 100644 src/Events/AutoUpdater/UpdateDownloaded.php create mode 100644 src/Events/AutoUpdater/UpdateNotAvailable.php create mode 100644 src/Facades/AutoUpdater.php diff --git a/src/AutoUpdater.php b/src/AutoUpdater.php new file mode 100644 index 0000000..e714a0f --- /dev/null +++ b/src/AutoUpdater.php @@ -0,0 +1,20 @@ +client->post('auto-updater/check-for-updates'); + } + + public function quitAndInstall(): void + { + $this->client->post('auto-updater/quit-and-install'); + } +} diff --git a/src/Events/AutoUpdater/CheckingForUpdate.php b/src/Events/AutoUpdater/CheckingForUpdate.php new file mode 100644 index 0000000..a646d32 --- /dev/null +++ b/src/Events/AutoUpdater/CheckingForUpdate.php @@ -0,0 +1,23 @@ + Date: Thu, 8 May 2025 09:27:47 +0100 Subject: [PATCH 097/105] Include sleep option in queue config (#564) * feat/add-sleep-option-to-queue-config | expose sleep option to QueueConfig * feat/add-sleep-option-to-queue-config | include default sleep option in nativephp config file --- config/nativephp.php | 1 + src/DTOs/QueueConfig.php | 2 ++ src/QueueWorker.php | 1 + tests/DTOs/QueueWorkerTest.php | 5 +++++ tests/Fakes/FakeQueueWorkerTest.php | 4 ++-- tests/QueueWorker/QueueWorkerTest.php | 3 ++- 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/config/nativephp.php b/config/nativephp.php index 96b6b1e..d68245f 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -141,6 +141,7 @@ 'queues' => ['default'], 'memory_limit' => 128, 'timeout' => 60, + 'sleep' => 3, ], ], diff --git a/src/DTOs/QueueConfig.php b/src/DTOs/QueueConfig.php index 0ec3ca0..df04d0c 100644 --- a/src/DTOs/QueueConfig.php +++ b/src/DTOs/QueueConfig.php @@ -12,6 +12,7 @@ public function __construct( public readonly array $queuesToConsume, public readonly int $memoryLimit, public readonly int $timeout, + public readonly int $sleep, ) {} /** @@ -26,6 +27,7 @@ function (array|string $worker, string $alias) { $worker['queues'] ?? ['default'], $worker['memory_limit'] ?? 128, $worker['timeout'] ?? 60, + $worker['sleep'] ?? 3, ); }, $config, diff --git a/src/QueueWorker.php b/src/QueueWorker.php index 875a5c2..f9c2ea1 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -31,6 +31,7 @@ public function up(string|QueueConfig $config): void '--queue='.implode(',', $config->queuesToConsume), "--memory={$config->memoryLimit}", "--timeout={$config->timeout}", + "--sleep={$config->sleep}", ], 'queue_'.$config->alias, persistent: true, diff --git a/tests/DTOs/QueueWorkerTest.php b/tests/DTOs/QueueWorkerTest.php index 76209c8..01197aa 100644 --- a/tests/DTOs/QueueWorkerTest.php +++ b/tests/DTOs/QueueWorkerTest.php @@ -21,6 +21,8 @@ fn (QueueConfig $config) => $config->alias === $worker)))->memoryLimit->toBe(128); expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $worker)))->timeout->toBe(60); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $worker)))->sleep->toBe(3); continue; } @@ -35,6 +37,8 @@ fn (QueueConfig $config) => $config->alias === $alias)))->memoryLimit->toBe($worker['memory_limit'] ?? 128); expect(Arr::first(array_filter($configObject, fn (QueueConfig $config) => $config->alias === $alias)))->timeout->toBe($worker['timeout'] ?? 60); + expect(Arr::first(array_filter($configObject, + fn (QueueConfig $config) => $config->alias === $alias)))->sleep->toBe($worker['sleep'] ?? 3); } })->with([ [ @@ -44,6 +48,7 @@ 'queues' => ['default'], 'memory_limit' => 64, 'timeout' => 60, + 'sleep' => 3, ], ], ], diff --git a/tests/Fakes/FakeQueueWorkerTest.php b/tests/Fakes/FakeQueueWorkerTest.php index 4b22f34..150f2a2 100644 --- a/tests/Fakes/FakeQueueWorkerTest.php +++ b/tests/Fakes/FakeQueueWorkerTest.php @@ -17,8 +17,8 @@ it('asserts up using callable', function () { swap(QueueWorkerContract::class, $fake = app(QueueWorkerFake::class)); - $fake->up(new QueueConfig('testA', ['default'], 123, 123)); - $fake->up(new QueueConfig('testB', ['default'], 123, 123)); + $fake->up(new QueueConfig('testA', ['default'], 123, 123, 0)); + $fake->up(new QueueConfig('testB', ['default'], 123, 123, 0)); $fake->assertUp(fn (QueueConfig $up) => $up->alias === 'testA'); $fake->assertUp(fn (QueueConfig $up) => $up->alias === 'testB'); diff --git a/tests/QueueWorker/QueueWorkerTest.php b/tests/QueueWorker/QueueWorkerTest.php index 2a6c137..7605a56 100644 --- a/tests/QueueWorker/QueueWorkerTest.php +++ b/tests/QueueWorker/QueueWorkerTest.php @@ -9,7 +9,7 @@ $workerName = 'some_worker'; - $config = new QueueConfig($workerName, ['default'], 128, 61); + $config = new QueueConfig($workerName, ['default'], 128, 61, 5); QueueWorker::up($config); @@ -20,6 +20,7 @@ '--queue=default', '--memory=128', '--timeout=61', + '--sleep=5', ]); expect($iniSettings)->toBe([ From 11cb34fdfeb5a30cc450296ae3f204024c54cd89 Mon Sep 17 00:00:00 2001 From: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Thu, 8 May 2025 05:53:06 -0300 Subject: [PATCH 098/105] Updated get method in Settings.php to handle default values better (#562) --- src/Settings.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Settings.php b/src/Settings.php index e849c72..ddcc030 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -17,7 +17,13 @@ public function set(string $key, $value): void public function get(string $key, $default = null): mixed { - return $this->client->get('settings/'.$key)->json('value') ?? $default; + $response = $this->client->get('settings/'.$key)->json('value'); + + if ($response === null) { + return $default instanceof \Closure ? $default() : $default; + } + + return $response; } public function forget(string $key): void From 9d1fc92db58474650f87d4a94e0846f6ad7e85d7 Mon Sep 17 00:00:00 2001 From: Willem Leuverink Date: Mon, 12 May 2025 13:09:22 +0200 Subject: [PATCH 099/105] Automatically apply PreventBrowserAccess middleware (#551) * skip when running tests * skip when not in Electron context * automatically apply middleware to 'web' group * apply middleware to all routes --- src/Http/Middleware/PreventRegularBrowserAccess.php | 4 ++++ src/NativeServiceProvider.php | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/Http/Middleware/PreventRegularBrowserAccess.php b/src/Http/Middleware/PreventRegularBrowserAccess.php index de1c72b..6722a3d 100644 --- a/src/Http/Middleware/PreventRegularBrowserAccess.php +++ b/src/Http/Middleware/PreventRegularBrowserAccess.php @@ -9,6 +9,10 @@ class PreventRegularBrowserAccess { public function handle(Request $request, Closure $next) { + if (! config('nativephp-internal.running')) { + return $next($request); + } + // Explicitly skip for the cookie-setting route if ($request->path() === '_native/api/cookie') { return $next($request); diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index ad222f9..74dfba3 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Console\Application; use Illuminate\Foundation\Application as Foundation; +use Illuminate\Foundation\Http\Kernel; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; @@ -23,6 +24,7 @@ use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; use Native\Laravel\GlobalShortcut as GlobalShortcutImplementation; +use Native\Laravel\Http\Middleware\PreventRegularBrowserAccess; use Native\Laravel\Logging\LogWatcher; use Native\Laravel\PowerMonitor as PowerMonitorImplementation; use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; @@ -84,6 +86,11 @@ public function packageRegistered() Handler::class ); + // Automatically prevent browser access + $this->app->make(Kernel::class)->pushMiddleware( + PreventRegularBrowserAccess::class, + ); + Application::starting(function ($app) { $app->resolveCommands([ LoadStartupConfigurationCommand::class, From 9175e4648f5f5f16f60adff33b2427020eebc953 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 13 May 2025 08:19:02 +0200 Subject: [PATCH 100/105] fix: use queue:listen in local environment for QueueWorker (#587) --- src/QueueWorker.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/QueueWorker.php b/src/QueueWorker.php index f9c2ea1..2c99808 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -24,9 +24,13 @@ public function up(string|QueueConfig $config): void throw new \InvalidArgumentException("Invalid queue configuration alias [$config]"); } + $command = app()->isLocal() + ? 'queue:listen' + : 'queue:work'; + $this->childProcess->artisan( [ - 'queue:work', + $command, "--name={$config->alias}", '--queue='.implode(',', $config->queuesToConsume), "--memory={$config->memoryLimit}", From 584033d7c0d3d5ea466ca0371b09e5c5cfb380ae Mon Sep 17 00:00:00 2001 From: Chris Keller <67823070+chr15k@users.noreply.github.com> Date: Sat, 17 May 2025 11:30:37 -0400 Subject: [PATCH 101/105] feat/queue-sleep-option-config-type-hint-update | update sleep type hint to allow float values (#583) --- src/DTOs/QueueConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DTOs/QueueConfig.php b/src/DTOs/QueueConfig.php index df04d0c..29308a0 100644 --- a/src/DTOs/QueueConfig.php +++ b/src/DTOs/QueueConfig.php @@ -12,7 +12,7 @@ public function __construct( public readonly array $queuesToConsume, public readonly int $memoryLimit, public readonly int $timeout, - public readonly int $sleep, + public readonly int|float $sleep, ) {} /** From 2aa5e4ff01b65baaaea9dc0528872167d99721f3 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 21 May 2025 20:12:51 +0200 Subject: [PATCH 102/105] fix: remove App facade from composer.json aliases (#586) --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 73e68c5..7d55ade 100644 --- a/composer.json +++ b/composer.json @@ -85,7 +85,6 @@ "Native\\Laravel\\NativeServiceProvider" ], "aliases": { - "App": "Native\\Laravel\\Facades\\App", "ChildProcess": "Native\\Laravel\\Facades\\ChildProcess", "Clipboard": "Native\\Laravel\\Facades\\Clipboard", "ContextMenu": "Native\\Laravel\\Facades\\ContextMenu", From 3b79aa9cef55acc17893854af74db822d1f059ae Mon Sep 17 00:00:00 2001 From: WINBIGFOX Date: Thu, 22 May 2025 14:41:58 +0200 Subject: [PATCH 103/105] Add support for detailed auto-updater event handling (#590) Expanded event constructors to include more detailed update metadata such as version, files, release information, and system requirements. Added a `downloadUpdate` method to the AutoUpdater facade. These updates enhance event broadcasting and improve client interaction with the auto-updater. --- src/AutoUpdater.php | 5 +++ src/Events/AutoUpdater/DownloadProgress.php | 8 ++++- src/Events/AutoUpdater/Error.php | 6 +++- src/Events/AutoUpdater/UpdateAvailable.php | 10 +++++- src/Events/AutoUpdater/UpdateCancelled.php | 31 +++++++++++++++++++ src/Events/AutoUpdater/UpdateDownloaded.php | 11 ++++++- src/Events/AutoUpdater/UpdateNotAvailable.php | 10 ++++++ src/Facades/AutoUpdater.php | 1 + 8 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/Events/AutoUpdater/UpdateCancelled.php diff --git a/src/AutoUpdater.php b/src/AutoUpdater.php index e714a0f..7950045 100644 --- a/src/AutoUpdater.php +++ b/src/AutoUpdater.php @@ -17,4 +17,9 @@ public function quitAndInstall(): void { $this->client->post('auto-updater/quit-and-install'); } + + public function downloadUpdate(): void + { + $this->client->post('auto-updater/download-update'); + } } diff --git a/src/Events/AutoUpdater/DownloadProgress.php b/src/Events/AutoUpdater/DownloadProgress.php index 42045b2..2339315 100644 --- a/src/Events/AutoUpdater/DownloadProgress.php +++ b/src/Events/AutoUpdater/DownloadProgress.php @@ -12,7 +12,13 @@ class DownloadProgress implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public int $total, public int $delta, public int $transferred, public float $percent, public int $bytesPerSecond) {} + public function __construct( + public int $total, + public int $delta, + public int $transferred, + public float $percent, + public int $bytesPerSecond + ) {} public function broadcastOn() { diff --git a/src/Events/AutoUpdater/Error.php b/src/Events/AutoUpdater/Error.php index 8444dcd..9d2219d 100644 --- a/src/Events/AutoUpdater/Error.php +++ b/src/Events/AutoUpdater/Error.php @@ -12,7 +12,11 @@ class Error implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public string $error) {} + public function __construct( + public string $name, + public string $message, + public ?string $stack, + ) {} public function broadcastOn() { diff --git a/src/Events/AutoUpdater/UpdateAvailable.php b/src/Events/AutoUpdater/UpdateAvailable.php index 5dc55cd..1fb05fb 100644 --- a/src/Events/AutoUpdater/UpdateAvailable.php +++ b/src/Events/AutoUpdater/UpdateAvailable.php @@ -12,7 +12,15 @@ class UpdateAvailable implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct() {} + public function __construct( + public string $version, + public array $files, + public string $releaseDate, + public ?string $releaseName, + public string|array|null $releaseNotes, + public ?int $stagingPercentage, + public ?string $minimumSystemVersion, + ) {} public function broadcastOn() { diff --git a/src/Events/AutoUpdater/UpdateCancelled.php b/src/Events/AutoUpdater/UpdateCancelled.php new file mode 100644 index 0000000..09d01b6 --- /dev/null +++ b/src/Events/AutoUpdater/UpdateCancelled.php @@ -0,0 +1,31 @@ + Date: Thu, 22 May 2025 13:43:16 +0100 Subject: [PATCH 104/105] chore(deps): bump dependabot/fetch-metadata from 2.3.0 to 2.4.0 (#585) Bumps [dependabot/fetch-metadata](https://github.com/dependabot/fetch-metadata) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/dependabot/fetch-metadata/releases) - [Commits](https://github.com/dependabot/fetch-metadata/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: dependabot/fetch-metadata dependency-version: 2.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependabot-auto-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 2cb1d5e..1a13177 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.3.0 + uses: dependabot/fetch-metadata@v2.4.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" From 787a9d660bd79d22432d7f111ecfd485642db33a Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Tue, 17 Jun 2025 07:24:44 +0200 Subject: [PATCH 105/105] fix: symfony console deprecation notice (#611) --- src/Commands/DebugCommand.php | 7 +++++-- src/Commands/FreshCommand.php | 5 +++++ src/Commands/LoadPHPConfigurationCommand.php | 5 +++++ src/Commands/LoadStartupConfigurationCommand.php | 5 +++++ src/Commands/MigrateCommand.php | 7 +++++-- src/Commands/SeedDatabaseCommand.php | 9 +++++---- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Commands/DebugCommand.php b/src/Commands/DebugCommand.php index 2bb7633..504ef7b 100644 --- a/src/Commands/DebugCommand.php +++ b/src/Commands/DebugCommand.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Process; use Native\Laravel\Support\Environment; +use Symfony\Component\Console\Attribute\AsCommand; use function Laravel\Prompts\error; use function Laravel\Prompts\info; @@ -17,12 +18,14 @@ use function Laravel\Prompts\outro; use function Laravel\Prompts\select; +#[AsCommand( + name: 'native:debug', + description: 'Generate debug information required for opening an issue.', +)] class DebugCommand extends Command implements PromptsForMissingInput { protected $signature = 'native:debug {output}'; - protected $description = 'Generate debug information required for opening an issue.'; - private Collection $debugInfo; public function handle(): void diff --git a/src/Commands/FreshCommand.php b/src/Commands/FreshCommand.php index 96746c6..42e65f6 100644 --- a/src/Commands/FreshCommand.php +++ b/src/Commands/FreshCommand.php @@ -4,7 +4,12 @@ use Illuminate\Database\Console\Migrations\FreshCommand as BaseFreshCommand; use Native\Laravel\NativeServiceProvider; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand( + name: 'native:migrate:fresh', + description: 'Drop all tables and re-run all migrations in the NativePHP development environment', +)] class FreshCommand extends BaseFreshCommand { protected $name = 'native:migrate:fresh'; diff --git a/src/Commands/LoadPHPConfigurationCommand.php b/src/Commands/LoadPHPConfigurationCommand.php index 1a2ba57..c68899f 100644 --- a/src/Commands/LoadPHPConfigurationCommand.php +++ b/src/Commands/LoadPHPConfigurationCommand.php @@ -4,7 +4,12 @@ use Illuminate\Console\Command; use Native\Laravel\Contracts\ProvidesPhpIni; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand( + name: 'native:php-ini', + description: 'Load the PHP configuration for the NativePHP development environment', +)] class LoadPHPConfigurationCommand extends Command { protected $signature = 'native:php-ini'; diff --git a/src/Commands/LoadStartupConfigurationCommand.php b/src/Commands/LoadStartupConfigurationCommand.php index c1907a2..540b55d 100644 --- a/src/Commands/LoadStartupConfigurationCommand.php +++ b/src/Commands/LoadStartupConfigurationCommand.php @@ -3,7 +3,12 @@ namespace Native\Laravel\Commands; use Illuminate\Console\Command; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand( + name: 'native:config', + description: 'Load the startup configuration for the NativePHP development environment', +)] class LoadStartupConfigurationCommand extends Command { protected $signature = 'native:config'; diff --git a/src/Commands/MigrateCommand.php b/src/Commands/MigrateCommand.php index 9093d67..77ec5f3 100644 --- a/src/Commands/MigrateCommand.php +++ b/src/Commands/MigrateCommand.php @@ -6,11 +6,14 @@ use Illuminate\Database\Console\Migrations\MigrateCommand as BaseMigrateCommand; use Illuminate\Database\Migrations\Migrator; use Native\Laravel\NativeServiceProvider; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand( + name: 'native:migrate', + description: 'Run the database migrations in the NativePHP development environment', +)] class MigrateCommand extends BaseMigrateCommand { - protected $description = 'Run the database migrations in the NativePHP development environment'; - public function __construct(Migrator $migrator, Dispatcher $dispatcher) { $this->signature = 'native:'.$this->signature; diff --git a/src/Commands/SeedDatabaseCommand.php b/src/Commands/SeedDatabaseCommand.php index cdc032e..68b225a 100644 --- a/src/Commands/SeedDatabaseCommand.php +++ b/src/Commands/SeedDatabaseCommand.php @@ -4,13 +4,14 @@ use Illuminate\Database\Console\Seeds\SeedCommand as BaseSeedCommand; use Native\Laravel\NativeServiceProvider; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand( + name: 'native:seed', + description: 'Seed the database in the NativePHP development environment', +)] class SeedDatabaseCommand extends BaseSeedCommand { - protected $name = 'native:db:seed'; - - protected $description = 'Run the database seeders in the NativePHP development environment'; - public function handle() { (new NativeServiceProvider($this->laravel))->rewriteDatabase();