diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index eb537d8c..2cb1d5e1 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 }}" diff --git a/.github/workflows/fix-php-code-style-issues.yml b/.github/workflows/fix-php-code-style-issues.yml index 255506b8..eb79b6e0 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 - uses: aglipanci/laravel-pint-action@2.4 + 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 + diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 107761bf..a1909f62 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/CHANGELOG.md b/CHANGELOG.md index 7c33f8d4..1e898901 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 diff --git a/README.md b/README.md index 45e26d39..4308c95a 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 diff --git a/composer.json b/composer.json index 9f7b760f..71336edd 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": { @@ -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/config/nativephp.php b/config/nativephp.php index 5c6e7a8f..6e4edcf1 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', ], /** @@ -115,6 +115,9 @@ ], ], + /** + * The queue workers that get auto-started on your application start. + */ 'queue_workers' => [ 'default' => [ 'queues' => ['default'], @@ -122,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', + ], ]; diff --git a/phpstan.neon b/phpstan.neon index 33be1e3a..b6543b0b 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 b3b7a714..1a2ba579 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/Commands/MinifyApplicationCommand.php b/src/Commands/MinifyApplicationCommand.php deleted file mode 100644 index 37d8378b..00000000 --- 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 7b5d3ad1..00000000 --- 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('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; - } - } -} diff --git a/src/Events/Notifications/NotificationActionClicked.php b/src/Events/Notifications/NotificationActionClicked.php new file mode 100644 index 00000000..de658b3c --- /dev/null +++ b/src/Events/Notifications/NotificationActionClicked.php @@ -0,0 +1,23 @@ +client->post('menu-bar/resize', [ + 'width' => $width, + 'height' => $height, + ]); + } + public function contextMenu(Menu $contextMenu) { $this->client->post('menu-bar/context-menu', [ diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 6ca8d626..b5b32252 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -12,7 +12,6 @@ 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\GlobalShortcut as GlobalShortcutContract; @@ -39,7 +38,6 @@ public function configurePackage(Package $package): void MigrateCommand::class, FreshCommand::class, SeedDatabaseCommand::class, - MinifyApplicationCommand::class, ]) ->hasConfigFile() ->hasRoute('api') diff --git a/src/Notification.php b/src/Notification.php index 8bde4d59..578d860e 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -6,12 +6,20 @@ class Notification { + public ?string $reference = null; + protected string $title; protected string $body; protected string $event = ''; + private bool $hasReply = false; + + private string $replyPlaceholder = ''; + + private array $actions = []; + final public function __construct(protected Client $client) { $this->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; } } diff --git a/tests/Command/IgnoreFilesAndFoldersTest.php b/tests/Command/IgnoreFilesAndFoldersTest.php deleted file mode 100644 index e805cc0a..00000000 --- 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'); - } -} diff --git a/tests/DTOs/QueueWorkerTest.php b/tests/DTOs/QueueWorkerTest.php index bc1b764f..76209c8b 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, + ], ], ], ],