diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f4547857..29e73812 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: diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index eb79b6e0..4ae67bf2 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' diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a1909f62..68e6ca9b 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] - laravel: [12.*, 11.*, 10.*] + php: [8.4, 8.3] + laravel: [12.*, 11.*] 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/README.md b/README.md index 4308c95a..c1284be9 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 diff --git a/composer.json b/composer.json index 71336edd..7d55ade2 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" @@ -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", diff --git a/config/nativephp-internal.php b/config/nativephp-internal.php index 4210df92..bb7dbf23 100644 --- a/config/nativephp-internal.php +++ b/config/nativephp-internal.php @@ -29,4 +29,27 @@ * 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'), + ], + + /** + * 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/config/nativephp.php b/config/nativephp.php index 6e4edcf1..d68245fc 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -31,6 +31,21 @@ */ 'author' => env('NATIVEPHP_APP_AUTHOR'), + /** + * The copyright notice for your application. + */ + '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 @@ -48,6 +63,7 @@ 'GITHUB_*', 'DO_SPACES_*', '*_SECRET', + 'ZEPHPYR_*', 'NATIVEPHP_UPDATER_PATH', 'NATIVEPHP_APPLE_ID', 'NATIVEPHP_APPLE_ID_PASS', @@ -60,6 +76,8 @@ * You may use glob / wildcard patterns here. */ 'cleanup_exclude_files' => [ + 'build', + 'temp', 'content', 'node_modules', '*/tests', @@ -123,6 +141,7 @@ 'queues' => ['default'], 'memory_limit' => 128, 'timeout' => 60, + 'sleep' => 3, ], ], @@ -136,4 +155,9 @@ 'postbuild' => [ // 'rm -rf public/build', ], + + /** + * Custom PHP binary path. + */ + 'binary_path' => env('NATIVEPHP_PHP_BINARY_PATH', null), ]; diff --git a/src/Alert.php b/src/Alert.php new file mode 100644 index 00000000..1303721c --- /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/App.php b/src/App.php index 81f1a26c..f7fa1c3a 100644 --- a/src/App.php +++ b/src/App.php @@ -3,11 +3,22 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Phar; class App { public function __construct(protected Client $client) {} + 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'); @@ -57,4 +68,23 @@ public function clearRecentDocuments(): void { $this->client->delete('app/recent-documents'); } + + public function isRunningBundled(): bool + { + return Phar::running() !== ''; + + } + + 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/AutoUpdater.php b/src/AutoUpdater.php new file mode 100644 index 00000000..e714a0f1 --- /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/ChildProcess.php b/src/ChildProcess.php index f524370c..6ba99721 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 @@ -80,7 +82,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 +92,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 +102,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 @@ -146,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; } diff --git a/src/Commands/DebugCommand.php b/src/Commands/DebugCommand.php new file mode 100644 index 00000000..2bb7633d --- /dev/null +++ b/src/Commands/DebugCommand.php @@ -0,0 +1,167 @@ +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 (Environment::isWindows()) { + $locationCommand = 'where'; + } + + info('Generating Environment Data...'); + $environment = [ + 'PHP' => [ + 'Version' => phpversion(), + 'Path' => PHP_BINARY, + ], + 'Laravel' => [ + 'Version' => app()->version(), + 'ConfigCached' => $this->laravel->configurationIsCached(), + 'RoutesCached' => $this->laravel->routesAreCached(), + '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 (Environment::isWindows()) { + Process::run('echo '.escapeshellarg($json).' | clip'); + } elseif (Environment::isLinux()) { + Process::run('echo '.escapeshellarg($json).' | xclip -selection clipboard'); + } else { + Process::run('echo '.escapeshellarg($json).' | pbcopy'); + } + } +} diff --git a/src/Contracts/ChildProcess.php b/src/Contracts/ChildProcess.php index 9859e3e6..0a4f8777 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/DTOs/QueueConfig.php b/src/DTOs/QueueConfig.php index 0ec3ca01..29308a03 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|float $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/Enums/SystemThemesEnum.php b/src/Enums/SystemThemesEnum.php new file mode 100644 index 00000000..e290dbca --- /dev/null +++ b/src/Enums/SystemThemesEnum.php @@ -0,0 +1,10 @@ +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/Http/Middleware/PreventRegularBrowserAccess.php b/src/Http/Middleware/PreventRegularBrowserAccess.php index de1c72bf..6722a3da 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/Menu/MenuBuilder.php b/src/Menu/MenuBuilder.php index f0627f78..f2ef764a 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); } diff --git a/src/MenuBar/MenuBar.php b/src/MenuBar/MenuBar.php index 13a57092..c3b6ce8a 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/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index b5b32252..74dfba3c 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -4,10 +4,12 @@ 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; 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; @@ -22,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; @@ -35,8 +38,9 @@ public function configurePackage(Package $package): void $package ->name('nativephp') ->hasCommands([ - MigrateCommand::class, + DebugCommand::class, FreshCommand::class, + MigrateCommand::class, SeedDatabaseCommand::class, ]) ->hasConfigFile() @@ -49,7 +53,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']); }); @@ -83,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, @@ -148,6 +156,7 @@ public function rewriteDatabase() { $databasePath = config('nativephp-internal.database_path'); + // Automatically create the database in development mode if (config('app.debug')) { $databasePath = database_path('nativephp.sqlite'); @@ -169,6 +178,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;'); diff --git a/src/QueueWorker.php b/src/QueueWorker.php index 1eb0c000..2c998080 100644 --- a/src/QueueWorker.php +++ b/src/QueueWorker.php @@ -24,24 +24,29 @@ public function up(string|QueueConfig $config): void throw new \InvalidArgumentException("Invalid queue configuration alias [$config]"); } - $this->childProcess->php( + $command = app()->isLocal() + ? 'queue:listen' + : 'queue:work'; + + $this->childProcess->artisan( [ - '-d', - "memory_limit={$config->memoryLimit}M", - 'artisan', - 'queue:work', + $command, "--name={$config->alias}", '--queue='.implode(',', $config->queuesToConsume), "--memory={$config->memoryLimit}", "--timeout={$config->timeout}", + "--sleep={$config->sleep}", ], - $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); } } diff --git a/src/Settings.php b/src/Settings.php index e849c729..ddcc0302 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 diff --git a/src/Support/Environment.php b/src/Support/Environment.php new file mode 100644 index 00000000..70f20367 --- /dev/null +++ b/src/Support/Environment.php @@ -0,0 +1,31 @@ +translateFromWindowsString(exec('tzutil /g')); } else { $timezone = $timezones->translateFromAbbreviatedString(exec('date +%Z')); @@ -87,4 +89,17 @@ public function timezone(): string return $timezone; } + + public function theme(?SystemThemesEnum $theme = null): SystemThemesEnum + { + if ($theme) { + $result = $this->client->post('system/theme', [ + 'theme' => $theme, + ])->json('result'); + } else { + $result = $this->client->get('system/theme')->json('result'); + } + + return SystemThemesEnum::from($result); + } } diff --git a/tests/DTOs/QueueWorkerTest.php b/tests/DTOs/QueueWorkerTest.php index 76209c8b..01197aac 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/FakeChildProcessTest.php b/tests/Fakes/FakeChildProcessTest.php index 57b0c354..aae9d264 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/Fakes/FakeQueueWorkerTest.php b/tests/Fakes/FakeQueueWorkerTest.php index 4b22f34d..150f2a28 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/MenuBar/MenuBarTest.php b/tests/MenuBar/MenuBarTest.php index 4b86aa95..79dbb801 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']); diff --git a/tests/QueueWorker/QueueWorkerTest.php b/tests/QueueWorker/QueueWorkerTest.php index a3fbd576..7605a565 100644 --- a/tests/QueueWorker/QueueWorkerTest.php +++ b/tests/QueueWorker/QueueWorkerTest.php @@ -6,23 +6,28 @@ 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, 5); 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', + '--sleep=5', + ]); + + expect($iniSettings)->toBe([ + 'memory_limit' => '128M', ]); - expect($alias)->toBe('some_worker'); + expect($alias)->toBe('queue_some_worker'); expect($env)->toBeNull(); expect($persistent)->toBeTrue(); @@ -35,5 +40,5 @@ QueueWorker::down('some_worker'); - ChildProcess::assertStop('some_worker'); + ChildProcess::assertStop('queue_some_worker'); });