From e05faa4dffdb19c9b5c2d8219100fe5aa8f324eb Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sun, 24 Nov 2024 13:53:34 +0800 Subject: [PATCH 01/35] wip Signed-off-by: Mior Muhammad Zaki --- tests/DefaultConfigurationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/DefaultConfigurationTest.php b/tests/DefaultConfigurationTest.php index 1c27729a..b7126289 100644 --- a/tests/DefaultConfigurationTest.php +++ b/tests/DefaultConfigurationTest.php @@ -75,6 +75,7 @@ public function it_uses_mutable_dates_by_default() $this->assertNotInstanceOf(DateTimeImmutable::class, $date); } + /** @test */ public function it_resolve_the_default_user_model() { $this->assertSame(User::class, $this->app['config']['auth.providers.users.model']); From 6a333a227430b9cb7296409c64ddb0e988362357 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 07:27:30 +0800 Subject: [PATCH 02/35] [7.x] Backport `Commander` changes from Testbench 9 (#268) Signed-off-by: Mior Muhammad Zaki --- src/Console/Commander.php | 63 +++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 6574fa42..6aec8014 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -2,7 +2,6 @@ namespace Orchestra\Testbench\Console; -use Illuminate\Console\Command; use Illuminate\Console\Concerns\InteractsWithSignals; use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Contracts\Debug\ExceptionHandler; @@ -10,7 +9,7 @@ use Illuminate\Foundation\Application as LaravelApplication; use Illuminate\Support\Arr; use Illuminate\Support\Collection; -use Orchestra\Testbench\Foundation\Application; +use Orchestra\Testbench\Foundation\Application as Testbench; use Orchestra\Testbench\Foundation\Bootstrap\LoadMigrationsFromArray; use Orchestra\Testbench\Foundation\Config; use Orchestra\Testbench\Foundation\Console\Concerns\CopyTestbenchFiles; @@ -65,6 +64,22 @@ class Commander */ protected $environmentFile = '.env'; + /** + * The testbench implementation class. + * + * @var class-string<\Orchestra\Testbench\Foundation\Application> + */ + protected static string $testbench = Testbench::class; + + /** + * List of providers. + * + * @var array> + */ + protected array $providers = [ + TestbenchServiceProvider::class, + ]; + /** * Construct a new Commander. * @@ -103,7 +118,7 @@ public function handle() } finally { $this->handleTerminatingConsole(); Workbench::flush(); - Application::flushState(); + static::$testbench::flushState(); $this->untrap(); } @@ -123,7 +138,7 @@ public function laravel() $hasEnvironmentFile = fn () => file_exists(join_paths($laravelBasePath, '.env')); - tap(Application::createVendorSymlink($laravelBasePath, join_paths($this->workingPath, 'vendor')), function ($app) use ($hasEnvironmentFile) { + tap(static::$testbench::createVendorSymlink($laravelBasePath, join_paths($this->workingPath, 'vendor')), function ($app) use ($hasEnvironmentFile) { $filesystem = new Filesystem; $this->copyTestbenchConfigurationFile($app, $filesystem, $this->workingPath); @@ -133,25 +148,13 @@ public function laravel() } }); - $options = array_filter([ - 'load_environment_variables' => $hasEnvironmentFile(), - 'extra' => $this->config->getExtraAttributes(), - ]); - - $this->app = Application::create( + $this->app = static::$testbench::create( basePath: $this->getBasePath(), - resolvingCallback: function ($app) { - Workbench::startWithProviders($app, $this->config); - Workbench::discoverRoutes($app, $this->config); - - (new LoadMigrationsFromArray( - $this->config['migrations'] ?? [], - $this->config['seeders'] ?? false, - ))->bootstrap($app); - - \call_user_func($this->resolveApplicationCallback(), $app); - }, - options: $options, + resolvingCallback: $this->resolveApplicationCallback(), + options: array_filter([ + 'load_environment_variables' => $hasEnvironmentFile(), + 'extra' => $this->config->getExtraAttributes(), + ]), ); } @@ -165,8 +168,18 @@ public function laravel() */ protected function resolveApplicationCallback() { - return static function ($app) { - $app->register(TestbenchServiceProvider::class); + return function ($app) { + Workbench::startWithProviders($app, $this->config); + Workbench::discoverRoutes($app, $this->config); + + (new LoadMigrationsFromArray( + $this->config['migrations'] ?? [], + $this->config['seeders'] ?? false, + ))->bootstrap($app); + + foreach ($this->providers as $provider) { + $app->register($provider); + } }; } @@ -195,7 +208,7 @@ protected function getBasePath() */ public static function applicationBasePath() { - return Application::applicationBasePath(); + return static::$testbench::applicationBasePath(); } /** From 65176dcf4a6db498827584ce74ced72b2876ad2c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 07:46:10 +0800 Subject: [PATCH 03/35] wip Signed-off-by: Mior Muhammad Zaki --- bin/configure-skeleton.php | 183 +++++++++++++++++++++++++++++++++++++ bin/sync | 173 +---------------------------------- 2 files changed, 184 insertions(+), 172 deletions(-) create mode 100644 bin/configure-skeleton.php diff --git a/bin/configure-skeleton.php b/bin/configure-skeleton.php new file mode 100644 index 00000000..06f802b7 --- /dev/null +++ b/bin/configure-skeleton.php @@ -0,0 +1,183 @@ +transform(fn ($file) => "{$workingPath}/skeleton/{$file}") + ->map(fn ($file) => str_contains($file, '*') ? [...$files->glob($file)] : $file) + ->flatten() + ->each(function ($file) use ($files, $workingPath) { + $files->copy($file, "{$workingPath}/laravel".Illuminate\Support\Str::after($file, "{$workingPath}/skeleton")); + }); +$files->delete("{$workingPath}/laravel/config/sanctum.php"); +$files->move("{$workingPath}/laravel/database/migrations/2014_10_12_000000_create_users_table.php", "{$workingPath}/laravel/migrations/2014_10_12_000000_testbench_create_users_table.php"); +$files->move("{$workingPath}/laravel/database/migrations/2014_10_12_100000_create_password_resets_table.php", "{$workingPath}/laravel/migrations/2014_10_12_100000_testbench_create_password_resets_table.php"); +$files->move("{$workingPath}/laravel/database/migrations/2019_08_19_000000_create_failed_jobs_table.php", "{$workingPath}/laravel/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php"); + +collect([ + 'cache/0001_01_01_000000_testbench_create_cache_table' => 'Cache/Console/stubs/cache.stub', + 'notifications/0001_01_01_000000_testbench_create_notifications_table' => 'Notifications/Console/stubs/notifications.stub', + 'queue/0001_01_01_000000_testbench_create_jobs_table' => 'Queue/Console/stubs/jobs.stub', + 'queue/0001_01_01_000000_testbench_create_job_batches_table' => 'Queue/Console/stubs/batches.stub', + // 'queue/0001_01_01_000000_testbench_create_failed_jobs_table' => 'Queue/Console/stubs/failed_jobs.stub', + 'session/0001_01_01_000000_testbench_create_sessions_table' => 'Session/Console/stubs/database.stub', +])->transform(fn ($file) => "{$workingPath}/vendor/laravel/framework/src/Illuminate/{$file}") + ->each(function ($from, $to) use ($files, $workingPath) { + $files->copy($from, "{$workingPath}/laravel/migrations/{$to}.php"); + })->keys() + ->push(...[ + '2014_10_12_000000_testbench_create_users_table', + '2014_10_12_100000_testbench_create_password_resets_table', + '2019_08_19_000000_testbench_create_failed_jobs_table', + ])->each(function ($migration) use ($files, $workingPath) { + $files->replaceInFile('class Create', 'class TestbenchCreate', "{$workingPath}/laravel/migrations/{$migration}.php"); + })->filter(fn ($migration) => str_starts_with($migration, 'queue')) + ->mapWithKeys(fn ($migration) => match ($migration) { + 'queue/0001_01_01_000000_testbench_create_jobs_table' => [$migration => 'jobs'], + 'queue/0001_01_01_000000_testbench_create_job_batches_table' => [$migration => 'job_batches'], + // 'queue/0001_01_01_000000_testbench_create_failed_jobs_table' => [$migration => 'failed_jobs'], + })->each(function ($table, $migration) use ($files, $workingPath) { + $files->replaceInFile(['{{tableClassName}}', '{{table}}'], [Illuminate\Support\Str::studly($table), $table], "{$workingPath}/laravel/migrations/{$migration}.php"); + }); + +transform([ + line('APP_KEY=', 0) => line('APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF', 0), + line('DB_CONNECTION=mysql', 0) => line('DB_CONNECTION=sqlite', 0), + line('DB_HOST=', 0) => line('# DB_HOST=', 0), + line('DB_PORT=', 0) => line('# DB_PORT=', 0), + line('DB_DATABASE=', 0) => line('# DB_DATABASE=', 0), + line('DB_USERNAME=', 0) => line('# DB_USERNAME=', 0), + line('DB_PASSWORD=', 0) => line('# DB_PASSWORD=', 0), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/.env.example")); + +transform([ + line("'env' => env('APP_ENV', 'production'),", 1) => line("'env' => env('APP_ENV', 'workbench'),", 1), + line("'timezone' => 'UTC',", 1) => line("'timezone' => env('APP_TIMEZONE', 'UTC'),", 1), + line("'locale' => 'en',", 1) => line("'locale' => env('APP_LOCALE', 'en'),", 1), + line("'fallback_locale' => 'en',", 1) => line("'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/app.php")); + +transform([ + line("'guard' => 'web',", 2) => line("'guard' => env('AUTH_GUARD', 'web'),", 2), + line("'passwords' => 'users',", 2) => line("'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),", 2), + line("'model' => App\Models\User::class,", 3) => line("'model' => env('AUTH_MODEL', Illuminate\Foundation\Auth\User::class),", 3), + // line("'table' => 'password_resets',", 3) => line("table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_resets'),", 3), + line("'password_timeout' => 10800,", 1) => line("'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/auth.php")); + +transform([ + line("'connection' => null,", 3) => line("'connection' => env('DB_CACHE_CONNECTION'),", 3), + line("'table' => 'cache',", 3) => line("'table' => env('DB_CACHE_TABLE', 'cache'),", 3), + line("'lock_connection' => null,", 3) => line("'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),", 3), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/cache.php")); + +transform([ + line("'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci',", 3) => line("'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),", 3), + line("'charset' => 'utf8',", 3) => line("'charset' => env('DB_CHARSET', 'utf8'),", 3), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/database.php")); + +transform([ + line("'driver' => 'bcrypt',", 1) => line("'driver' => env('HASH_DRIVER', 'bcrypt'),", 1), + line("'argon' => [ + 'memory' => 65536, + 'threads' => 1, + 'time' => 4,", 1) => line("'argon' => [ + 'memory' => env('ARGON_MEMORY', 65536), + 'threads' => env('ARGON_THREADS', 1), + 'time' => env('ARGON_TIME', 4),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/hashing.php")); + +transform([ + line("'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => false,", 1) => line("'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false),", 1), + line("'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'],", 2) => line("'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', env('LOG_STACK', 'single')),", 2), + line("'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 14,", 2) => line("'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14),", 2), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/logging.php")); + +transform([ + // line("'default' => env('MAIL_MAILER', 'smtp'),", 1) => line("'default' => env('MAIL_MAILER', 'log'),", 1), + line("'markdown' => [ + 'theme' => 'default',", 1) => line("'markdown' => [ + 'theme' => env('MAIL_MARKDOWN_THEME', 'default'),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/mail.php")); + +transform([ + line("'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 90,", 2) => line("'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),", 2), + line("'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + 'retry_after' => 90,", 2) => line("'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),", 2), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/queue.php")); + +transform([ + line("'expire_on_close' => false,", 1) => line("'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),", 1), + line("'encrypt' => false,", 1) => line("'encrypt' => env('SESSION_ENCRYPT', false),", 1), + line("'table' => 'sessions',", 1) => line("'table' => env('SESSION_TABLE', 'sessions'),", 1), + line("'path' => '/',", 1) => line("'path' => env('SESSION_PATH', '/'),", 1), + line("'http_only' => true,", 1) => line("'http_only' => env('SESSION_HTTP_ONLY', true),", 1), + line("'same_site' => 'lax',", 1) => line("'same_site' => env('SESSION_SAME_SITE', 'lax'),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); + +$files->copy("{$workingPath}/skeleton/database/factories/UserFactory.php", "{$workingPath}/src/Factories/UserFactory.php"); +transform([ + 'namespace Database\Factories;' => 'namespace Orchestra\Testbench\Factories;', + 'use Illuminate\Database\Eloquent\Factories\Factory;' => 'use Illuminate\Database\Eloquent\Factories\Factory;'.line('use Illuminate\Foundation\Auth\User;'), + ' * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>' => ' * @phpstan-type TModel \Illuminate\Foundation\Auth\User + * + * @extends \Illuminate\Database\Eloquent\Factories\Factory + * + * @property \Illuminate\Database\Eloquent\Model|TModel $model', + line('}'.PHP_EOL.'}'.PHP_EOL, 1) => line('} + + /** + * Get the name of the model that is generated by the factory. + * + * @return class-string<\Illuminate\Database\Eloquent\Model|TModel> + */ + public function modelName() + { + return $this->model ?? config(\'auth.providers.users.model\', User::class); + } +}'.PHP_EOL, 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/src/Factories/UserFactory.php")); diff --git a/bin/sync b/bin/sync index 04e19cbf..6f5fad40 100755 --- a/bin/sync +++ b/bin/sync @@ -38,189 +38,18 @@ Symfony\Component\Process\Process::fromShellCommandline( 'composer create-project "laravel/laravel:9.x-dev" skeleton --no-scripts --no-plugins --quiet --no-install', $workingPath )->mustRun(); -collect([ - 'artisan', - '.env.example', - 'config/*.php', - 'database/.gitignore', - 'database/migrations/2014_10_12_000000_create_users_table.php', - 'database/migrations/2014_10_12_100000_create_password_resets_table.php', - 'database/migrations/2019_08_19_000000_create_failed_jobs_table.php', - 'lang/en/*.php', - 'lang/*.json', - 'resources/views/*', - 'public/index.php', - 'tests/CreatesApplication.php', -])->transform(fn ($file) => "{$workingPath}/skeleton/{$file}") -->map(fn ($file) => str_contains($file, '*') ? [...$files->glob($file)] : $file) -->flatten() -->each(function ($file) use ($files, $workingPath) { - $files->copy($file, "{$workingPath}/laravel".Illuminate\Support\Str::after($file, "{$workingPath}/skeleton")); -}); -$files->delete("{$workingPath}/laravel/config/sanctum.php"); -$files->move("{$workingPath}/laravel/database/migrations/2014_10_12_000000_create_users_table.php", "{$workingPath}/laravel/migrations/2014_10_12_000000_testbench_create_users_table.php"); -$files->move("{$workingPath}/laravel/database/migrations/2014_10_12_100000_create_password_resets_table.php", "{$workingPath}/laravel/migrations/2014_10_12_100000_testbench_create_password_resets_table.php"); -$files->move("{$workingPath}/laravel/database/migrations/2019_08_19_000000_create_failed_jobs_table.php", "{$workingPath}/laravel/migrations/2019_08_19_000000_testbench_create_failed_jobs_table.php"); - -collect([ - 'cache/0001_01_01_000000_testbench_create_cache_table' => 'Cache/Console/stubs/cache.stub', - 'notifications/0001_01_01_000000_testbench_create_notifications_table' => 'Notifications/Console/stubs/notifications.stub', - 'queue/0001_01_01_000000_testbench_create_jobs_table' => 'Queue/Console/stubs/jobs.stub', - 'queue/0001_01_01_000000_testbench_create_job_batches_table' => 'Queue/Console/stubs/batches.stub', - // 'queue/0001_01_01_000000_testbench_create_failed_jobs_table' => 'Queue/Console/stubs/failed_jobs.stub', - 'session/0001_01_01_000000_testbench_create_sessions_table' => 'Session/Console/stubs/database.stub', -])->transform(fn ($file) => "{$workingPath}/vendor/laravel/framework/src/Illuminate/{$file}") -->each(function ($from, $to) use ($files, $workingPath) { - $files->copy($from, "{$workingPath}/laravel/migrations/{$to}.php"); -})->keys() -->push(...[ - '2014_10_12_000000_testbench_create_users_table', - '2014_10_12_100000_testbench_create_password_resets_table', - '2019_08_19_000000_testbench_create_failed_jobs_table', -])->each(function ($migration) use ($files, $workingPath) { - $files->replaceInFile('class Create', 'class TestbenchCreate', "{$workingPath}/laravel/migrations/{$migration}.php"); -})->filter(fn ($migration) => str_starts_with($migration, 'queue')) -->mapWithKeys(fn ($migration) => match ($migration) { - 'queue/0001_01_01_000000_testbench_create_jobs_table' => [$migration => 'jobs'], - 'queue/0001_01_01_000000_testbench_create_job_batches_table' => [$migration => 'job_batches'], - // 'queue/0001_01_01_000000_testbench_create_failed_jobs_table' => [$migration => 'failed_jobs'], -})->each(function ($table, $migration) use ($files, $workingPath) { - $files->replaceInFile(['{{tableClassName}}', '{{table}}'], [Illuminate\Support\Str::studly($table), $table], "{$workingPath}/laravel/migrations/{$migration}.php"); -}); +require Orchestra\Testbench\join_paths(__DIR__, 'configure-skeleton.php'); transform([ - line('APP_KEY=', 0) => line('APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF', 0), - line('DB_CONNECTION=mysql', 0) => line('DB_CONNECTION=sqlite', 0), - line('DB_HOST=', 0) => line('# DB_HOST=', 0), - line('DB_PORT=', 0) => line('# DB_PORT=', 0), - line('DB_DATABASE=', 0) => line('# DB_DATABASE=', 0), - line('DB_USERNAME=', 0) => line('# DB_USERNAME=', 0), - line('DB_PASSWORD=', 0) => line('# DB_PASSWORD=', 0), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/.env.example")); - -transform([ - line("'env' => env('APP_ENV', 'production'),", 1) => line("'env' => env('APP_ENV', 'workbench'),", 1), - line("'timezone' => 'UTC',", 1) => line("'timezone' => env('APP_TIMEZONE', 'UTC'),", 1), - line("'locale' => 'en',", 1) => line("'locale' => env('APP_LOCALE', 'en'),", 1), - line("'fallback_locale' => 'en',", 1) => line("'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),", 1), line('App\Providers', 2) => line('// App\Providers', 2), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/app.php")); -transform([ - line("'guard' => 'web',", 2) => line("'guard' => env('AUTH_GUARD', 'web'),", 2), - line("'passwords' => 'users',", 2) => line("'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),", 2), - line("'model' => App\Models\User::class,", 3) => line("'model' => env('AUTH_MODEL', Illuminate\Foundation\Auth\User::class),", 3), - // line("'table' => 'password_resets',", 3) => line("table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_resets'),", 3), - line("'password_timeout' => 10800,", 1) => line("'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),", 1), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/auth.php")); - transform([ line("'default' => env('CACHE_DRIVER', 'file'),", 1) => line("'default' => env('CACHE_DRIVER', 'array'),", 1), - line("'connection' => null,", 3) => line("'connection' => env('DB_CACHE_CONNECTION'),", 3), - line("'table' => 'cache',", 3) => line("'table' => env('DB_CACHE_TABLE', 'cache'),", 3), - line("'lock_connection' => null,", 3) => line("'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),", 3), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/cache.php")); -transform([ - line("'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci',", 3) => line("'charset' => env('DB_CHARSET', 'utf8mb4'), - 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),", 3), - line("'charset' => 'utf8',", 3) => line("'charset' => env('DB_CHARSET', 'utf8'),", 3), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/database.php")); - -transform([ - line("'driver' => 'bcrypt',", 1) => line("'driver' => env('HASH_DRIVER', 'bcrypt'),", 1), - line("'argon' => [ - 'memory' => 65536, - 'threads' => 1, - 'time' => 4,", 1) => line("'argon' => [ - 'memory' => env('ARGON_MEMORY', 65536), - 'threads' => env('ARGON_THREADS', 1), - 'time' => env('ARGON_TIME', 4),", 1), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/hashing.php")); - -transform([ - line("'deprecations' => [ - 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), - 'trace' => false,", 1) => line("'deprecations' => [ - 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), - 'trace' => env('LOG_DEPRECATIONS_TRACE', false),", 1), - line("'stack' => [ - 'driver' => 'stack', - 'channels' => ['single'],", 2) => line("'stack' => [ - 'driver' => 'stack', - 'channels' => explode(',', env('LOG_STACK', 'single')),", 2), - line("'daily' => [ - 'driver' => 'daily', - 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), - 'days' => 14,", 2) => line("'daily' => [ - 'driver' => 'daily', - 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), - 'days' => env('LOG_DAILY_DAYS', 14),", 2), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/logging.php")); - -transform([ - // line("'default' => env('MAIL_MAILER', 'smtp'),", 1) => line("'default' => env('MAIL_MAILER', 'log'),", 1), - line("'markdown' => [ - 'theme' => 'default',", 1) => line("'markdown' => [ - 'theme' => env('MAIL_MARKDOWN_THEME', 'default'),", 1), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/mail.php")); - -transform([ - line("'database' => [ - 'driver' => 'database', - 'table' => 'jobs', - 'queue' => 'default', - 'retry_after' => 90,", 2) => line("'database' => [ - 'driver' => 'database', - 'connection' => env('DB_QUEUE_CONNECTION'), - 'table' => env('DB_QUEUE_TABLE', 'jobs'), - 'queue' => env('DB_QUEUE', 'default'), - 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),", 2), - line("'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => 'localhost', - 'queue' => 'default', - 'retry_after' => 90,", 2) => line("'beanstalkd' => [ - 'driver' => 'beanstalkd', - 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), - 'queue' => env('BEANSTALKD_QUEUE', 'default'), - 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),", 2), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/queue.php")); - transform([ line("'driver' => env('SESSION_DRIVER', 'file'),", 1) => line("'driver' => env('SESSION_DRIVER', 'array'),", 1), - line("'expire_on_close' => false,", 1) => line("'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),", 1), - line("'encrypt' => false,", 1) => line("'encrypt' => env('SESSION_ENCRYPT', false),", 1), - line("'table' => 'sessions',", 1) => line("'table' => env('SESSION_TABLE', 'sessions'),", 1), - line("'path' => '/',", 1) => line("'path' => env('SESSION_PATH', '/'),", 1), - line("'http_only' => true,", 1) => line("'http_only' => env('SESSION_HTTP_ONLY', true),", 1), - line("'same_site' => 'lax',", 1) => line("'same_site' => env('SESSION_SAME_SITE', 'lax'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); -$files->copy("{$workingPath}/skeleton/database/factories/UserFactory.php", "{$workingPath}/src/Factories/UserFactory.php"); -transform([ - 'namespace Database\Factories;' => 'namespace Orchestra\Testbench\Factories;', - 'use Illuminate\Database\Eloquent\Factories\Factory;' => 'use Illuminate\Database\Eloquent\Factories\Factory;'.line('use Illuminate\Foundation\Auth\User;'), - ' * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>' => ' * @phpstan-type TModel \Illuminate\Foundation\Auth\User - * - * @extends \Illuminate\Database\Eloquent\Factories\Factory - * - * @property \Illuminate\Database\Eloquent\Model|TModel $model', - line('}'.PHP_EOL.'}'.PHP_EOL, 1) => line('} - - /** - * Get the name of the model that is generated by the factory. - * - * @return class-string<\Illuminate\Database\Eloquent\Model|TModel> - */ - public function modelName() - { - return $this->model ?? config(\'auth.providers.users.model\', User::class); - } -}'.PHP_EOL, 1), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/src/Factories/UserFactory.php")); - $purgeSkeletonDirectories(); From 0e37a8a1ff4b07c1c9386673fbf2c1ece26476cc Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 07:49:45 +0800 Subject: [PATCH 04/35] wip Signed-off-by: Mior Muhammad Zaki --- bin/configure-skeleton.php | 23 ----------------------- bin/sync | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bin/configure-skeleton.php b/bin/configure-skeleton.php index 06f802b7..ceb7aded 100644 --- a/bin/configure-skeleton.php +++ b/bin/configure-skeleton.php @@ -158,26 +158,3 @@ line("'http_only' => true,", 1) => line("'http_only' => env('SESSION_HTTP_ONLY', true),", 1), line("'same_site' => 'lax',", 1) => line("'same_site' => env('SESSION_SAME_SITE', 'lax'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); - -$files->copy("{$workingPath}/skeleton/database/factories/UserFactory.php", "{$workingPath}/src/Factories/UserFactory.php"); -transform([ - 'namespace Database\Factories;' => 'namespace Orchestra\Testbench\Factories;', - 'use Illuminate\Database\Eloquent\Factories\Factory;' => 'use Illuminate\Database\Eloquent\Factories\Factory;'.line('use Illuminate\Foundation\Auth\User;'), - ' * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>' => ' * @phpstan-type TModel \Illuminate\Foundation\Auth\User - * - * @extends \Illuminate\Database\Eloquent\Factories\Factory - * - * @property \Illuminate\Database\Eloquent\Model|TModel $model', - line('}'.PHP_EOL.'}'.PHP_EOL, 1) => line('} - - /** - * Get the name of the model that is generated by the factory. - * - * @return class-string<\Illuminate\Database\Eloquent\Model|TModel> - */ - public function modelName() - { - return $this->model ?? config(\'auth.providers.users.model\', User::class); - } -}'.PHP_EOL, 1), -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/src/Factories/UserFactory.php")); diff --git a/bin/sync b/bin/sync index 6f5fad40..edd8f749 100755 --- a/bin/sync +++ b/bin/sync @@ -52,4 +52,27 @@ transform([ line("'driver' => env('SESSION_DRIVER', 'file'),", 1) => line("'driver' => env('SESSION_DRIVER', 'array'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); +$files->copy("{$workingPath}/skeleton/database/factories/UserFactory.php", "{$workingPath}/src/Factories/UserFactory.php"); +transform([ + 'namespace Database\Factories;' => 'namespace Orchestra\Testbench\Factories;', + 'use Illuminate\Database\Eloquent\Factories\Factory;' => 'use Illuminate\Database\Eloquent\Factories\Factory;'.line('use Illuminate\Foundation\Auth\User;'), + ' * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>' => ' * @phpstan-type TModel \Illuminate\Foundation\Auth\User + * + * @extends \Illuminate\Database\Eloquent\Factories\Factory + * + * @property \Illuminate\Database\Eloquent\Model|TModel $model', + line('}'.PHP_EOL.'}'.PHP_EOL, 1) => line('} + + /** + * Get the name of the model that is generated by the factory. + * + * @return class-string<\Illuminate\Database\Eloquent\Model|TModel> + */ + public function modelName() + { + return $this->model ?? config(\'auth.providers.users.model\', User::class); + } +}'.PHP_EOL, 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/src/Factories/UserFactory.php")); + $purgeSkeletonDirectories(); From da917a4511d537d6736fa16c8a9e19dfb3d8776d Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 08:13:44 +0800 Subject: [PATCH 05/35] wip Signed-off-by: Mior Muhammad Zaki --- bin/configure-skeleton.php | 1 - bin/sync | 4 ++++ src/Console/Commander.php | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bin/configure-skeleton.php b/bin/configure-skeleton.php index ceb7aded..5ad854f7 100644 --- a/bin/configure-skeleton.php +++ b/bin/configure-skeleton.php @@ -122,7 +122,6 @@ ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/logging.php")); transform([ - // line("'default' => env('MAIL_MAILER', 'smtp'),", 1) => line("'default' => env('MAIL_MAILER', 'log'),", 1), line("'markdown' => [ 'theme' => 'default',", 1) => line("'markdown' => [ 'theme' => env('MAIL_MARKDOWN_THEME', 'default'),", 1), diff --git a/bin/sync b/bin/sync index edd8f749..7457a6ec 100755 --- a/bin/sync +++ b/bin/sync @@ -48,6 +48,10 @@ transform([ line("'default' => env('CACHE_DRIVER', 'file'),", 1) => line("'default' => env('CACHE_DRIVER', 'array'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/cache.php")); +transform([ + // line("'default' => env('MAIL_MAILER', 'smtp'),", 1) => line("'default' => env('MAIL_MAILER', 'log'),", 1), +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/mail.php")); + transform([ line("'driver' => env('SESSION_DRIVER', 'file'),", 1) => line("'driver' => env('SESSION_DRIVER', 'array'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 6aec8014..58a3636f 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -134,11 +134,11 @@ public function handle() public function laravel() { if (! $this->app instanceof LaravelApplication) { - $laravelBasePath = $this->getBasePath(); + $APP_BASE_PATH = $this->getBasePath(); - $hasEnvironmentFile = fn () => file_exists(join_paths($laravelBasePath, '.env')); + $hasEnvironmentFile = fn () => file_exists(join_paths($APP_BASE_PATH, '.env')); - tap(static::$testbench::createVendorSymlink($laravelBasePath, join_paths($this->workingPath, 'vendor')), function ($app) use ($hasEnvironmentFile) { + tap(static::$testbench::createVendorSymlink($APP_BASE_PATH, join_paths($this->workingPath, 'vendor')), function ($app) use ($hasEnvironmentFile) { $filesystem = new Filesystem; $this->copyTestbenchConfigurationFile($app, $filesystem, $this->workingPath); @@ -149,7 +149,7 @@ public function laravel() }); $this->app = static::$testbench::create( - basePath: $this->getBasePath(), + basePath: $APP_BASE_PATH, resolvingCallback: $this->resolveApplicationCallback(), options: array_filter([ 'load_environment_variables' => $hasEnvironmentFile(), From 52959993d8962690dae5d0b7026afe3928e0e2c0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 11:04:41 +0800 Subject: [PATCH 06/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Workbench/Workbench.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index cc1a4d47..b7681dc6 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -52,7 +52,9 @@ class Workbench */ public static function start(ApplicationContract $app, ConfigContract $config): void { - $app->singleton(ConfigContract::class, static fn () => $config); + if (! $app->bound(ConfigContract::class)) { + $app->singleton(ConfigContract::class, static fn () => $config); + } } /** From 7767206280df180fc26b6e63cd7d82dad18012e7 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 11:19:56 +0800 Subject: [PATCH 07/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Workbench/Workbench.php | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index b7681dc6..eb0ad604 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -7,14 +7,15 @@ use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Routing\Router; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Orchestra\Testbench\Contracts\Config as ConfigContract; use Orchestra\Testbench\Foundation\Config; +use Orchestra\Workbench\AuthServiceProvider; use Orchestra\Workbench\WorkbenchServiceProvider; use ReflectionClass; use Symfony\Component\Finder\Finder; - use function Orchestra\Testbench\after_resolving; use function Orchestra\Testbench\package_path; use function Orchestra\Testbench\workbench_path; @@ -50,11 +51,22 @@ class Workbench * @param \Orchestra\Testbench\Contracts\Config $config * @return void */ - public static function start(ApplicationContract $app, ConfigContract $config): void + public static function start(ApplicationContract $app, ConfigContract $config, array $providers = []): void { if (! $app->bound(ConfigContract::class)) { $app->singleton(ConfigContract::class, static fn () => $config); } + + Collection::make($providers) + ->when( + Arr::get($config->getWorkbenchAttributes(), 'auth', false) === true, + static fn ($providers) => $providers->push(AuthServiceProvider::class) + ) + ->filter() + ->filter(static fn ($provider) => class_exists($provider)) + ->each(static function ($provider) use ($app) { + $app->register($provider); + }); } /** @@ -66,11 +78,9 @@ public static function start(ApplicationContract $app, ConfigContract $config): */ public static function startWithProviders(ApplicationContract $app, ConfigContract $config): void { - static::start($app, $config); - - if (class_exists(WorkbenchServiceProvider::class)) { - $app->register(WorkbenchServiceProvider::class); - } + static::start($app, $config, [ + WorkbenchServiceProvider::class, + ]); } /** From dfcafac74023da0176b6ce6b76dfb672654087f1 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 11:22:26 +0800 Subject: [PATCH 08/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Workbench/Workbench.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index eb0ad604..68989d40 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -16,6 +16,7 @@ use Orchestra\Workbench\WorkbenchServiceProvider; use ReflectionClass; use Symfony\Component\Finder\Finder; + use function Orchestra\Testbench\after_resolving; use function Orchestra\Testbench\package_path; use function Orchestra\Testbench\workbench_path; @@ -60,7 +61,7 @@ public static function start(ApplicationContract $app, ConfigContract $config, a Collection::make($providers) ->when( Arr::get($config->getWorkbenchAttributes(), 'auth', false) === true, - static fn ($providers) => $providers->push(AuthServiceProvider::class) + static fn ($providers) => $providers->push(AuthServiceProvider::class) // @phpstan-ignore class.notFound ) ->filter() ->filter(static fn ($provider) => class_exists($provider)) @@ -79,7 +80,7 @@ public static function start(ApplicationContract $app, ConfigContract $config, a public static function startWithProviders(ApplicationContract $app, ConfigContract $config): void { static::start($app, $config, [ - WorkbenchServiceProvider::class, + WorkbenchServiceProvider::class, // @phpstan-ignore class.notFound ]); } From 80f9d507fe7fd0dd22a2c5f6b0da5b06aaee24ff Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:11:03 +0800 Subject: [PATCH 09/35] wip Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- src/functions.php | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ae338cf3..d3839132 100644 --- a/composer.json +++ b/composer.json @@ -55,7 +55,7 @@ "brianium/paratest": "<6.4.0 || >=7.0.0", "laravel/framework": "<9.52.17 || >=10.0.0", "laravel/serializable-closure": "<1.3.0 || >=2.0.0", - "orchestra/testbench-dusk": "<7.39.0 || >=8.0.0", + "orchestra/testbench-dusk": "<7.50.0 || >=8.0.0", "orchestra/workbench": "<1.0.0", "nunomaduro/collision": "<6.2.0 || >=7.0.0", "phpunit/phpunit": "<9.5.10 || >=10.0.0" diff --git a/src/functions.php b/src/functions.php index 01bb9f99..8007add9 100644 --- a/src/functions.php +++ b/src/functions.php @@ -245,7 +245,13 @@ function transform_relative_path(string $path, string $workingPath): string */ function default_skeleton_path(array|string $path = ''): string { - return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))); + $paths = Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path); + + if (defined('TESTBENCH_DUSK') && function_exists('Orchestra\Testbench\Dusk\default_skeleton_path')) { + return \Orchestra\Testbench\Dusk\default_skeleton_path($paths); // @phpstan-ignore function.notFound + } + + return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...$paths)); } /** From 86c212706eaa3df94075518a670f025b0fb3f43d Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:34:38 +0800 Subject: [PATCH 10/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Bootstrap/LoadConfiguration.php | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Bootstrap/LoadConfiguration.php b/src/Bootstrap/LoadConfiguration.php index f87c5759..f6d5c566 100644 --- a/src/Bootstrap/LoadConfiguration.php +++ b/src/Bootstrap/LoadConfiguration.php @@ -40,9 +40,7 @@ public function bootstrap(Application $app): void ]); } - if ($config->get('database.default') === 'sqlite' && ! file_exists($config->get('database.connections.sqlite.database'))) { - $config->set('database.default', 'testing'); - } + $this->configureDefaultDatabaseConnection($config); mb_internal_encoding('UTF-8'); } @@ -117,4 +115,30 @@ protected function extendsLoadedConfiguration(Collection $configurations): Colle { return $configurations; } + + /** + * Configure the default database connection. + * + * @param \Illuminate\Contracts\Config\Repository $config + * @return void + */ + protected function configureDefaultDatabaseConnection(RepositoryContract $config): void + { + if ($config->get('database.default') === 'sqlite' && ! file_exists($config->get('database.connections.sqlite.database'))) { + $config->set('database.default', 'testing'); + } + } + + /** + * Get the application configuration path. + * + * @param TLaravel $app + * @return string + */ + protected function getConfigurationPath(Application $app): string + { + return is_dir($app->basePath('config')) + ? $app->basePath('config') + : default_skeleton_path('config'); + } } From c5664478b7db5793a27aedaa3e654ab1088b9279 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:35:41 +0800 Subject: [PATCH 11/35] wip Signed-off-by: Mior Muhammad Zaki --- src/functions.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/functions.php b/src/functions.php index 8007add9..9c03385e 100644 --- a/src/functions.php +++ b/src/functions.php @@ -247,10 +247,6 @@ function default_skeleton_path(array|string $path = ''): string { $paths = Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path); - if (defined('TESTBENCH_DUSK') && function_exists('Orchestra\Testbench\Dusk\default_skeleton_path')) { - return \Orchestra\Testbench\Dusk\default_skeleton_path($paths); // @phpstan-ignore function.notFound - } - return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...$paths)); } From cc4f4de2e98f720bcfbc79a50e1a153a5ade920b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:40:12 +0800 Subject: [PATCH 12/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Bootstrap/LoadConfigurationWithWorkbench.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index fde3ee1b..a1a3a485 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -59,6 +59,7 @@ class_exists(User::class) => User::class, * @param string $key * @return string */ + #[\Override] protected function resolveConfigurationFile(string $path, string $key): string { return $this->usesWorkbenchConfigFile === true && is_file(workbench_path('config', "{$key}.php")) @@ -72,6 +73,7 @@ protected function resolveConfigurationFile(string $path, string $key): string * @param \Illuminate\Support\Collection $configurations * @return \Illuminate\Support\Collection */ + #[\Override] protected function extendsLoadedConfiguration(Collection $configurations): Collection { if ($this->usesWorkbenchConfigFile === false) { From 14217e4ab385b6c40c77fb30488f6dba3c270b82 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:40:43 +0800 Subject: [PATCH 13/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Bootstrap/LoadConfiguration.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Bootstrap/LoadConfiguration.php b/src/Bootstrap/LoadConfiguration.php index f6d5c566..6692af64 100644 --- a/src/Bootstrap/LoadConfiguration.php +++ b/src/Bootstrap/LoadConfiguration.php @@ -56,9 +56,7 @@ private function loadConfigurationFiles(Application $app, RepositoryContract $co { $this->extendsLoadedConfiguration( LazyCollection::make(function () use ($app) { - $path = is_dir($app->basePath('config')) - ? $app->basePath('config') - : default_skeleton_path('config'); + $path = $this->getConfigurationPath($app); if (\is_string($path)) { foreach (Finder::create()->files()->name('*.php')->in($path) as $file) { From 23e45c7829d5a6ce110adac71eba04f9bbbff2ea Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 13:48:37 +0800 Subject: [PATCH 14/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Bootstrap/LoadConfigurationWithWorkbench.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index a1a3a485..dc2d4494 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -52,13 +52,7 @@ class_exists(User::class) => User::class, } } - /** - * Resolve the configuration file. - * - * @param string $path - * @param string $key - * @return string - */ + /** {@inheritDoc} */ #[\Override] protected function resolveConfigurationFile(string $path, string $key): string { @@ -67,12 +61,7 @@ protected function resolveConfigurationFile(string $path, string $key): string : $path; } - /** - * Extend the loaded configuration. - * - * @param \Illuminate\Support\Collection $configurations - * @return \Illuminate\Support\Collection - */ + /** {@inheritDoc} */ #[\Override] protected function extendsLoadedConfiguration(Collection $configurations): Collection { From 6287ae9b45998e77b6eede733b60176a77ca3cea Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 14:13:31 +0800 Subject: [PATCH 15/35] wip Signed-off-by: Mior Muhammad Zaki --- src/functions.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/functions.php b/src/functions.php index 9c03385e..01bb9f99 100644 --- a/src/functions.php +++ b/src/functions.php @@ -245,9 +245,7 @@ function transform_relative_path(string $path, string $workingPath): string */ function default_skeleton_path(array|string $path = ''): string { - $paths = Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path); - - return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...$paths)); + return (string) realpath(join_paths(__DIR__, '..', 'laravel', ...Arr::wrap(\func_num_args() > 1 ? \func_get_args() : $path))); } /** From c97f6033bed10b57f0358c5287bc4ab66eb9a901 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 25 Nov 2024 15:59:59 +0800 Subject: [PATCH 16/35] wip Signed-off-by: Mior Muhammad Zaki --- .../LoadConfigurationWithWorkbench.php | 9 +---- src/Concerns/InteractsWithWorkbench.php | 13 ++++++- src/Workbench/Workbench.php | 37 ++++++++++++++----- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index dc2d4494..cd81d9d3 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -6,10 +6,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; -use Orchestra\Testbench\Foundation\Env; use Orchestra\Testbench\Workbench\Workbench; use Symfony\Component\Finder\Finder; -use Workbench\App\Models\User; use function Orchestra\Testbench\workbench_path; @@ -40,12 +38,7 @@ public function bootstrap(Application $app): void { parent::bootstrap($app); - /** @var class-string<\Illuminate\Foundation\Auth\User>|false $userModel */ - $userModel = match (true) { - Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), - class_exists(User::class) => User::class, - default => false, - }; + $userModel = Workbench::applicationUserModel(); if ($userModel !== false && is_a($userModel, Authenticatable::class, true)) { $app->make('config')->set('auth.providers.users.model', $userModel); diff --git a/src/Concerns/InteractsWithWorkbench.php b/src/Concerns/InteractsWithWorkbench.php index 8e353d53..e8208996 100644 --- a/src/Concerns/InteractsWithWorkbench.php +++ b/src/Concerns/InteractsWithWorkbench.php @@ -68,11 +68,20 @@ protected function getPackageBootstrappersUsingWorkbench($app): ?array */ protected function getPackageProvidersUsingWorkbench($app): ?array { - if (empty($providers = static::cachedConfigurationForWorkbench()?->getExtraAttributes()['providers'] ?? null)) { + $config = static::cachedConfigurationForWorkbench(); + + $hasAuthentication = $config?->getWorkbenchAttributes()['auth'] ?? false; + $providers = $config?->getExtraAttributes()['providers'] ?? []; + + if ($hasAuthentication && class_exists('Orchestra\Workbench\AuthServiceProvider')) { + array_push($providers, 'Orchestra\Workbench\AuthServiceProvider'); + } + + if (empty($providers)) { return null; } - return static::usesTestingConcern(WithWorkbench::class) + return static::usesTestingConcern(WithWorkbench::class) || ! static::usesTestingConcern() ? Arr::wrap($providers) : []; } diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 68989d40..3a6e07a4 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -7,13 +7,11 @@ use Illuminate\Contracts\Foundation\Application as ApplicationContract; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Routing\Router; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Orchestra\Testbench\Contracts\Config as ConfigContract; use Orchestra\Testbench\Foundation\Config; -use Orchestra\Workbench\AuthServiceProvider; -use Orchestra\Workbench\WorkbenchServiceProvider; +use Orchestra\Testbench\Foundation\Env; use ReflectionClass; use Symfony\Component\Finder\Finder; @@ -35,6 +33,13 @@ class Workbench */ protected static $cachedConfiguration; + /** + * The cached test case configuration. + * + * @var class-string<\Illuminate\Foundation\Auth\User>|false|null + */ + protected static $cachedUserModel = null; + /** * The cached core workbench bindings. * @@ -50,6 +55,7 @@ class Workbench * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Orchestra\Testbench\Contracts\Config $config + * @param array> $providers * @return void */ public static function start(ApplicationContract $app, ConfigContract $config, array $providers = []): void @@ -59,12 +65,7 @@ public static function start(ApplicationContract $app, ConfigContract $config, a } Collection::make($providers) - ->when( - Arr::get($config->getWorkbenchAttributes(), 'auth', false) === true, - static fn ($providers) => $providers->push(AuthServiceProvider::class) // @phpstan-ignore class.notFound - ) - ->filter() - ->filter(static fn ($provider) => class_exists($provider)) + ->filter(static fn ($provider) => ! empty($provider) && class_exists($provider)) ->each(static function ($provider) use ($app) { $app->register($provider); }); @@ -80,7 +81,8 @@ public static function start(ApplicationContract $app, ConfigContract $config, a public static function startWithProviders(ApplicationContract $app, ConfigContract $config): void { static::start($app, $config, [ - WorkbenchServiceProvider::class, // @phpstan-ignore class.notFound + 'Orchestra\Workbench\AuthServiceProvider', + 'Orchestra\Workbench\WorkbenchServiceProvider', ]); } @@ -292,6 +294,21 @@ public static function applicationExceptionHandler(): ?string return static::$cachedCoreBindings['handler']['exception']; } + /** + * Get application User Model + * + * @return class-string<\Illuminate\Foundation\Auth\User>|false + */ + public static function applicationUserModel(): string|false + { + return static::$cachedUserModel ??= match (true) { + Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), + class_exists('Workbench\App\Models\User') => 'Workbench\App\Models\User', + class_exists('App\Models\User') => 'App\Models\User', + default => false, + }; + } + /** * Flush the cached configuration. * From 74faf5df3c602890eed0487e207371c27b5da290 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 09:41:47 +0800 Subject: [PATCH 17/35] wip Signed-off-by: Mior Muhammad Zaki --- bin/configure-skeleton.php | 13 +++++++++++ bin/sync | 45 +++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/bin/configure-skeleton.php b/bin/configure-skeleton.php index 5ad854f7..d4f97222 100644 --- a/bin/configure-skeleton.php +++ b/bin/configure-skeleton.php @@ -1,5 +1,18 @@ hasParameterOption('--dev') || $input->hasParameterOption('--stable') === false) ? '9.x-dev' : '^9.0'; + +echo '> composer create-project "laravel/laravel:'.$version.'" skeleton --no-scripts --no-plugins --quiet --no-install'.PHP_EOL; + +Symfony\Component\Process\Process::fromShellCommandline( + 'composer create-project "laravel/laravel:'.$version.'" skeleton --no-scripts --no-plugins --quiet --no-install', $workingPath +)->mustRun(); + collect([ 'artisan', '.env.example', diff --git a/bin/sync b/bin/sync index 7457a6ec..3619884d 100755 --- a/bin/sync +++ b/bin/sync @@ -17,6 +17,12 @@ function line(string $code, int $tab = 0): string { return implode('', [PHP_EOL, str_repeat(' ', ($tab * 4)), $code]); } +/** + * ---------------------------------------------------------------------- + * Prepare skeleton folder + * ---------------------------------------------------------------------- + */ + $purgeSkeletonDirectories(); if ($files->isDirectory("{$workingPath}/laravel/migrations")) { @@ -29,14 +35,11 @@ $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/notifications") $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/queue"); $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/session"); -$files->copy("{$workingPath}/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php", "{$workingPath}/laravel/server.php"); -transform([ - "getcwd()" => "__DIR__.'/public'", -], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/server.php")); - -Symfony\Component\Process\Process::fromShellCommandline( - 'composer create-project "laravel/laravel:9.x-dev" skeleton --no-scripts --no-plugins --quiet --no-install', $workingPath -)->mustRun(); +/** + * ---------------------------------------------------------------------- + * Copy files from `laravel/laravel` + * ---------------------------------------------------------------------- + */ require Orchestra\Testbench\join_paths(__DIR__, 'configure-skeleton.php'); @@ -56,7 +59,26 @@ transform([ line("'driver' => env('SESSION_DRIVER', 'file'),", 1) => line("'driver' => env('SESSION_DRIVER', 'array'),", 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/config/session.php")); +/** + * ---------------------------------------------------------------------- + * Copy `server.php` file from `laravel/framework` + * ---------------------------------------------------------------------- + */ + +$files->copy("{$workingPath}/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php", "{$workingPath}/laravel/server.php"); +transform([ + "getcwd()" => "__DIR__.'/public'", +], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/laravel/server.php")); + + +/** + * ---------------------------------------------------------------------- + * Update `Orchestra\Testbench\Factories\UserFactory` + * ---------------------------------------------------------------------- + */ + $files->copy("{$workingPath}/skeleton/database/factories/UserFactory.php", "{$workingPath}/src/Factories/UserFactory.php"); + transform([ 'namespace Database\Factories;' => 'namespace Orchestra\Testbench\Factories;', 'use Illuminate\Database\Eloquent\Factories\Factory;' => 'use Illuminate\Database\Eloquent\Factories\Factory;'.line('use Illuminate\Foundation\Auth\User;'), @@ -79,4 +101,11 @@ transform([ }'.PHP_EOL, 1), ], fn ($changes) => $files->replaceInFile(array_keys($changes), array_values($changes), "{$workingPath}/src/Factories/UserFactory.php")); + +/** + * ---------------------------------------------------------------------- + * Cleanup + * ---------------------------------------------------------------------- + */ + $purgeSkeletonDirectories(); From d9a43ee85292569d45eb46732b00e8a65c4c0fdc Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 11:37:32 +0800 Subject: [PATCH 18/35] wip Signed-off-by: Mior Muhammad Zaki --- .../LoadConfigurationWithWorkbench.php | 16 +++--- src/Workbench/Workbench.php | 54 +++++++++++++++---- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index cd81d9d3..d22b7da2 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -9,6 +9,7 @@ use Orchestra\Testbench\Workbench\Workbench; use Symfony\Component\Finder\Finder; +use function Orchestra\Testbench\join_paths; use function Orchestra\Testbench\workbench_path; /** @@ -40,7 +41,11 @@ public function bootstrap(Application $app): void $userModel = Workbench::applicationUserModel(); - if ($userModel !== false && is_a($userModel, Authenticatable::class, true)) { + if (\is_null($userModel) && file_exists($app->basePath(join_paths('Models', 'User.php')))) { + $userModel = 'App\Models\User'; + } + + if (! \is_null($userModel) && is_a($userModel, Authenticatable::class, true)) { $app->make('config')->set('auth.providers.users.model', $userModel); } } @@ -70,11 +75,10 @@ protected function extendsLoadedConfiguration(Collection $configurations): Colle yield $directory.basename($file->getRealPath(), '.php') => $file->getRealPath(); } - })->reject(static function ($path, $key) use ($configurations) { - return $configurations->has($key); - })->each(static function ($path, $key) use ($configurations) { - $configurations->put($key, $path); - }); + })->reject(static fn ($path, $key) => $configurations->has($key)) + ->each(static function ($path, $key) use ($configurations) { + $configurations->put($key, $path); + }); return $configurations; } diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 3a6e07a4..99ae6404 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -33,6 +33,13 @@ class Workbench */ protected static $cachedConfiguration; + /** + * Cached namespace by path. + * + * @var array + */ + protected static array $cachedNamespaces = []; + /** * The cached test case configuration. * @@ -210,7 +217,7 @@ public static function discoverCommandsRoutes(ApplicationContract $app): void return; } - $namespace = 'Workbench\App'; + $namespace = rtrim(static::detectNamespace('app'), '\\'); foreach ((new Finder)->in([workbench_path('app', 'Console', 'Commands')])->files() as $command) { $command = $namespace.str_replace( @@ -255,7 +262,7 @@ public static function applicationConsoleKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['console'])) { static::$cachedCoreBindings['kernel']['console'] = file_exists(workbench_path('app', 'Console', 'Kernel.php')) - ? 'Workbench\App\Console\Kernel' + ? \sprintf('%sConsole\Kernel', static::detectNamespace('app')) : null; } @@ -271,7 +278,7 @@ public static function applicationHttpKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['http'])) { static::$cachedCoreBindings['kernel']['http'] = file_exists(workbench_path('app', 'Http', 'Kernel.php')) - ? 'Workbench\App\Http\Kernel' + ? \sprintf('%sHttp\Kernel', static::detectNamespace('app')) : null; } @@ -287,7 +294,7 @@ public static function applicationExceptionHandler(): ?string { if (! isset(static::$cachedCoreBindings['handler']['exception'])) { static::$cachedCoreBindings['handler']['exception'] = file_exists(workbench_path('app', 'Exceptions', 'Handler.php')) - ? 'Workbench\App\Exceptions\Handler' + ? \sprintf('%sExceptions\Handler', static::detectNamespace('app')) : null; } @@ -297,16 +304,45 @@ public static function applicationExceptionHandler(): ?string /** * Get application User Model * - * @return class-string<\Illuminate\Foundation\Auth\User>|false + * @return class-string<\Illuminate\Foundation\Auth\User>|null */ - public static function applicationUserModel(): string|false + public static function applicationUserModel(): ?string { - return static::$cachedUserModel ??= match (true) { + static::$cachedUserModel ??= match (true) { Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), - class_exists('Workbench\App\Models\User') => 'Workbench\App\Models\User', - class_exists('App\Models\User') => 'App\Models\User', + file_exists(workbench_path('app', 'Models', 'User.php')) => \sprintf('%sModels\User', static::detectNamespace('app')), default => false, }; + + return static::$cachedUserModel != false ? static::$cachedUserModel : null; + } + + /** + * Detect namespace by type. + */ + public static function detectNamespace(string $type): ?string + { + $type = trim($type, '/'); + $path = implode('/', ['workbench', $type]); + + if (! isset(static::$cachedNamespaces[$type])) { + static::$cachedNamespaces[$type] = null; + + /** @var array{'autoload-dev': array{'psr-4': array|string>}} $composer */ + $composer = json_decode((string) file_get_contents(package_path('composer.json')), true); + + $collection = $composer['autoload-dev']['psr-4'] ?? []; + + foreach ((array) $collection as $namespace => $paths) { + foreach ((array) $paths as $pathChoice) { + if (trim($pathChoice, '/') === $path) { + static::$cachedNamespaces[$type] = $namespace; + } + } + } + } + + return static::$cachedNamespaces[$type]; } /** From 33f5b2592e17fb04541f6be272b9f1ff731090e5 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 11:59:51 +0800 Subject: [PATCH 19/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Workbench/Workbench.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 99ae6404..c0d354a7 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -217,7 +217,7 @@ public static function discoverCommandsRoutes(ApplicationContract $app): void return; } - $namespace = rtrim(static::detectNamespace('app'), '\\'); + $namespace = rtrim((static::detectNamespace('app') ?? 'Workbench\App\\'), '\\'); foreach ((new Finder)->in([workbench_path('app', 'Console', 'Commands')])->files() as $command) { $command = $namespace.str_replace( @@ -342,7 +342,13 @@ public static function detectNamespace(string $type): ?string } } - return static::$cachedNamespaces[$type]; + $defaults = [ + 'app' => 'Workbench\App\\', + 'database/factories' => 'Workbench\Database\Factories\\', + 'database/seeders' => 'Workbench\Database\Seeders\\', + ]; + + return static::$cachedNamespaces[$type] ?? $defaults[$type] ?? null; } /** From fd3016afc05f3ca0bcbeda223676e93e33cf7f4c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 12:08:11 +0800 Subject: [PATCH 20/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Workbench/Workbench.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index c0d354a7..8a5528aa 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -308,11 +308,13 @@ public static function applicationExceptionHandler(): ?string */ public static function applicationUserModel(): ?string { - static::$cachedUserModel ??= match (true) { - Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), - file_exists(workbench_path('app', 'Models', 'User.php')) => \sprintf('%sModels\User', static::detectNamespace('app')), - default => false, - }; + if (! isset(static::$cachedUserModel)) { + static::$cachedUserModel = match (true) { + Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), + file_exists(workbench_path('app', 'Models', 'User.php')) => \sprintf('%sModels\User', static::detectNamespace('app')), + default => false, + }; + } return static::$cachedUserModel != false ? static::$cachedUserModel : null; } @@ -323,7 +325,6 @@ public static function applicationUserModel(): ?string public static function detectNamespace(string $type): ?string { $type = trim($type, '/'); - $path = implode('/', ['workbench', $type]); if (! isset(static::$cachedNamespaces[$type])) { static::$cachedNamespaces[$type] = null; @@ -332,6 +333,8 @@ public static function detectNamespace(string $type): ?string $composer = json_decode((string) file_get_contents(package_path('composer.json')), true); $collection = $composer['autoload-dev']['psr-4'] ?? []; + + $path = implode('/', ['workbench', $type]); foreach ((array) $collection as $namespace => $paths) { foreach ((array) $paths as $pathChoice) { From 5b12c9f12bb1b1910f57cc6f226c31d2f152f0c7 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 12:16:44 +0800 Subject: [PATCH 21/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Bootstrap/ConfigureRay.php | 2 +- src/Bootstrap/LoadConfiguration.php | 2 +- .../LoadConfigurationWithWorkbench.php | 2 +- src/Bootstrap/LoadEnvironmentVariables.php | 2 +- src/Console/Commander.php | 2 +- src/Console/Kernel.php | 2 +- src/Foundation/Application.php | 6 +++--- src/Foundation/Config.php | 5 ++--- src/Foundation/Console/TestCommand.php | 2 +- src/Workbench/Workbench.php | 18 ++++++++---------- 10 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Bootstrap/ConfigureRay.php b/src/Bootstrap/ConfigureRay.php index d3edce07..c9b04b04 100644 --- a/src/Bootstrap/ConfigureRay.php +++ b/src/Bootstrap/ConfigureRay.php @@ -29,7 +29,7 @@ public function bootstrap(Application $app): void /** @var \Illuminate\Contracts\Config\Repository $config */ $config = $app->make('config'); - if ($config->get('database.default') === 'sqlite' && ! file_exists($config->get('database.connections.sqlite.database'))) { + if ($config->get('database.default') === 'sqlite' && ! is_file($config->get('database.connections.sqlite.database'))) { $settings->send_queries_to_ray = false; /** @phpstan-ignore property.notFound */ $settings->send_duplicate_queries_to_ray = false; /** @phpstan-ignore property.notFound */ $settings->send_slow_queries_to_ray = false; /** @phpstan-ignore property.notFound */ diff --git a/src/Bootstrap/LoadConfiguration.php b/src/Bootstrap/LoadConfiguration.php index 6692af64..19331370 100644 --- a/src/Bootstrap/LoadConfiguration.php +++ b/src/Bootstrap/LoadConfiguration.php @@ -122,7 +122,7 @@ protected function extendsLoadedConfiguration(Collection $configurations): Colle */ protected function configureDefaultDatabaseConnection(RepositoryContract $config): void { - if ($config->get('database.default') === 'sqlite' && ! file_exists($config->get('database.connections.sqlite.database'))) { + if ($config->get('database.default') === 'sqlite' && ! is_file($config->get('database.connections.sqlite.database'))) { $config->set('database.default', 'testing'); } } diff --git a/src/Bootstrap/LoadConfigurationWithWorkbench.php b/src/Bootstrap/LoadConfigurationWithWorkbench.php index d22b7da2..4e202cc3 100644 --- a/src/Bootstrap/LoadConfigurationWithWorkbench.php +++ b/src/Bootstrap/LoadConfigurationWithWorkbench.php @@ -41,7 +41,7 @@ public function bootstrap(Application $app): void $userModel = Workbench::applicationUserModel(); - if (\is_null($userModel) && file_exists($app->basePath(join_paths('Models', 'User.php')))) { + if (\is_null($userModel) && is_file($app->basePath(join_paths('Models', 'User.php')))) { $userModel = 'App\Models\User'; } diff --git a/src/Bootstrap/LoadEnvironmentVariables.php b/src/Bootstrap/LoadEnvironmentVariables.php index 458cdbf1..959b2f71 100644 --- a/src/Bootstrap/LoadEnvironmentVariables.php +++ b/src/Bootstrap/LoadEnvironmentVariables.php @@ -17,7 +17,7 @@ final class LoadEnvironmentVariables extends \Illuminate\Foundation\Bootstrap\Lo protected function createDotenv($app) { /** @phpstan-ignore method.notFound, method.notFound */ - if (! file_exists(join_paths($app->environmentPath(), $app->environmentFile()))) { + if (! is_file(join_paths($app->environmentPath(), $app->environmentFile()))) { return Dotenv::create( Env::getRepository(), (string) realpath(join_paths(__DIR__, 'stubs')), '.env.testbench' ); diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 58a3636f..275ae323 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -136,7 +136,7 @@ public function laravel() if (! $this->app instanceof LaravelApplication) { $APP_BASE_PATH = $this->getBasePath(); - $hasEnvironmentFile = fn () => file_exists(join_paths($APP_BASE_PATH, '.env')); + $hasEnvironmentFile = fn () => is_file(join_paths($APP_BASE_PATH, '.env')); tap(static::$testbench::createVendorSymlink($APP_BASE_PATH, join_paths($this->workingPath, 'vendor')), function ($app) use ($hasEnvironmentFile) { $filesystem = new Filesystem; diff --git a/src/Console/Kernel.php b/src/Console/Kernel.php index 45033725..8625330b 100644 --- a/src/Console/Kernel.php +++ b/src/Console/Kernel.php @@ -26,7 +26,7 @@ final class Kernel extends ConsoleKernel */ protected function commands() { - if (file_exists($console = base_path(join_paths('routes', 'console.php')))) { + if (is_file($console = base_path(join_paths('routes', 'console.php')))) { require $console; } } diff --git a/src/Foundation/Application.php b/src/Foundation/Application.php index 303dfdbd..f0fe3583 100644 --- a/src/Foundation/Application.php +++ b/src/Foundation/Application.php @@ -112,7 +112,7 @@ public static function makeFromConfig(ConfigContract $config, ?callable $resolvi $basePath = $config['laravel'] ?? static::applicationBasePath(); return (new static($config['laravel'], $resolvingCallback))->configure(array_merge($options, [ - 'load_environment_variables' => file_exists("{$basePath}/.env"), + 'load_environment_variables' => is_file("{$basePath}/.env"), 'extra' => $config->getExtraAttributes(), ])); } @@ -315,7 +315,7 @@ protected function resolveApplicationConsoleKernel($app) { $kernel = Workbench::applicationConsoleKernel() ?? 'Orchestra\Testbench\Console\Kernel'; - if (file_exists($app->basePath('app/Console/Kernel.php')) && class_exists('App\Console\Kernel')) { + if (is_file($app->basePath('app/Console/Kernel.php')) && class_exists('App\Console\Kernel')) { $kernel = 'App\Console\Kernel'; } @@ -332,7 +332,7 @@ protected function resolveApplicationHttpKernel($app) { $kernel = Workbench::applicationHttpKernel() ?? 'Orchestra\Testbench\Http\Kernel'; - if (file_exists($app->basePath('app/Http/Kernel.php')) && class_exists('App\Http\Kernel')) { + if (is_file($app->basePath('app/Http/Kernel.php')) && class_exists('App\Http\Kernel')) { $kernel = 'App\Http\Kernel'; } diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index a8aee719..239a35c8 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -202,9 +202,8 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te yield $filename; yield "{$filename}.example"; yield "{$filename}.dist"; - })->filter(static function ($file) use ($workingPath) { - return file_exists(join_paths($workingPath, $file)); - })->first(); + })->filter(static fn ($file) => is_file(join_paths($workingPath, $file))) + ->first(); if (! \is_null($filename)) { /** diff --git a/src/Foundation/Console/TestCommand.php b/src/Foundation/Console/TestCommand.php index 58ec6d15..2d6d2daf 100644 --- a/src/Foundation/Console/TestCommand.php +++ b/src/Foundation/Console/TestCommand.php @@ -70,7 +70,7 @@ public function phpUnitConfigurationFile() package_path($configurationFile), package_path("{$configurationFile}.dist"), ])->transform(static fn ($path) => DIRECTORY_SEPARATOR.$path) - ->filter(static fn ($path) => file_exists($path)) + ->filter(static fn ($path) => is_file($path)) ->first() ?? './'; } diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 8a5528aa..1a9f5a5b 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -67,9 +67,7 @@ class Workbench */ public static function start(ApplicationContract $app, ConfigContract $config, array $providers = []): void { - if (! $app->bound(ConfigContract::class)) { - $app->singleton(ConfigContract::class, static fn () => $config); - } + $app->singleton(ConfigContract::class, static fn () => $config); Collection::make($providers) ->filter(static fn ($provider) => ! empty($provider) && class_exists($provider)) @@ -109,7 +107,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ tap($app->make('router'), static function (Router $router) use ($discoversConfig) { foreach (['web', 'api'] as $group) { if (($discoversConfig[$group] ?? false) === true) { - if (file_exists($route = workbench_path('routes', "{$group}.php"))) { + if (is_file($route = workbench_path('routes', "{$group}.php"))) { $router->middleware($group)->group($route); } } @@ -209,7 +207,7 @@ public static function discoverRoutes(ApplicationContract $app, ConfigContract $ */ public static function discoverCommandsRoutes(ApplicationContract $app): void { - if (file_exists($console = workbench_path('routes', 'console.php'))) { + if (is_file($console = workbench_path('routes', 'console.php'))) { require $console; } @@ -261,7 +259,7 @@ public static function configuration(): ConfigContract public static function applicationConsoleKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['console'])) { - static::$cachedCoreBindings['kernel']['console'] = file_exists(workbench_path('app', 'Console', 'Kernel.php')) + static::$cachedCoreBindings['kernel']['console'] = is_file(workbench_path('app', 'Console', 'Kernel.php')) ? \sprintf('%sConsole\Kernel', static::detectNamespace('app')) : null; } @@ -277,7 +275,7 @@ public static function applicationConsoleKernel(): ?string public static function applicationHttpKernel(): ?string { if (! isset(static::$cachedCoreBindings['kernel']['http'])) { - static::$cachedCoreBindings['kernel']['http'] = file_exists(workbench_path('app', 'Http', 'Kernel.php')) + static::$cachedCoreBindings['kernel']['http'] = is_file(workbench_path('app', 'Http', 'Kernel.php')) ? \sprintf('%sHttp\Kernel', static::detectNamespace('app')) : null; } @@ -293,7 +291,7 @@ public static function applicationHttpKernel(): ?string public static function applicationExceptionHandler(): ?string { if (! isset(static::$cachedCoreBindings['handler']['exception'])) { - static::$cachedCoreBindings['handler']['exception'] = file_exists(workbench_path('app', 'Exceptions', 'Handler.php')) + static::$cachedCoreBindings['handler']['exception'] = is_file(workbench_path('app', 'Exceptions', 'Handler.php')) ? \sprintf('%sExceptions\Handler', static::detectNamespace('app')) : null; } @@ -311,7 +309,7 @@ public static function applicationUserModel(): ?string if (! isset(static::$cachedUserModel)) { static::$cachedUserModel = match (true) { Env::has('AUTH_MODEL') => Env::get('AUTH_MODEL'), - file_exists(workbench_path('app', 'Models', 'User.php')) => \sprintf('%sModels\User', static::detectNamespace('app')), + is_file(workbench_path('app', 'Models', 'User.php')) => \sprintf('%sModels\User', static::detectNamespace('app')), default => false, }; } @@ -333,7 +331,7 @@ public static function detectNamespace(string $type): ?string $composer = json_decode((string) file_get_contents(package_path('composer.json')), true); $collection = $composer['autoload-dev']['psr-4'] ?? []; - + $path = implode('/', ['workbench', $type]); foreach ((array) $collection as $namespace => $paths) { From a809554345f5eb76713badbe90f3e80403442669 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 21:01:40 +0800 Subject: [PATCH 22/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Concerns/InteractsWithTestCase.php | 20 ++++++++------------ src/Foundation/PackageManifest.php | 6 ++++-- src/PHPUnit/AttributeParser.php | 2 +- src/Workbench/Workbench.php | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Concerns/InteractsWithTestCase.php b/src/Concerns/InteractsWithTestCase.php index 78996e0d..cf5d1226 100644 --- a/src/Concerns/InteractsWithTestCase.php +++ b/src/Concerns/InteractsWithTestCase.php @@ -131,9 +131,8 @@ protected function setUpTheTestEnvironmentUsingTestCase(): void $this->resolvePhpUnitAttributes() ->flatten() - ->filter(static function ($instance) { - return $instance instanceof BeforeEachContract; - })->map(function ($instance) use ($app) { + ->filter(static fn ($instance) => $instance instanceof BeforeEachContract) + ->map(static function ($instance) use ($app) { $instance->beforeEach($app); }); } @@ -152,9 +151,8 @@ protected function tearDownTheTestEnvironmentUsingTestCase(): void $this->resolvePhpUnitAttributes() ->flatten() - ->filter(static function ($instance) { - return $instance instanceof AfterEachContract; - })->map(static function ($instance) use ($app) { + ->filter(static fn ($instance) => $instance instanceof AfterEachContract) + ->map(static function ($instance) use ($app) { $instance->afterEach($app); }); } @@ -170,9 +168,8 @@ public static function setUpBeforeClassUsingTestCase(): void { static::resolvePhpUnitAttributesForMethod(static::class) ->flatten() - ->filter(static function ($instance) { - return $instance instanceof BeforeAllContract; - })->map(static function ($instance) { + ->filter(static fn ($instance) => $instance instanceof BeforeAllContract) + ->map(static function ($instance) { $instance->beforeAll(); }); } @@ -188,9 +185,8 @@ public static function tearDownAfterClassUsingTestCase(): void { static::resolvePhpUnitAttributesForMethod(static::class) ->flatten() - ->filter(static function ($instance) { - return $instance instanceof AfterAllContract; - })->map(static function ($instance) { + ->filter(static fn ($instance) => $instance instanceof AfterAllContract) + ->map(static function ($instance) { $instance->afterAll(); }); diff --git a/src/Foundation/PackageManifest.php b/src/Foundation/PackageManifest.php index 8de245b9..5061dff1 100644 --- a/src/Foundation/PackageManifest.php +++ b/src/Foundation/PackageManifest.php @@ -94,9 +94,11 @@ protected function getManifest() $ignoreAll = \in_array('*', $ignore); + $requires = $this->requiredPackages; + return Collection::make(parent::getManifest()) - ->reject(function ($configuration, $package) use ($ignore, $ignoreAll) { - return ($ignoreAll && ! \in_array($package, $this->requiredPackages)) + ->reject(static function ($configuration, $package) use ($ignore, $ignoreAll, $requires) { + return ($ignoreAll && ! \in_array($package, $requires)) || \in_array($package, $ignore); })->map(static function ($configuration, $key) { foreach ($configuration['providers'] ?? [] as $provider) { diff --git a/src/PHPUnit/AttributeParser.php b/src/PHPUnit/AttributeParser.php index a1ef79e7..3f427c54 100644 --- a/src/PHPUnit/AttributeParser.php +++ b/src/PHPUnit/AttributeParser.php @@ -110,7 +110,7 @@ public static function validAttribute($class): bool */ protected static function resolveAttribute(ReflectionAttribute $attribute): array { - return rescue(function () use ($attribute) { + return rescue(static function () use ($attribute) { /** @var TTestingFeature|null $instance */ $instance = isset(class_implements($attribute->getName())[ResolvableContract::class]) ? transform($attribute->newInstance(), static function ($instance) { diff --git a/src/Workbench/Workbench.php b/src/Workbench/Workbench.php index 1a9f5a5b..61eb4395 100644 --- a/src/Workbench/Workbench.php +++ b/src/Workbench/Workbench.php @@ -228,7 +228,7 @@ public static function discoverCommandsRoutes(ApplicationContract $app): void is_subclass_of($command, Command::class) && ! (new ReflectionClass($command))->isAbstract() ) { - Artisan::starting(function ($artisan) use ($command) { + Artisan::starting(static function ($artisan) use ($command) { $artisan->resolve($command); }); } From 2ab1101f7771068088d9f6aaa4c3e20cf2c69b48 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 21:04:19 +0800 Subject: [PATCH 23/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/PackageManifest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Foundation/PackageManifest.php b/src/Foundation/PackageManifest.php index 5061dff1..580d2931 100644 --- a/src/Foundation/PackageManifest.php +++ b/src/Foundation/PackageManifest.php @@ -97,10 +97,8 @@ protected function getManifest() $requires = $this->requiredPackages; return Collection::make(parent::getManifest()) - ->reject(static function ($configuration, $package) use ($ignore, $ignoreAll, $requires) { - return ($ignoreAll && ! \in_array($package, $requires)) - || \in_array($package, $ignore); - })->map(static function ($configuration, $key) { + ->reject(static fn ($configuration, $package) => ($ignoreAll && ! \in_array($package, $requires)) || \in_array($package, $ignore)) + ->map(static function ($configuration, $package) { foreach ($configuration['providers'] ?? [] as $provider) { if (! class_exists($provider)) { return null; From 40b05421b54df0d35ee5d57f931155f83155d1b0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Nov 2024 21:21:09 +0800 Subject: [PATCH 24/35] Prepare 7.49.0 release note Signed-off-by: Mior Muhammad Zaki --- CHANGELOG-7.x.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG-7.x.md b/CHANGELOG-7.x.md index 7fae1ef2..f17f64a4 100644 --- a/CHANGELOG-7.x.md +++ b/CHANGELOG-7.x.md @@ -2,6 +2,22 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. +## 7.49.0 (Unreleased) + +### Added + +* Added ability to detect Workbench namespace via `Orchestra\Testbench\Workbench\Workbench::detectNamespace()` method. +* Added ability to detect the default user model via `Orchestra\Testbench\Workbench\Workbench::applicationUserModel()` method. +* Added support for authentication routes within Workbench by configurating `workbench.auth` config to `true`. + +### Changes + +* Testbench Dusk integration improvements: + - Refactor `Orchestra\Testbench\Bootstrap\LoadConfiguration` and `Orchestra\Testbench\Bootstrap\LoadConfigurationWithWorkbench` to allow being extended by Testbench Dusk. + - Refactor `Orchestra\Testbench\Console\Commander`. +* Add multiple environment variables to Laravel 9 skeleton's configuration files based on changes made for Laravel 11. +* Add `$tty` parameter to `Orchestra\Testbench\remote()` function. + ## 7.48.0 Released: 2024-11-18 From 1218dd86212d3da0600372d0fe0f3512753aee52 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 27 Nov 2024 10:51:46 +0800 Subject: [PATCH 25/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Config.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index 239a35c8..3476af86 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -213,9 +213,15 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te */ $config = Yaml::parseFile(join_paths($workingPath, $filename)); - $config['laravel'] = transform( - Arr::get($config, 'laravel'), static fn ($path) => transform_relative_path($path, $workingPath) - ); + $config['laravel'] = transform(Arr::get($config, 'laravel'), static function ($path) { + $laravel = match ($path) { + '@testbench' => \Orchestra\Testbench\default_skeleton_path(), + '@testbench-dusk' => \Orchestra\Testbench\Dusk\default_skeleton_path(), + default => $path, + }; + + return transform_relative_path($laravel, $workingPath); + }); if (isset($config['env']) && \is_array($config['env']) && Arr::isAssoc($config['env'])) { $config['env'] = parse_environment_variables($config['env']); From 1e93d0461b92afd407e0ab06e60284d855128035 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 27 Nov 2024 10:52:30 +0800 Subject: [PATCH 26/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index 3476af86..84e29c1b 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -213,7 +213,7 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te */ $config = Yaml::parseFile(join_paths($workingPath, $filename)); - $config['laravel'] = transform(Arr::get($config, 'laravel'), static function ($path) { + $config['laravel'] = transform(Arr::get($config, 'laravel'), static function ($path) use ($workingPath) { $laravel = match ($path) { '@testbench' => \Orchestra\Testbench\default_skeleton_path(), '@testbench-dusk' => \Orchestra\Testbench\Dusk\default_skeleton_path(), From d159fa5f960e909fde63f1df37f2711e5defa28d Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 27 Nov 2024 15:08:30 +0800 Subject: [PATCH 27/35] wip Signed-off-by: Mior Muhammad Zaki --- bin/sync | 2 ++ laravel/bootstrap/.testbench-default-skeleton | 1 + laravel/bootstrap/app.php | 7 ++++--- src/Console/Commander.php | 2 ++ src/Foundation/Config.php | 6 ++++-- .../Console/Concerns/CopyTestbenchFiles.php | 20 +++++++++---------- .../Console/PurgeSkeletonCommand.php | 2 +- .../Console/TestFallbackCommand.php | 2 +- 8 files changed, 25 insertions(+), 17 deletions(-) create mode 100644 laravel/bootstrap/.testbench-default-skeleton diff --git a/bin/sync b/bin/sync index 3619884d..b432359c 100755 --- a/bin/sync +++ b/bin/sync @@ -35,6 +35,8 @@ $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/notifications") $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/queue"); $files->ensureDirectoryExists("{$workingPath}/laravel/migrations/session"); +$files->put("{$workingPath}/laravel/bootstrap/.testbench-default-skeleton", '@testbench'.PHP_EOL); + /** * ---------------------------------------------------------------------- * Copy files from `laravel/laravel` diff --git a/laravel/bootstrap/.testbench-default-skeleton b/laravel/bootstrap/.testbench-default-skeleton new file mode 100644 index 00000000..81632dec --- /dev/null +++ b/laravel/bootstrap/.testbench-default-skeleton @@ -0,0 +1 @@ +@testbench diff --git a/laravel/bootstrap/app.php b/laravel/bootstrap/app.php index 309c9647..602b895e 100644 --- a/laravel/bootstrap/app.php +++ b/laravel/bootstrap/app.php @@ -15,12 +15,13 @@ */ $createApp = static function (string $workingPath) { $config = Config::loadFromYaml( - defined('TESTBENCH_WORKING_PATH') ? TESTBENCH_WORKING_PATH : $workingPath + workingPath: defined('TESTBENCH_WORKING_PATH') ? TESTBENCH_WORKING_PATH : $workingPath, + filename: defined('TESTBENCH_WORKING_PATH') ? 'testbench.yaml' : join_paths($workingPath, 'bootstrap', 'cache', 'testbench.yaml') ); $hasEnvironmentFile = ! is_null($config['laravel']) - ? file_exists(join_paths($config['laravel'], '.env')) - : file_exists(join_paths($workingPath, '.env')); + ? is_file(join_paths($config['laravel'], '.env')) + : is_file(join_paths($workingPath, '.env')); return Application::create( basePath: $config['laravel'], diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 275ae323..75ba33ca 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -92,6 +92,8 @@ public function __construct($config, string $workingPath) { $this->config = $config instanceof Config ? $config : new Config($config); $this->workingPath = $workingPath; + + $_ENV['TESTBENCH_ENVIRONMENT_FILE_USING'] = $this->environmentFile; } /** diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index 84e29c1b..59dc127e 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -202,7 +202,9 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te yield $filename; yield "{$filename}.example"; yield "{$filename}.dist"; - })->filter(static fn ($file) => is_file(join_paths($workingPath, $file))) + })->map(static function ($file) use ($workingPath) { + return str_contains($file, DIRECTORY_SEPARATOR) ? $file : join_paths($workingPath, $file); + })->filter(static fn ($file) => is_file($file)) ->first(); if (! \is_null($filename)) { @@ -216,7 +218,7 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te $config['laravel'] = transform(Arr::get($config, 'laravel'), static function ($path) use ($workingPath) { $laravel = match ($path) { '@testbench' => \Orchestra\Testbench\default_skeleton_path(), - '@testbench-dusk' => \Orchestra\Testbench\Dusk\default_skeleton_path(), + '@testbench-dusk' => \Orchestra\Testbench\Dusk\default_skeleton_path(), // @phpstan-ignore function.notFound default => $path, }; diff --git a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php index c69b58f1..d2c7c64b 100644 --- a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php +++ b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php @@ -27,16 +27,16 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ yield 'testbench.yaml.example'; yield 'testbench.yaml.dist'; })->map(static fn ($file) => join_paths($workingPath, $file)) - ->filter(static fn ($file) => $filesystem->exists($file)) + ->filter(static fn ($file) => $filesystem->isFile($file)) ->first(); - $testbenchFile = $app->basePath('testbench.yaml'); + $testbenchFile = $app->basePath(join_paths('bootstrap', 'cache', 'testbench.yaml')); - if ($filesystem->exists($testbenchFile)) { + if ($filesystem->isFile($testbenchFile)) { $filesystem->copy($testbenchFile, "{$testbenchFile}.backup"); $this->beforeTerminating(static function () use ($filesystem, $testbenchFile) { - if ($filesystem->exists("{$testbenchFile}.backup")) { + if ($filesystem->isFile("{$testbenchFile}.backup")) { $filesystem->move("{$testbenchFile}.backup", $testbenchFile); } }); @@ -46,7 +46,7 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ $filesystem->copy($configurationFile, $testbenchFile); $this->beforeTerminating(static function () use ($filesystem, $testbenchFile) { - if ($filesystem->exists($testbenchFile)) { + if ($filesystem->isFile($testbenchFile)) { $filesystem->delete($testbenchFile); } }); @@ -71,17 +71,17 @@ protected function copyTestbenchDotEnvFile(Application $app, Filesystem $filesys yield "{$this->environmentFile}.example"; yield "{$this->environmentFile}.dist"; })->map(static fn ($file) => join_paths($workingPath, $file)) - ->filter(static fn ($file) => $filesystem->exists($file)) + ->filter(static fn ($file) => $filesystem->isFile($file)) ->first(); - if (\is_null($configurationFile) && $filesystem->exists($app->basePath('.env.example'))) { + if (\is_null($configurationFile) && $filesystem->isFile($app->basePath('.env.example'))) { $configurationFile = $app->basePath('.env.example'); } $environmentFile = $app->basePath('.env'); - $environmentFileBackup = "{$this->environmentFile}.backup"; + $environmentFileBackup = $app->basePath("{$this->environmentFile}.backup"); - if ($filesystem->exists($environmentFile)) { + if ($filesystem->isFile($environmentFile)) { $filesystem->copy($environmentFile, $environmentFileBackup); $this->beforeTerminating(static function () use ($filesystem, $environmentFile, $environmentFileBackup) { @@ -89,7 +89,7 @@ protected function copyTestbenchDotEnvFile(Application $app, Filesystem $filesys }); } - if (! \is_null($configurationFile) && ! $filesystem->exists($environmentFile)) { + if (! \is_null($configurationFile) && ! $filesystem->isFile($environmentFile)) { $filesystem->copy($configurationFile, $environmentFile); $this->beforeTerminating(static function () use ($filesystem, $environmentFile) { diff --git a/src/Foundation/Console/PurgeSkeletonCommand.php b/src/Foundation/Console/PurgeSkeletonCommand.php index 19984f2f..427e5dc9 100644 --- a/src/Foundation/Console/PurgeSkeletonCommand.php +++ b/src/Foundation/Console/PurgeSkeletonCommand.php @@ -47,7 +47,7 @@ public function handle(Filesystem $filesystem, ConfigContract $config) ))->handle( Collection::make([ '.env', - 'testbench.yaml', + join_paths('bootstrap', 'cache', 'testbench.yaml'), ])->map(fn ($file) => $this->laravel->basePath($file)) ); diff --git a/src/Foundation/Console/TestFallbackCommand.php b/src/Foundation/Console/TestFallbackCommand.php index 1d40465d..2db10d6a 100644 --- a/src/Foundation/Console/TestFallbackCommand.php +++ b/src/Foundation/Console/TestFallbackCommand.php @@ -104,7 +104,7 @@ protected function findComposer() { $composerPath = package_path('composer.phar'); - if (file_exists($composerPath)) { + if (is_file($composerPath)) { return implode(' ', [php_binary(true), $composerPath]); } From 764c966c8fc6ca5a9d062723bc6995eb39c21544 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 27 Nov 2024 15:10:59 +0800 Subject: [PATCH 28/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Foundation/Config.php b/src/Foundation/Config.php index 59dc127e..f9c4da91 100644 --- a/src/Foundation/Config.php +++ b/src/Foundation/Config.php @@ -213,7 +213,7 @@ public static function loadFromYaml(string $workingPath, ?string $filename = 'te * * @phpstan-var TOptionalConfig $config */ - $config = Yaml::parseFile(join_paths($workingPath, $filename)); + $config = Yaml::parseFile($filename); $config['laravel'] = transform(Arr::get($config, 'laravel'), static function ($path) use ($workingPath) { $laravel = match ($path) { From 676ab12781aead532f6d2e6d020f56b10dcc141b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 27 Nov 2024 15:12:12 +0800 Subject: [PATCH 29/35] wip Signed-off-by: Mior Muhammad Zaki --- .github/workflows/audits.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/audits.yaml b/.github/workflows/audits.yaml index 77a1961c..a59ff8f8 100644 --- a/.github/workflows/audits.yaml +++ b/.github/workflows/audits.yaml @@ -18,6 +18,8 @@ jobs: - 8.3 experimental: - true + exclude: + - php: '8.0' name: PHP:${{ matrix.php }} Code Audit From 81bd5b797b6e958e072422aa76d564a2f1c13908 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 29 Nov 2024 17:21:59 +0800 Subject: [PATCH 30/35] [7.x] Add new `package:sync-skeleton` command (#269) * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- CHANGELOG-7.x.md | 9 +++ src/Attributes/UsesVendor.php | 4 +- src/Console/Commander.php | 9 ++- .../Actions/CreateVendorSymlink.php | 60 ++++++++++++++ .../DeleteVendorSymlink.php | 6 +- .../Bootstrap/CreateVendorSymlink.php | 48 ++--------- .../SyncTestbenchConfigurationFiles.php | 44 ----------- .../Console/Concerns/CopyTestbenchFiles.php | 79 ++++++++++++++----- .../Concerns/HandleTerminatingConsole.php | 27 +++---- .../Console/PurgeSkeletonCommand.php | 11 ++- .../Console/SyncSkeletonCommand.php | 59 ++++++++++++++ src/Foundation/Console/TerminatingConsole.php | 65 +++++++++++++++ src/Foundation/TestbenchServiceProvider.php | 1 + .../Actions/AddAssetSymlinkFolders.php | 75 ++++++++++++++++++ .../Actions/RemoveAssetSymlinkFolders.php | 64 +++++++++++++++ .../Bootstrap/CreateVendorSymlinkTest.php | 4 +- 16 files changed, 433 insertions(+), 132 deletions(-) create mode 100644 src/Foundation/Actions/CreateVendorSymlink.php rename src/Foundation/{Bootstrap => Actions}/DeleteVendorSymlink.php (81%) delete mode 100644 src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php create mode 100644 src/Foundation/Console/SyncSkeletonCommand.php create mode 100644 src/Foundation/Console/TerminatingConsole.php create mode 100644 src/Workbench/Actions/AddAssetSymlinkFolders.php create mode 100644 src/Workbench/Actions/RemoveAssetSymlinkFolders.php diff --git a/CHANGELOG-7.x.md b/CHANGELOG-7.x.md index f17f64a4..8118d630 100644 --- a/CHANGELOG-7.x.md +++ b/CHANGELOG-7.x.md @@ -9,6 +9,7 @@ This changelog references the relevant changes (bug and security fixes) done to * Added ability to detect Workbench namespace via `Orchestra\Testbench\Workbench\Workbench::detectNamespace()` method. * Added ability to detect the default user model via `Orchestra\Testbench\Workbench\Workbench::applicationUserModel()` method. * Added support for authentication routes within Workbench by configurating `workbench.auth` config to `true`. +* Added new `package:sync-skeleton` command. ### Changes @@ -17,6 +18,14 @@ This changelog references the relevant changes (bug and security fixes) done to - Refactor `Orchestra\Testbench\Console\Commander`. * Add multiple environment variables to Laravel 9 skeleton's configuration files based on changes made for Laravel 11. * Add `$tty` parameter to `Orchestra\Testbench\remote()` function. +* Refactor `Orchestra\Testbench\Foundation\Bootstrap\CreateVendorSymlink` class and mark it as `@api`. +* Add `$backupExistingFile` and `$resetOnTerminating` parameter to following methods in `Orchestra\Testbench\Foundation\Console\Concerns\CopyTestbenchFiles` trait: + - `copyTestbenchConfigurationFile()` + - `copyTestbenchDotEnvFile()` + +### Deprecated + +* Deprecate `Orchestra\Testbench\Foundation\Console\Concerns\HandleTerminatingConsole` trait, use `Orchestra\Testbench\Foundation\Console\TerminatingConsole` class instead. ## 7.48.0 diff --git a/src/Attributes/UsesVendor.php b/src/Attributes/UsesVendor.php index ab5126a0..13e736b8 100644 --- a/src/Attributes/UsesVendor.php +++ b/src/Attributes/UsesVendor.php @@ -5,8 +5,8 @@ use Attribute; use Orchestra\Testbench\Contracts\Attributes\AfterEach as AfterEachContract; use Orchestra\Testbench\Contracts\Attributes\BeforeEach as BeforeEachContract; +use Orchestra\Testbench\Foundation\Actions\DeleteVendorSymlink; use Orchestra\Testbench\Foundation\Application; -use Orchestra\Testbench\Foundation\Bootstrap\DeleteVendorSymlink; use function Orchestra\Testbench\package_path; @@ -40,7 +40,7 @@ public function beforeEach($app): void public function afterEach($app): void { if ($this->vendorSymlinkCreated === true) { - (new DeleteVendorSymlink)->bootstrap($app); + (new DeleteVendorSymlink)->handle($app); } } } diff --git a/src/Console/Commander.php b/src/Console/Commander.php index 75ba33ca..3fb0efb4 100644 --- a/src/Console/Commander.php +++ b/src/Console/Commander.php @@ -14,6 +14,7 @@ use Orchestra\Testbench\Foundation\Config; use Orchestra\Testbench\Foundation\Console\Concerns\CopyTestbenchFiles; use Orchestra\Testbench\Foundation\Console\Signals; +use Orchestra\Testbench\Foundation\Console\TerminatingConsole; use Orchestra\Testbench\Foundation\TestbenchServiceProvider; use Orchestra\Testbench\Workbench\Workbench; use Symfony\Component\Console\Application as ConsoleApplication; @@ -118,7 +119,7 @@ public function handle() } catch (Throwable $error) { $status = $this->handleException($output, $error); } finally { - $this->handleTerminatingConsole(); + TerminatingConsole::handle(); Workbench::flush(); static::$testbench::flushState(); @@ -158,6 +159,8 @@ public function laravel() 'extra' => $this->config->getExtraAttributes(), ]), ); + + $this->app->instance('TESTBENCH_COMMANDER', $this); } return $this->app; @@ -251,7 +254,7 @@ protected function prepareCommandSignals(): void Collection::make(Arr::wrap([SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT])) ->each( fn ($signal) => $this->signals->register($signal, function () use ($signal) { - $this->handleTerminatingConsole(); + TerminatingConsole::handle(); Workbench::flush(); $status = match ($signal) { @@ -272,7 +275,7 @@ protected function prepareCommandSignals(): void }, function () { if (windows_os() && PHP_SAPI === 'cli' && \function_exists('sapi_windows_set_ctrl_handler')) { sapi_windows_set_ctrl_handler(function ($event) { - $this->handleTerminatingConsole(); + TerminatingConsole::handle(); Workbench::flush(); $status = match ($event) { diff --git a/src/Foundation/Actions/CreateVendorSymlink.php b/src/Foundation/Actions/CreateVendorSymlink.php new file mode 100644 index 00000000..74014681 --- /dev/null +++ b/src/Foundation/Actions/CreateVendorSymlink.php @@ -0,0 +1,60 @@ +basePath('vendor'); + + $vendorLinkCreated = false; + + if (! laravel_vendor_exists($app, $this->workingPath)) { + if ($filesystem->exists($app->bootstrapPath(join_paths('cache', 'packages.php')))) { + $filesystem->delete($app->bootstrapPath(join_paths('cache', 'packages.php'))); + } + + (new DeleteVendorSymlink)->handle($app); + + try { + $filesystem->link($this->workingPath, $appVendorPath); + + $vendorLinkCreated = true; + } catch (ErrorException $e) { + // + } + } + + $app->flush(); + + $app->instance('TESTBENCH_VENDOR_SYMLINK', $vendorLinkCreated); + } +} diff --git a/src/Foundation/Bootstrap/DeleteVendorSymlink.php b/src/Foundation/Actions/DeleteVendorSymlink.php similarity index 81% rename from src/Foundation/Bootstrap/DeleteVendorSymlink.php rename to src/Foundation/Actions/DeleteVendorSymlink.php index be353198..72ea21e0 100644 --- a/src/Foundation/Bootstrap/DeleteVendorSymlink.php +++ b/src/Foundation/Actions/DeleteVendorSymlink.php @@ -1,6 +1,6 @@ basePath('vendor'), static function ($appVendorPath) { if (windows_os() && is_dir($appVendorPath) && readlink($appVendorPath) !== $appVendorPath) { diff --git a/src/Foundation/Bootstrap/CreateVendorSymlink.php b/src/Foundation/Bootstrap/CreateVendorSymlink.php index fb57812d..b8929b57 100644 --- a/src/Foundation/Bootstrap/CreateVendorSymlink.php +++ b/src/Foundation/Bootstrap/CreateVendorSymlink.php @@ -2,34 +2,22 @@ namespace Orchestra\Testbench\Foundation\Bootstrap; -use ErrorException; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Filesystem\Filesystem; - -use function Orchestra\Testbench\join_paths; -use function Orchestra\Testbench\laravel_vendor_exists; +use Orchestra\Testbench\Foundation\Actions\CreateVendorSymlink as Action; /** - * @internal + * @api */ final class CreateVendorSymlink { - /** - * The project working path. - * - * @var string - */ - public $workingPath; - /** * Construct a new Create Vendor Symlink bootstrapper. * * @param string $workingPath */ - public function __construct(string $workingPath) - { - $this->workingPath = $workingPath; - } + public function __construct( + public string $workingPath + ) {} /** * Bootstrap the given application. @@ -39,30 +27,6 @@ public function __construct(string $workingPath) */ public function bootstrap(Application $app): void { - $filesystem = new Filesystem; - - $appVendorPath = $app->basePath('vendor'); - - $vendorLinkCreated = false; - - if (! laravel_vendor_exists($app, $this->workingPath)) { - if ($filesystem->exists($app->bootstrapPath(join_paths('cache', 'packages.php')))) { - $filesystem->delete($app->bootstrapPath(join_paths('cache', 'packages.php'))); - } - - (new DeleteVendorSymlink)->bootstrap($app); - - try { - $filesystem->link($this->workingPath, $appVendorPath); - - $vendorLinkCreated = true; - } catch (ErrorException $e) { - // - } - } - - $app->flush(); - - $app->instance('TESTBENCH_VENDOR_SYMLINK', $vendorLinkCreated); + (new Action($this->workingPath))->handle($app); } } diff --git a/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php b/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php deleted file mode 100644 index 135af6ca..00000000 --- a/src/Foundation/Bootstrap/SyncTestbenchConfigurationFiles.php +++ /dev/null @@ -1,44 +0,0 @@ -exists($app->basePath('testbench.yaml'))) { - $this->copyTestbenchConfigurationFile($app, $filesystem, package_path()); - } - - if (! $filesystem->exists($app->basePath('.env'))) { - $this->copyTestbenchDotEnvFile($app, $filesystem, package_path()); - } - - $app->terminating(function () { - $this->handleTerminatingConsole(); - }); - } -} diff --git a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php index d2c7c64b..633bb750 100644 --- a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php +++ b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php @@ -5,23 +5,34 @@ use Illuminate\Contracts\Foundation\Application; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\LazyCollection; +use Orchestra\Testbench\Foundation\Console\TerminatingConsole; +use Orchestra\Testbench\Foundation\Env; use function Orchestra\Testbench\join_paths; +/** + * @codeCoverageIgnore + */ trait CopyTestbenchFiles { - use HandleTerminatingConsole; - /** * Copy the "testbench.yaml" file. * + * @internal + * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Filesystem\Filesystem $filesystem * @param string $workingPath + * @param bool $resetOnTerminating * @return void */ - protected function copyTestbenchConfigurationFile(Application $app, Filesystem $filesystem, string $workingPath): void - { + protected function copyTestbenchConfigurationFile( + Application $app, + Filesystem $filesystem, + string $workingPath, + bool $backupExistingFile = true, + bool $resetOnTerminating = true + ): void { $configurationFile = LazyCollection::make(static function () { yield 'testbench.yaml'; yield 'testbench.yaml.example'; @@ -32,10 +43,10 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ $testbenchFile = $app->basePath(join_paths('bootstrap', 'cache', 'testbench.yaml')); - if ($filesystem->isFile($testbenchFile)) { + if ($backupExistingFile === true && $filesystem->isFile($testbenchFile)) { $filesystem->copy($testbenchFile, "{$testbenchFile}.backup"); - $this->beforeTerminating(static function () use ($filesystem, $testbenchFile) { + TerminatingConsole::beforeWhen($resetOnTerminating, static function () use ($filesystem, $testbenchFile) { if ($filesystem->isFile("{$testbenchFile}.backup")) { $filesystem->move("{$testbenchFile}.backup", $testbenchFile); } @@ -45,7 +56,7 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ if (! \is_null($configurationFile)) { $filesystem->copy($configurationFile, $testbenchFile); - $this->beforeTerminating(static function () use ($filesystem, $testbenchFile) { + TerminatingConsole::beforeWhen($resetOnTerminating, static function () use ($filesystem, $testbenchFile) { if ($filesystem->isFile($testbenchFile)) { $filesystem->delete($testbenchFile); } @@ -56,20 +67,31 @@ protected function copyTestbenchConfigurationFile(Application $app, Filesystem $ /** * Copy the ".env" file. * + * @internal + * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Filesystem\Filesystem $filesystem * @param string $workingPath + * @param bool $resetOnTerminating * @return void */ - protected function copyTestbenchDotEnvFile(Application $app, Filesystem $filesystem, string $workingPath): void - { + protected function copyTestbenchDotEnvFile( + Application $app, + Filesystem $filesystem, + string $workingPath, + bool $backupExistingFile = true, + bool $resetOnTerminating = true + ): void { $workingPath = $filesystem->isDirectory(join_paths($workingPath, 'workbench')) ? join_paths($workingPath, 'workbench') : $workingPath; - $configurationFile = LazyCollection::make(function () { - yield $this->environmentFile; - yield "{$this->environmentFile}.example"; - yield "{$this->environmentFile}.dist"; + + $testbenchEnvFilename = $this->testbenchEnvironmentFile(); + + $configurationFile = LazyCollection::make(static function () use ($testbenchEnvFilename) { + yield $testbenchEnvFilename; + yield "{$testbenchEnvFilename}.example"; + yield "{$testbenchEnvFilename}.dist"; })->map(static fn ($file) => join_paths($workingPath, $file)) ->filter(static fn ($file) => $filesystem->isFile($file)) ->first(); @@ -79,22 +101,39 @@ protected function copyTestbenchDotEnvFile(Application $app, Filesystem $filesys } $environmentFile = $app->basePath('.env'); - $environmentFileBackup = $app->basePath("{$this->environmentFile}.backup"); - if ($filesystem->isFile($environmentFile)) { - $filesystem->copy($environmentFile, $environmentFileBackup); + if ($backupExistingFile === true && $filesystem->isFile($environmentFile)) { + $filesystem->copy($environmentFile, "{$environmentFile}.backup"); - $this->beforeTerminating(static function () use ($filesystem, $environmentFile, $environmentFileBackup) { - $filesystem->move($environmentFileBackup, $environmentFile); + TerminatingConsole::beforeWhen($resetOnTerminating, static function () use ($filesystem, $environmentFile) { + $filesystem->move("{$environmentFile}.backup", $environmentFile); }); } - if (! \is_null($configurationFile) && ! $filesystem->isFile($environmentFile)) { + if (! \is_null($configurationFile)) { $filesystem->copy($configurationFile, $environmentFile); - $this->beforeTerminating(static function () use ($filesystem, $environmentFile) { + TerminatingConsole::beforeWhen($resetOnTerminating, static function () use ($filesystem, $environmentFile) { $filesystem->delete($environmentFile); }); } } + + /** + * Determine the Testbench's environment file. + * + * @internal + * + * @return string + */ + protected function testbenchEnvironmentFile(): string + { + if (property_exists($this, 'environmentFile')) { + return $this->environmentFile; + } elseif (Env::has('TESTBENCH_ENVIRONMENT_FILE_USING')) { + return Env::get('TESTBENCH_ENVIRONMENT_FILE_USING'); + } + + return '.env'; + } } diff --git a/src/Foundation/Console/Concerns/HandleTerminatingConsole.php b/src/Foundation/Console/Concerns/HandleTerminatingConsole.php index 6edcff5d..c04e6d50 100644 --- a/src/Foundation/Console/Concerns/HandleTerminatingConsole.php +++ b/src/Foundation/Console/Concerns/HandleTerminatingConsole.php @@ -2,40 +2,37 @@ namespace Orchestra\Testbench\Foundation\Console\Concerns; -use Illuminate\Support\Collection; +use Orchestra\Testbench\Foundation\Console\TerminatingConsole; +/** + * @deprecated + * + * @codeCoverageIgnore + */ trait HandleTerminatingConsole { - /** - * The terminating callbacks. - * - * @var array - */ - protected $beforeTerminatingCallbacks = []; - /** * Register a callback to be run before terminating the command. * * @param callable():void $callback * @return void + * + * @deprecated Use `Orchestra\Testbench\Foundation\Console\TerminatingConsole::before()` instead. */ protected function beforeTerminating(callable $callback): void { - array_unshift($this->beforeTerminatingCallbacks, $callback); + TerminatingConsole::before($callback); } /** * Handle terminating console. * * @return void + * + * @deprecated Use `Orchestra\Testbench\Foundation\Console\TerminatingConsole::handle()` instead. */ protected function handleTerminatingConsole(): void { - Collection::make($this->beforeTerminatingCallbacks) - ->each(static function ($callback) { - \call_user_func($callback); - }); - - $this->beforeTerminatingCallbacks = []; + TerminatingConsole::handle(); } } diff --git a/src/Foundation/Console/PurgeSkeletonCommand.php b/src/Foundation/Console/PurgeSkeletonCommand.php index 427e5dc9..84dc39e9 100644 --- a/src/Foundation/Console/PurgeSkeletonCommand.php +++ b/src/Foundation/Console/PurgeSkeletonCommand.php @@ -7,6 +7,8 @@ use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use Orchestra\Testbench\Contracts\Config as ConfigContract; +use Orchestra\Testbench\Foundation\Env; +use Orchestra\Testbench\Workbench\Actions\RemoveAssetSymlinkFolders; use Symfony\Component\Console\Attribute\AsCommand; use function Orchestra\Testbench\join_paths; @@ -28,6 +30,7 @@ class PurgeSkeletonCommand extends Command * Execute the console command. * * @param \Illuminate\Filesystem\Filesystem $filesystem + * @param \Orchestra\Testbench\Contracts\Config $config * @return int */ public function handle(Filesystem $filesystem, ConfigContract $config) @@ -37,17 +40,23 @@ public function handle(Filesystem $filesystem, ConfigContract $config) $this->call('route:clear'); $this->call('view:clear'); + (new RemoveAssetSymlinkFolders($filesystem, $config))->handle(); + ['files' => $files, 'directories' => $directories] = $config->getPurgeAttributes(); $workingPath = $this->laravel->basePath(); + $environmentFile = Env::get('TESTBENCH_ENVIRONMENT_FILE_USING', '.env'); + (new Actions\DeleteFiles( filesystem: $filesystem, workingPath: $workingPath, ))->handle( Collection::make([ - '.env', + $environmentFile, + "{$environmentFile}.backup", join_paths('bootstrap', 'cache', 'testbench.yaml'), + join_paths('bootstrap', 'cache', 'testbench.yaml.backup'), ])->map(fn ($file) => $this->laravel->basePath($file)) ); diff --git a/src/Foundation/Console/SyncSkeletonCommand.php b/src/Foundation/Console/SyncSkeletonCommand.php new file mode 100644 index 00000000..bd94b625 --- /dev/null +++ b/src/Foundation/Console/SyncSkeletonCommand.php @@ -0,0 +1,59 @@ +copyTestbenchConfigurationFile( + $this->laravel, $filesystem, package_path(), backupExistingFile: false, resetOnTerminating: false + ); + + $this->copyTestbenchDotEnvFile( + $this->laravel, $filesystem, package_path(), backupExistingFile: false, resetOnTerminating: false + ); + + (new AddAssetSymlinkFolders($filesystem, $config))->handle(); + + return self::SUCCESS; + } +} diff --git a/src/Foundation/Console/TerminatingConsole.php b/src/Foundation/Console/TerminatingConsole.php new file mode 100644 index 00000000..ea765a9b --- /dev/null +++ b/src/Foundation/Console/TerminatingConsole.php @@ -0,0 +1,65 @@ + + */ + protected static array $beforeTerminatingCallbacks = []; + + /** + * Register a callback to be run before terminating the command. + * + * @param callable():void $callback + * @return void + */ + public static function before(callable $callback): void + { + array_unshift(self::$beforeTerminatingCallbacks, $callback); + } + + /** + * Register a callback to be run before terminating the command. + * + * @param bool $condition + * @param callable():void $callback + * @return void + */ + public static function beforeWhen(bool $condition, callable $callback): void + { + if ($condition === true) { + self::before($callback); + } + } + + /** + * Handle terminating console. + * + * @return void + */ + public static function handle(): void + { + Collection::make(self::$beforeTerminatingCallbacks) + ->each(static function ($callback) { + \call_user_func($callback); + }); + + self::purge(); + } + + /** + * Purge terminating console callbacks. + * + * @return void + */ + public static function purge(): void + { + self::$beforeTerminatingCallbacks = []; + } +} diff --git a/src/Foundation/TestbenchServiceProvider.php b/src/Foundation/TestbenchServiceProvider.php index 50593b98..179bf107 100644 --- a/src/Foundation/TestbenchServiceProvider.php +++ b/src/Foundation/TestbenchServiceProvider.php @@ -40,6 +40,7 @@ public function boot() Console\DevToolCommand::class, Console\DropSqliteDbCommand::class, Console\PurgeSkeletonCommand::class, + Console\SyncSkeletonCommand::class, Console\ServeCommand::class, ]); } diff --git a/src/Workbench/Actions/AddAssetSymlinkFolders.php b/src/Workbench/Actions/AddAssetSymlinkFolders.php new file mode 100644 index 00000000..066ecf6f --- /dev/null +++ b/src/Workbench/Actions/AddAssetSymlinkFolders.php @@ -0,0 +1,75 @@ + $sync */ + $sync = $this->config->getWorkbenchAttributes()['sync'] ?? []; + + Collection::make($sync) + ->map(function ($pair) { + /** @var string $from */ + $from = package_path($pair['from']); + + /** @var string $to */ + $to = base_path($pair['to']); + + return $this->files->isDirectory($from) + ? ['from' => $from, 'to' => $to] + : null; + })->filter() + ->each(function ($pair) { + /** @var array{from: string, to: string} $pair */ + + /** @var string $from */ + $from = $pair['from']; + + /** @var string $to */ + $to = $pair['to']; + + if (is_link($to)) { + $this->files->delete($to); + } elseif ($this->files->isDirectory($to)) { + $this->files->deleteDirectory($to); + } + + /** @var string $rootDirectory */ + $rootDirectory = Str::beforeLast($to, '/'); + + if (! $this->files->isDirectory($rootDirectory)) { + $this->files->ensureDirectoryExists($rootDirectory); + } + + $this->files->link($from, $to); + }); + } +} diff --git a/src/Workbench/Actions/RemoveAssetSymlinkFolders.php b/src/Workbench/Actions/RemoveAssetSymlinkFolders.php new file mode 100644 index 00000000..6f19f1ac --- /dev/null +++ b/src/Workbench/Actions/RemoveAssetSymlinkFolders.php @@ -0,0 +1,64 @@ + $sync */ + $sync = $this->config->getWorkbenchAttributes()['sync'] ?? []; + + Collection::make($sync) + ->map(static function ($pair) { + /** @var string $from */ + $from = package_path($pair['from']); + + /** @var string $to */ + $to = base_path($pair['to']); + + if (windows_os() && is_dir($to) && readlink($to) !== $to) { + return [$to, static function ($to) { + @rmdir($to); + }]; + } elseif (is_link($to)) { + return [$to, static function ($to) { + @unlink($to); + }]; + } + + return null; + })->filter() + ->each(static function ($payload) { + /** @var array{0: string, 1: (\Closure(string):(void))} $payload */ + value($payload[1], $payload[0]); + + @clearstatcache(false, \dirname($payload[0])); + }); + } +} diff --git a/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php b/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php index ed1b598d..11e09329 100644 --- a/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php +++ b/tests/Foundation/Bootstrap/CreateVendorSymlinkTest.php @@ -3,8 +3,8 @@ namespace Orchestra\Testbench\Tests\Foundation\Bootstrap; use Illuminate\Filesystem\Filesystem; +use Orchestra\Testbench\Foundation\Actions\DeleteVendorSymlink; use Orchestra\Testbench\Foundation\Bootstrap\CreateVendorSymlink; -use Orchestra\Testbench\Foundation\Bootstrap\DeleteVendorSymlink; use Orchestra\Testbench\TestCase; use function Orchestra\Testbench\container; @@ -23,7 +23,7 @@ public function it_can_create_vendor_symlink() $stub = (new CreateVendorSymlink($workingPath)); if (laravel_vendor_exists($laravel, $workingPath)) { - (new DeleteVendorSymlink)->bootstrap($laravel); + (new DeleteVendorSymlink)->handle($laravel); } $stub->bootstrap($laravel); From 88b778a6f3889ac97ec086d37329c152747254f1 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 29 Nov 2024 17:32:19 +0800 Subject: [PATCH 31/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Actions/CreateVendorSymlink.php | 2 +- src/Workbench/Actions/AddAssetSymlinkFolders.php | 4 ++-- src/Workbench/Actions/RemoveAssetSymlinkFolders.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Foundation/Actions/CreateVendorSymlink.php b/src/Foundation/Actions/CreateVendorSymlink.php index 74014681..64ef79ef 100644 --- a/src/Foundation/Actions/CreateVendorSymlink.php +++ b/src/Foundation/Actions/CreateVendorSymlink.php @@ -20,7 +20,7 @@ final class CreateVendorSymlink * @param string $workingPath */ public function __construct( - public string $workingPath + protected string $workingPath ) {} /** diff --git a/src/Workbench/Actions/AddAssetSymlinkFolders.php b/src/Workbench/Actions/AddAssetSymlinkFolders.php index 066ecf6f..7ff6dc76 100644 --- a/src/Workbench/Actions/AddAssetSymlinkFolders.php +++ b/src/Workbench/Actions/AddAssetSymlinkFolders.php @@ -21,8 +21,8 @@ final class AddAssetSymlinkFolders * @param \Orchestra\Testbench\Contracts\Config $config */ public function __construct( - public Filesystem $files, - public ConfigContract $config + protected Filesystem $files, + protected ConfigContract $config ) {} /** diff --git a/src/Workbench/Actions/RemoveAssetSymlinkFolders.php b/src/Workbench/Actions/RemoveAssetSymlinkFolders.php index 6f19f1ac..d1789118 100644 --- a/src/Workbench/Actions/RemoveAssetSymlinkFolders.php +++ b/src/Workbench/Actions/RemoveAssetSymlinkFolders.php @@ -20,8 +20,8 @@ final class RemoveAssetSymlinkFolders * @param \Orchestra\Testbench\Contracts\Config $config */ public function __construct( - public Filesystem $files, - public ConfigContract $config + protected Filesystem $files, + protected ConfigContract $config ) {} /** From aa786086a2845c7d4be12f3d86e09bb55c873e37 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sat, 30 Nov 2024 10:09:27 +0800 Subject: [PATCH 32/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Foundation/Console/Concerns/CopyTestbenchFiles.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php index 633bb750..5ac6f25c 100644 --- a/src/Foundation/Console/Concerns/CopyTestbenchFiles.php +++ b/src/Foundation/Console/Concerns/CopyTestbenchFiles.php @@ -89,10 +89,17 @@ protected function copyTestbenchDotEnvFile( $testbenchEnvFilename = $this->testbenchEnvironmentFile(); $configurationFile = LazyCollection::make(static function () use ($testbenchEnvFilename) { + $defaultTestbenchEnvFilename = '.env'; + yield $testbenchEnvFilename; yield "{$testbenchEnvFilename}.example"; yield "{$testbenchEnvFilename}.dist"; - })->map(static fn ($file) => join_paths($workingPath, $file)) + + yield $defaultTestbenchEnvFilename; + yield "{$defaultTestbenchEnvFilename}.example"; + yield "{$defaultTestbenchEnvFilename}.dist"; + })->unique() + ->map(static fn ($file) => join_paths($workingPath, $file)) ->filter(static fn ($file) => $filesystem->isFile($file)) ->first(); From a508d167ed5ffb685595c4f69addd8e05438fe48 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sat, 30 Nov 2024 17:27:14 +0800 Subject: [PATCH 33/35] wip Signed-off-by: Mior Muhammad Zaki --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index d3839132..ddb24c19 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ }, "require-dev": { "fakerphp/faker": "^1.21", - "laravel/framework": "^9.52.17", + "laravel/framework": "^9.52.18", "laravel/pint": "^1.4", "mockery/mockery": "^1.5.1", "phpstan/phpstan": "^2.0", @@ -53,7 +53,7 @@ }, "conflict": { "brianium/paratest": "<6.4.0 || >=7.0.0", - "laravel/framework": "<9.52.17 || >=10.0.0", + "laravel/framework": "<9.52.18 || >=10.0.0", "laravel/serializable-closure": "<1.3.0 || >=2.0.0", "orchestra/testbench-dusk": "<7.50.0 || >=8.0.0", "orchestra/workbench": "<1.0.0", @@ -64,7 +64,7 @@ "ext-pcntl": "Required to use all features of the console signal trapping.", "brianium/paratest": "Allow using parallel testing (^6.4).", "fakerphp/faker": "Allow using Faker for testing (^1.21).", - "laravel/framework": "Required for testing (^9.52.17).", + "laravel/framework": "Required for testing (^9.52.18).", "mockery/mockery": "Allow using Mockery for testing (^1.5.1).", "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^6.2).", "orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^7.0).", From c76bf8e2ed6dfeb1a2e199242b66b86b4dcb562c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sun, 1 Dec 2024 17:05:14 +0800 Subject: [PATCH 34/35] Prepare 7.49.0 release Signed-off-by: Mior Muhammad Zaki --- CHANGELOG-7.x.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-7.x.md b/CHANGELOG-7.x.md index 8118d630..64579e6d 100644 --- a/CHANGELOG-7.x.md +++ b/CHANGELOG-7.x.md @@ -2,7 +2,9 @@ This changelog references the relevant changes (bug and security fixes) done to `orchestra/testbench-core`. -## 7.49.0 (Unreleased) +## 7.49.0 + +Released: 2024-12-01 ### Added From abefd181cc66686d8b766877c2b506e4bb562fb2 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 3 Dec 2024 08:06:46 +0800 Subject: [PATCH 35/35] wip Signed-off-by: Mior Muhammad Zaki --- src/Concerns/InteractsWithWorkbench.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Concerns/InteractsWithWorkbench.php b/src/Concerns/InteractsWithWorkbench.php index e8208996..20730924 100644 --- a/src/Concerns/InteractsWithWorkbench.php +++ b/src/Concerns/InteractsWithWorkbench.php @@ -64,7 +64,7 @@ protected function getPackageBootstrappersUsingWorkbench($app): ?array * Get package providers. * * @param \Illuminate\Foundation\Application $app - * @return array|null + * @return array>|null */ protected function getPackageProvidersUsingWorkbench($app): ?array {