diff --git a/.env.development.example b/.env.development.example index f7a01180e3..7c8fc5c028 100644 --- a/.env.development.example +++ b/.env.development.example @@ -9,6 +9,9 @@ APP_DEBUG=true SSH_MUX_ENABLED=true COOLIFY_CONTAINER_ROLE=all DEV_SENTINEL_URL= +# IPv4 address for seeded sslip.io domains (server wildcard domain and example apps). Empty uses 127.0.0.1. +# Set it to a LAN or Tailscale IP to reach seeded deployments from other devices, then reseed. +DEV_SEED_IP= # PostgreSQL Database Configuration DB_DATABASE=coolify diff --git a/config/constants.php b/config/constants.php index 9c7df6e804..0c4d9c7834 100644 --- a/config/constants.php +++ b/config/constants.php @@ -14,6 +14,8 @@ return [ // devHostDockerPath() uses them only for that server. The defaults are the legacy docker-compose.dev.yml names. 'dev_data_volume' => env('DEV_COOLIFY_DATA_VOLUME', 'coolify_dev_coolify_data'), 'dev_backups_volume' => env('DEV_COOLIFY_BACKUPS_VOLUME', 'coolify_dev_backups_data'), + // Development only: IP for the seeded sslip.io wildcard domain and application domains (for example a Tailscale IP). + 'dev_seed_ip' => env('DEV_SEED_IP') ?: '127.0.0.1', 'registry_url' => env('REGISTRY_URL', 'docker.io'), 'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'docker.io').'/coollabsio/coolify-helper'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), diff --git a/database/seeders/ApplicationSeeder.php b/database/seeders/ApplicationSeeder.php index 779f1f7df2..50b573b56f 100644 --- a/database/seeders/ApplicationSeeder.php +++ b/database/seeders/ApplicationSeeder.php @@ -10,18 +10,24 @@ use Illuminate\Database\Seeder; class ApplicationSeeder extends Seeder { + public const EXAMPLES_REPOSITORY = 'coollabsio/coolify-examples'; + + public const EXAMPLES_REPOSITORY_PROJECT_ID = 603035348; + /** * Run the database seeds. */ public function run(): void { + $sslipHost = ServerSettingSeeder::sslipHost(); + Application::create([ 'uuid' => 'docker-compose', 'name' => 'Docker Compose Example', - 'repository_project_id' => 603035348, - 'git_repository' => 'coollabsio/coolify-examples', + 'repository_project_id' => self::EXAMPLES_REPOSITORY_PROJECT_ID, + 'git_repository' => self::EXAMPLES_REPOSITORY, 'git_branch' => 'main', - 'base_directory' => '/docker-compose', + 'base_directory' => '/internal/compose', 'docker_compose_location' => '/docker-compose-test.yaml', 'build_pack' => 'dockercompose', 'ports_exposes' => '80', @@ -31,30 +37,14 @@ class ApplicationSeeder extends Seeder 'source_id' => 1, 'source_type' => GithubApp::class, ]); - Application::create([ - 'uuid' => 'nodejs', - 'name' => 'NodeJS Fastify Example', - 'fqdn' => 'http://nodejs.127.0.0.1.sslip.io', - 'repository_project_id' => 603035348, - 'git_repository' => 'coollabsio/coolify-examples', - 'git_branch' => 'main', - 'base_directory' => '/nodejs', - 'build_pack' => 'nixpacks', - 'ports_exposes' => '3000', - 'environment_id' => 1, - 'destination_id' => 0, - 'destination_type' => StandaloneDocker::class, - 'source_id' => 1, - 'source_type' => GithubApp::class, - ]); Application::create([ 'uuid' => 'dockerfile', 'name' => 'Dockerfile Example', - 'fqdn' => 'http://dockerfile.127.0.0.1.sslip.io', - 'repository_project_id' => 603035348, - 'git_repository' => 'coollabsio/coolify-examples', + 'fqdn' => "http://dockerfile.{$sslipHost}", + 'repository_project_id' => self::EXAMPLES_REPOSITORY_PROJECT_ID, + 'git_repository' => self::EXAMPLES_REPOSITORY, 'git_branch' => 'main', - 'base_directory' => '/dockerfile', + 'base_directory' => '/dockerfile/single-stage', 'build_pack' => 'dockerfile', 'ports_exposes' => '80', 'environment_id' => 1, @@ -66,7 +56,7 @@ class ApplicationSeeder extends Seeder Application::create([ 'uuid' => 'dockerfile-pure', 'name' => 'Pure Dockerfile Example', - 'fqdn' => 'http://pure-dockerfile.127.0.0.1.sslip.io', + 'fqdn' => "http://pure-dockerfile.{$sslipHost}", 'git_repository' => 'coollabsio/coolify', 'git_branch' => 'main', 'git_commit_sha' => 'HEAD', @@ -99,51 +89,81 @@ CMD ["nginx", "-g", "daemon off;"] CMD ["sh", "-c", "echo Crashing in 5 seconds... && sleep 5 && exit 1"] ', ]); - Application::create([ - 'uuid' => 'github-deploy-key', - 'name' => 'GitHub Deploy Key Example', - 'fqdn' => 'http://github-deploy-key.127.0.0.1.sslip.io', - 'git_repository' => 'git@github.com:coollabsio/coolify-examples-deploy-key.git', - 'git_branch' => 'main', - 'build_pack' => 'nixpacks', - 'ports_exposes' => '80', - 'environment_id' => 1, - 'destination_id' => 0, - 'destination_type' => StandaloneDocker::class, - 'source_id' => 0, - 'source_type' => GithubApp::class, - 'private_key_id' => 1, - ]); - Application::create([ - 'uuid' => 'gitlab-deploy-key', - 'name' => 'GitLab Deploy Key Example', - 'fqdn' => 'http://gitlab-deploy-key.127.0.0.1.sslip.io', - 'git_repository' => 'git@gitlab.com:coollabsio/php-example.git', - 'git_branch' => 'main', - 'build_pack' => 'nixpacks', - 'ports_exposes' => '80', - 'environment_id' => 1, - 'destination_id' => 0, - 'destination_type' => StandaloneDocker::class, - 'source_id' => 1, - 'source_type' => GitlabApp::class, - 'private_key_id' => 1, - ]); - Application::create([ - 'uuid' => 'gitlab-public-example', - 'name' => 'GitLab Public Example', - 'fqdn' => 'http://gitlab-public.127.0.0.1.sslip.io', - 'git_repository' => 'https://gitlab.com/andrasbacsai/coolify-examples.git', - 'base_directory' => '/astro/static', - 'publish_directory' => '/dist', - 'git_branch' => 'main', - 'build_pack' => 'nixpacks', - 'ports_exposes' => '80', - 'environment_id' => 1, - 'destination_id' => 0, - 'destination_type' => StandaloneDocker::class, - 'source_id' => 1, - 'source_type' => GitlabApp::class, - ]); + + foreach (self::railpackExamples() as $example) { + $application = Application::create([ + 'uuid' => $example['uuid'], + 'name' => $example['name'], + 'fqdn' => "http://{$example['uuid']}.{$sslipHost}", + 'repository_project_id' => isset($example['git_repository']) ? null : self::EXAMPLES_REPOSITORY_PROJECT_ID, + 'git_repository' => $example['git_repository'] ?? self::EXAMPLES_REPOSITORY, + 'git_branch' => 'main', + 'base_directory' => $example['base_directory'] ?? '/', + 'publish_directory' => $example['publish_directory'] ?? null, + 'build_command' => $example['build_command'] ?? null, + 'start_command' => $example['start_command'] ?? null, + 'build_pack' => 'railpack', + 'static_image' => 'nginx:alpine', + 'ports_exposes' => $example['ports_exposes'], + 'environment_id' => 1, + 'destination_id' => 0, + 'destination_type' => StandaloneDocker::class, + 'source_id' => $example['source_id'] ?? 0, + 'source_type' => $example['source_type'] ?? GithubApp::class, + 'private_key_id' => $example['private_key_id'] ?? null, + ]); + + $application->settings->update([ + 'is_static' => $example['is_static'] ?? false, + 'is_spa' => $example['is_spa'] ?? false, + ]); + } + } + + /** + * Railpack examples from coollabsio/coolify-examples (main branch) and the deploy key / GitLab examples. + * + * @return array> + */ + public static function railpackExamples(): array + { + return [ + // JavaScript runtimes + ['uuid' => 'nodejs', 'name' => 'Node.js Fastify Example', 'base_directory' => '/javascript/runtimes/nodejs', 'ports_exposes' => '3000', 'source_id' => 1], + ['uuid' => 'bun', 'name' => 'Bun Example', 'base_directory' => '/javascript/runtimes/bun', 'ports_exposes' => '3000'], + // JavaScript frameworks (server) + ['uuid' => 'nestjs', 'name' => 'NestJS Example', 'base_directory' => '/javascript/frameworks/nestjs', 'ports_exposes' => '3000', 'start_command' => 'npm run start:prod'], + ['uuid' => 'adonisjs', 'name' => 'AdonisJS Example', 'base_directory' => '/javascript/frameworks/adonisjs/simple', 'ports_exposes' => '3333'], + ['uuid' => 'remix', 'name' => 'Remix Example', 'base_directory' => '/javascript/frameworks/remix', 'ports_exposes' => '3000'], + ['uuid' => 'nextjs-ssr', 'name' => 'Next.js SSR Example', 'base_directory' => '/javascript/frameworks/nextjs/ssr', 'ports_exposes' => '3000'], + ['uuid' => 'nuxt-server', 'name' => 'Nuxt Server Example', 'base_directory' => '/javascript/frameworks/nuxt/server', 'ports_exposes' => '3000'], + ['uuid' => 'nuxt-nitro', 'name' => 'Nuxt Nitro Example', 'base_directory' => '/javascript/frameworks/nuxt/nitro', 'ports_exposes' => '3000'], + ['uuid' => 'astro-server', 'name' => 'Astro Server Example', 'base_directory' => '/javascript/frameworks/astro/server', 'ports_exposes' => '4321', 'start_command' => 'HOST=0.0.0.0 node dist/server/entry.mjs'], + ['uuid' => 'vue-ssr', 'name' => 'Vue SSR Example', 'base_directory' => '/javascript/frameworks/vue/ssr', 'ports_exposes' => '3000'], + // JavaScript frameworks and build tools (static) + ['uuid' => 'nextjs-static', 'name' => 'Next.js Static Example', 'base_directory' => '/javascript/frameworks/nextjs/spa', 'ports_exposes' => '80', 'publish_directory' => '/out', 'is_static' => true], + ['uuid' => 'nuxt-static', 'name' => 'Nuxt Static Example', 'base_directory' => '/javascript/frameworks/nuxt/static', 'ports_exposes' => '80', 'build_command' => 'npm run generate', 'publish_directory' => '/.output/public', 'is_static' => true], + ['uuid' => 'astro-static', 'name' => 'Astro Static Example', 'base_directory' => '/javascript/frameworks/astro/static', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'is_static' => true], + ['uuid' => 'vue-spa', 'name' => 'Vue SPA Example', 'base_directory' => '/javascript/frameworks/vue/spa', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'is_static' => true, 'is_spa' => true], + ['uuid' => 'vue-spa-router', 'name' => 'Vue SPA Router Example', 'base_directory' => '/javascript/frameworks/vue/spa-router', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'is_static' => true, 'is_spa' => true], + ['uuid' => 'vite-vanilla-js', 'name' => 'Vite Vanilla JS Example', 'base_directory' => '/javascript/build-tools/vite/vanilla-js', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'is_static' => true], + ['uuid' => 'vite-vanilla-ts', 'name' => 'Vite Vanilla TS Example', 'base_directory' => '/javascript/build-tools/vite/vanilla-ts', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'is_static' => true], + ['uuid' => 'static', 'name' => 'Static HTML Example', 'base_directory' => '/static', 'ports_exposes' => '80', 'publish_directory' => '/', 'is_static' => true], + // PHP + ['uuid' => 'laravel', 'name' => 'Laravel Example', 'base_directory' => '/php/laravel', 'ports_exposes' => '80'], + ['uuid' => 'laravel-pure', 'name' => 'Laravel Pure Example', 'base_directory' => '/php/laravel-pure', 'ports_exposes' => '80'], + ['uuid' => 'laravel-inertia', 'name' => 'Laravel Inertia Example', 'base_directory' => '/php/laravel-inertia', 'ports_exposes' => '80'], + ['uuid' => 'symfony', 'name' => 'Symfony Example', 'base_directory' => '/php/symfony', 'ports_exposes' => '80'], + // Other languages + ['uuid' => 'python-flask', 'name' => 'Python Flask Example', 'base_directory' => '/python/flask', 'ports_exposes' => '5000', 'start_command' => 'flask run --host=0.0.0.0 --port=5000'], + ['uuid' => 'go-gin', 'name' => 'Go Gin Example', 'base_directory' => '/go/gin', 'ports_exposes' => '3000'], + ['uuid' => 'rust-rocket', 'name' => 'Rust Rocket Example', 'base_directory' => '/rust/rocket', 'ports_exposes' => '8000'], + ['uuid' => 'ruby-rails', 'name' => 'Ruby on Rails Example', 'base_directory' => '/ruby/rails', 'ports_exposes' => '3000'], + ['uuid' => 'elixir-phoenix', 'name' => 'Elixir Phoenix Example', 'base_directory' => '/elixir/phoenix', 'ports_exposes' => '4000'], + // Deploy keys and GitLab + ['uuid' => 'github-deploy-key', 'name' => 'GitHub Deploy Key Example', 'git_repository' => 'git@github.com:coollabsio/coolify-examples-deploy-key.git', 'ports_exposes' => '80', 'private_key_id' => 1], + ['uuid' => 'gitlab-deploy-key', 'name' => 'GitLab Deploy Key Example', 'git_repository' => 'git@gitlab.com:coollabsio/php-example.git', 'ports_exposes' => '80', 'source_id' => 1, 'source_type' => GitlabApp::class, 'private_key_id' => 1], + ['uuid' => 'gitlab-public-example', 'name' => 'GitLab Public Example', 'git_repository' => 'https://gitlab.com/andrasbacsai/coolify-examples.git', 'base_directory' => '/astro/static', 'ports_exposes' => '80', 'publish_directory' => '/dist', 'source_id' => 1, 'source_type' => GitlabApp::class, 'is_static' => true], + ]; } } diff --git a/database/seeders/ApplicationSettingsSeeder.php b/database/seeders/ApplicationSettingsSeeder.php index 87236df8a9..8e439fd169 100644 --- a/database/seeders/ApplicationSettingsSeeder.php +++ b/database/seeders/ApplicationSettingsSeeder.php @@ -15,12 +15,5 @@ class ApplicationSettingsSeeder extends Seeder $application_1 = Application::find(1)->load(['settings']); $application_1->settings->is_debug_enabled = false; $application_1->settings->save(); - - $gitlabPublic = Application::where('uuid', 'gitlab-public-example')->first(); - if ($gitlabPublic) { - $gitlabPublic->load(['settings']); - $gitlabPublic->settings->is_static = true; - $gitlabPublic->settings->save(); - } } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 4f5c4431a7..57ccab4aea 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -31,11 +31,5 @@ class DatabaseSeeder extends Seeder CaSslCertSeeder::class, PersonalAccessTokenSeeder::class, ]); - - if (in_array(config('app.env'), ['local', 'development', 'dev'], true)) { - $this->call([ - DevelopmentRailpackExamplesSeeder::class, - ]); - } } } diff --git a/database/seeders/DevelopmentRailpackExamplesSeeder.php b/database/seeders/DevelopmentRailpackExamplesSeeder.php deleted file mode 100644 index fa5d281ab8..0000000000 --- a/database/seeders/DevelopmentRailpackExamplesSeeder.php +++ /dev/null @@ -1,604 +0,0 @@ -isDevelopmentEnvironment()) { - $this->command?->warn('Skipping DevelopmentRailpackExamplesSeeder outside development mode.'); - - return; - } - - $this->ensureDevelopmentPrerequisitesExist(); - - if (! StandaloneDocker::query()->find(0)) { - throw new RuntimeException('StandaloneDocker with id=0 is required before running DevelopmentRailpackExamplesSeeder.'); - } - - $this->cleanupLegacyLimaProjects(); - $this->cleanupLegacyProductionExamples(); - - $this->seedEnvironment(StandaloneDocker::query()->findOrFail(0)); - } - - /** - * @return array> - */ - public static function examples(): array - { - return [ - [ - 'uuid' => 'railpack-simple-webserver', - 'name' => 'Railpack Simple Webserver Example', - 'base_directory' => '/node/simple-webserver', - 'ports_exposes' => '3000', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-expressjs', - 'name' => 'Railpack Express.js Example', - 'base_directory' => '/node/expressjs', - 'ports_exposes' => '3000', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-fastify', - 'name' => 'Railpack Fastify Example', - 'base_directory' => '/node/fastify', - 'ports_exposes' => '3000', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-nestjs', - 'name' => 'Railpack NestJS Example', - 'base_directory' => '/node/nestjs', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start:prod', - ], - [ - 'uuid' => 'railpack-adonisjs', - 'name' => 'Railpack AdonisJS Example', - 'base_directory' => '/node/adonisjs', - 'ports_exposes' => '3333', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-hono', - 'name' => 'Railpack Hono Example', - 'base_directory' => '/node/hono', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-koa', - 'name' => 'Railpack Koa Example', - 'base_directory' => '/node/koa', - 'ports_exposes' => '3000', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-nextjs-ssr', - 'name' => 'Railpack Next.js SSR Example', - 'base_directory' => '/node/nextjs/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-nuxtjs-ssr', - 'name' => 'Railpack NuxtJS SSR Example', - 'base_directory' => '/node/nuxtjs/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run preview -- --host 0.0.0.0 --port 3000', - ], - [ - 'uuid' => 'railpack-astro-ssr', - 'name' => 'Railpack Astro SSR Example', - 'base_directory' => '/node/astro/ssr', - 'ports_exposes' => '4321', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-sveltekit-ssr', - 'name' => 'Railpack SvelteKit SSR Example', - 'base_directory' => '/node/sveltekit/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-tanstack-start-ssr', - 'name' => 'Railpack TanStack Start SSR Example', - 'base_directory' => '/node/tanstack-start/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-angular-ssr', - 'name' => 'Railpack Angular SSR Example', - 'base_directory' => '/node/angular/ssr', - 'ports_exposes' => '4000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-vue-ssr', - 'name' => 'Railpack Vue SSR Example', - 'base_directory' => '/node/vue/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run start', - ], - [ - 'uuid' => 'railpack-qwik-ssr', - 'name' => 'Railpack Qwik SSR Example', - 'base_directory' => '/node/qwik/ssr', - 'ports_exposes' => '3000', - 'build_command' => 'npm run build', - 'start_command' => 'npm run serve', - ], - [ - 'uuid' => 'railpack-react-static', - 'name' => 'Railpack React Static Example', - 'base_directory' => '/node/react', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-vite-static', - 'name' => 'Railpack Vite Static Example', - 'base_directory' => '/node/vite', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-eleventy-static', - 'name' => 'Railpack Eleventy Static Example', - 'base_directory' => '/node/eleventy', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/_site', - 'is_static' => true, - ], - [ - 'uuid' => 'railpack-gatsby-static', - 'name' => 'Railpack Gatsby Static Example', - 'base_directory' => '/node/gatsby', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/public', - 'is_static' => true, - ], - [ - 'uuid' => 'railpack-nextjs-static', - 'name' => 'Railpack Next.js Static Example', - 'base_directory' => '/node/nextjs/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/out', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-nuxtjs-static', - 'name' => 'Railpack NuxtJS Static Example', - 'base_directory' => '/node/nuxtjs/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/.output/public', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-astro-static', - 'name' => 'Railpack Astro Static Example', - 'base_directory' => '/node/astro/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist', - 'is_static' => true, - ], - [ - 'uuid' => 'railpack-sveltekit-static', - 'name' => 'Railpack SvelteKit Static Example', - 'base_directory' => '/node/sveltekit/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/build', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-tanstack-start-static', - 'name' => 'Railpack TanStack Start Static Example', - 'base_directory' => '/node/tanstack-start/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/.output/public', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-angular-static', - 'name' => 'Railpack Angular Static Example', - 'base_directory' => '/node/angular/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist/static/browser', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-vue-static', - 'name' => 'Railpack Vue Static Example', - 'base_directory' => '/node/vue/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist', - 'is_static' => true, - 'is_spa' => true, - ], - [ - 'uuid' => 'railpack-qwik-static', - 'name' => 'Railpack Qwik Static Example', - 'base_directory' => '/node/qwik/static', - 'ports_exposes' => '80', - 'build_command' => 'npm run build', - 'publish_directory' => '/dist', - 'is_static' => true, - 'is_spa' => true, - ], - // Multi-language examples on the main branch. - [ - 'uuid' => 'railpack-python-flask', - 'name' => 'Railpack Python Flask Example', - 'base_directory' => '/flask', - 'ports_exposes' => '5000', - 'git_branch' => 'main', - 'start_command' => 'flask run --host=0.0.0.0 --port=5000', - ], - [ - 'uuid' => 'railpack-go-gin', - 'name' => 'Railpack Go Gin Example', - 'base_directory' => '/go/gin', - 'ports_exposes' => '3000', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-rust', - 'name' => 'Railpack Rust Example', - 'base_directory' => '/rust', - 'ports_exposes' => '8000', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-laravel', - 'name' => 'Railpack Laravel Example', - 'base_directory' => '/laravel', - 'ports_exposes' => '80', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-laravel-pure', - 'name' => 'Railpack Laravel Pure Example', - 'base_directory' => '/laravel-pure', - 'ports_exposes' => '80', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-laravel-inertia', - 'name' => 'Railpack Laravel Inertia Example', - 'base_directory' => '/laravel-inertia', - 'ports_exposes' => '80', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-symfony', - 'name' => 'Railpack Symfony Example', - 'base_directory' => '/symfony', - 'ports_exposes' => '80', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-rails', - 'name' => 'Railpack Ruby on Rails Example', - 'base_directory' => '/rails-example', - 'ports_exposes' => '3000', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-elixir-phoenix', - 'name' => 'Railpack Elixir Phoenix Example', - 'base_directory' => '/elixir-phoenix', - 'ports_exposes' => '4000', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-bun', - 'name' => 'Railpack Bun Example', - 'base_directory' => '/bun', - 'ports_exposes' => '3000', - 'git_branch' => 'main', - ], - [ - 'uuid' => 'railpack-github-deploy-key', - 'name' => 'Railpack GitHub Deploy Key Example', - 'git_repository' => 'git@github.com:coollabsio/coolify-examples-deploy-key.git', - 'git_branch' => 'main', - 'ports_exposes' => '80', - 'private_key_id' => 1, - ], - [ - 'uuid' => 'railpack-gitlab-deploy-key', - 'name' => 'Railpack GitLab Deploy Key Example', - 'git_repository' => 'git@gitlab.com:coollabsio/php-example.git', - 'git_branch' => 'main', - 'ports_exposes' => '80', - 'source_id' => 1, - 'source_type' => GitlabApp::class, - 'private_key_id' => 1, - ], - [ - 'uuid' => 'railpack-gitlab-public-example', - 'name' => 'Railpack GitLab Public Example', - 'git_repository' => 'https://gitlab.com/andrasbacsai/coolify-examples.git', - 'git_branch' => 'main', - 'base_directory' => '/astro/static', - 'publish_directory' => '/dist', - 'ports_exposes' => '80', - 'source_id' => 1, - 'source_type' => GitlabApp::class, - 'is_static' => true, - ], - ]; - } - - private function ensureDevelopmentPrerequisitesExist(): void - { - Team::query()->firstOrCreate( - ['id' => 0], - [ - 'name' => 'Root Team', - 'description' => 'The root team', - 'personal_team' => true, - ], - ); - - PrivateKey::query()->firstOrCreate( - ['id' => 1], - [ - 'uuid' => 'ssh', - 'team_id' => 0, - 'name' => 'Testing Host Key', - 'description' => 'This is a test docker container', - 'private_key' => <<<'KEY' ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW -QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk -hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA -AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV -uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== ------END OPENSSH PRIVATE KEY----- -KEY, - ], - ); - - Server::query()->firstOrCreate( - ['id' => 0], - [ - 'uuid' => 'localhost', - 'name' => 'localhost', - 'description' => 'This is a test docker container in development mode', - 'ip' => 'coolify-testing-host', - 'team_id' => 0, - 'private_key_id' => 1, - 'proxy' => [ - 'type' => ProxyTypes::TRAEFIK->value, - 'status' => ProxyStatus::EXITED->value, - ], - ], - ); - - StandaloneDocker::query()->firstOrCreate( - ['id' => 0], - [ - 'uuid' => 'docker', - 'name' => 'Standalone Docker 1', - 'network' => 'coolify', - 'server_id' => 0, - ], - ); - - $this->ensurePublicGithubSourceExists(); - $this->ensurePublicGitlabSourceExists(); - } - - private function ensurePublicGithubSourceExists(): void - { - GithubApp::query()->firstOrCreate( - ['id' => 0], - [ - 'uuid' => 'github-public', - 'name' => 'Public GitHub', - 'api_url' => 'https://api.github.com', - 'html_url' => 'https://github.com', - 'is_public' => true, - 'team_id' => 0, - ], - ); - } - - private function ensurePublicGitlabSourceExists(): void - { - GitlabApp::query()->firstOrCreate( - ['id' => 1], - [ - 'uuid' => 'gitlab-public', - 'name' => 'Public GitLab', - 'api_url' => 'https://gitlab.com/api/v4', - 'html_url' => 'https://gitlab.com', - 'is_public' => true, - 'team_id' => 0, - ], - ); - } - - private function isDevelopmentEnvironment(): bool - { - return in_array(config('app.env'), ['local', 'development', 'dev'], true); - } - - private function cleanupLegacyLimaProjects(): void - { - Project::query() - ->whereIn('uuid', [ - 'railpack-examples-lima-ubuntu-2404', - 'railpack-examples-lima-ubuntu-2604', - ]) - ->get() - ->each(function (Project $project): void { - Application::withTrashed() - ->whereIn('environment_id', $project->environments()->pluck('id')) - ->get() - ->each - ->forceDelete(); - - $project->delete(); - }); - } - - private function cleanupLegacyProductionExamples(): void - { - $project = Project::query()->where('uuid', self::PROJECT_UUID)->first(); - - if (! $project) { - return; - } - - Application::withTrashed() - ->whereIn('environment_id', $project->environments()->pluck('id')) - ->whereIn('uuid', collect(self::examples())->pluck('uuid')) - ->get() - ->each - ->forceDelete(); - } - - private function seedEnvironment(StandaloneDocker $destination): void - { - $environment = $this->prepareEnvironment(); - - foreach (self::examples() as $example) { - $this->upsertApplication($environment, $destination, $example); - } - } - - private function prepareEnvironment(): Environment - { - $project = Project::query()->firstOrNew(['uuid' => self::PROJECT_UUID]); - $project->fill([ - 'name' => 'Railpack Examples', - 'description' => 'Development-only Railpack examples from coollabsio/coolify-examples@next.', - 'team_id' => 0, - ]); - $project->save(); - - $environment = $project->environments()->firstOrCreate(['name' => 'production']); - - $project->environments() - ->whereKeyNot($environment->id) - ->get() - ->each(function (Environment $obsoleteEnvironment): void { - Application::withTrashed() - ->where('environment_id', $obsoleteEnvironment->id) - ->get() - ->each - ->forceDelete(); - - $obsoleteEnvironment->delete(); - }); - - return $environment; - } - - /** - * @param array $example - */ - private function upsertApplication(Environment $environment, StandaloneDocker $destination, array $example): void - { - $uuid = $example['uuid']; - $name = $example['name']; - $application = Application::withTrashed()->firstOrNew(['uuid' => $uuid]); - $application->fill([ - 'name' => $name, - 'description' => $name, - 'fqdn' => "http://{$uuid}.127.0.0.1.sslip.io", - 'repository_project_id' => $example['repository_project_id'] ?? self::REPOSITORY_PROJECT_ID, - 'git_repository' => $example['git_repository'] ?? self::GIT_REPOSITORY, - 'git_branch' => $example['git_branch'] ?? self::GIT_BRANCH, - 'build_pack' => 'railpack', - 'ports_exposes' => $example['ports_exposes'], - 'base_directory' => $example['base_directory'] ?? '/', - 'publish_directory' => $example['publish_directory'] ?? null, - 'static_image' => 'nginx:alpine', - 'install_command' => $example['install_command'] ?? null, - 'build_command' => $example['build_command'] ?? null, - 'start_command' => $example['start_command'] ?? null, - 'environment_id' => $environment->id, - 'destination_id' => $destination->id, - 'destination_type' => StandaloneDocker::class, - 'source_id' => $example['source_id'] ?? 0, - 'source_type' => $example['source_type'] ?? GithubApp::class, - 'private_key_id' => $example['private_key_id'] ?? null, - ]); - $application->save(); - - if ($application->trashed()) { - $application->restore(); - } - - $application->settings()->updateOrCreate( - ['application_id' => $application->id], - [ - 'is_static' => $example['is_static'] ?? false, - 'is_spa' => $example['is_spa'] ?? false, - ], - ); - } -} diff --git a/database/seeders/ServerSettingSeeder.php b/database/seeders/ServerSettingSeeder.php index af4a2694b6..5e9c999f21 100644 --- a/database/seeders/ServerSettingSeeder.php +++ b/database/seeders/ServerSettingSeeder.php @@ -4,6 +4,7 @@ namespace Database\Seeders; use App\Models\Server; use Illuminate\Database\Seeder; +use InvalidArgumentException; class ServerSettingSeeder extends Seeder { @@ -13,10 +14,24 @@ class ServerSettingSeeder extends Seeder public function run(): void { $server_2 = Server::find(0)->load(['settings']); - $server_2->settings->wildcard_domain = 'http://127.0.0.1.sslip.io'; + $server_2->settings->wildcard_domain = 'http://'.self::sslipHost(); $server_2->settings->is_build_server = false; $server_2->settings->is_usable = true; $server_2->settings->is_reachable = true; $server_2->settings->save(); } + + /** + * The sslip.io host for seeded domains, based on DEV_SEED_IP (default 127.0.0.1). + */ + public static function sslipHost(): string + { + $ip = config('constants.coolify.dev_seed_ip'); + + if (! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + throw new InvalidArgumentException("DEV_SEED_IP must be an IPv4 address, got: {$ip}"); + } + + return "{$ip}.sslip.io"; + } } diff --git a/scripts/railpack-smoke.sh b/scripts/railpack-smoke.sh index cfcc311a3f..cb281c32f0 100755 --- a/scripts/railpack-smoke.sh +++ b/scripts/railpack-smoke.sh @@ -2,22 +2,22 @@ # # Railpack end-to-end deploy smoke test against the local dev stack. # -# Walks a curated set of railpack-* example apps from -# DevelopmentRailpackExamplesSeeder, triggers a deploy via the Coolify API, +# Walks a curated set of Railpack example apps from +# ApplicationSeeder, triggers a deploy via the Coolify API, # waits for the deployment queue to finish, then exec()s into the resulting # container and checks that COOLIFY_*, SOURCE_COMMIT, and any RAILPACK_* # build inputs landed correctly. Optionally curls the FQDN. # # Requires: # - Dev stack running: docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d -# - Seeder run: php artisan db:seed --class=DevelopmentRailpackExamplesSeeder +# - Seeder run: php artisan migrate:fresh --seed # - Personal token: PersonalAccessTokenSeeder run (creates Bearer 'root') # - jq, curl available on host # # Usage: # scripts/railpack-smoke.sh # default subset -# scripts/railpack-smoke.sh --app railpack-laravel # single app -# scripts/railpack-smoke.sh --all # every seeded railpack-* app +# scripts/railpack-smoke.sh --app laravel # single app +# scripts/railpack-smoke.sh --all # every seeded Railpack app # scripts/railpack-smoke.sh --timeout 900 # build wait per app, seconds # scripts/railpack-smoke.sh --no-curl # skip FQDN curl # scripts/railpack-smoke.sh --extra-env KEY=VALUE # build+runtime env (alias of --both-env) @@ -37,16 +37,16 @@ BOTH_ENVS=() APPS=() DEFAULT_APPS=( - railpack-expressjs - railpack-nestjs - railpack-nextjs-ssr - railpack-vite-static - railpack-astro-static - railpack-python-flask - railpack-go-gin - railpack-rust - railpack-symfony - railpack-bun + nodejs + nestjs + nextjs-ssr + vite-vanilla-js + astro-static + python-flask + go-gin + rust-rocket + symfony + bun ) while (( $# > 0 )); do @@ -87,7 +87,7 @@ if (( ${#APPS[@]} == 0 )); then fi if [[ "${APPS[0]}" == "__ALL__" ]]; then - mapfile -t APPS < <(curl_api GET /applications | jq -r '.[].uuid' | grep '^railpack-' || true) + mapfile -t APPS < <(curl_api GET /applications | jq -r '.[] | select(.build_pack == "railpack") | .uuid' || true) fi log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; } diff --git a/tests/Feature/ApplicationSeederTest.php b/tests/Feature/ApplicationSeederTest.php index 9f347c1bc0..2b551b410f 100644 --- a/tests/Feature/ApplicationSeederTest.php +++ b/tests/Feature/ApplicationSeederTest.php @@ -1,11 +1,16 @@ seed([ +function seedApplicationSeederPrerequisites(): void +{ + test()->seed([ UserSeeder::class, TeamSeeder::class, PrivateKeySeeder::class, @@ -22,22 +28,65 @@ it('seeds the default applications without railpack examples', function () { ProjectSeeder::class, StandaloneDockerSeeder::class, GithubAppSeeder::class, - ApplicationSeeder::class, + GitlabAppSeeder::class, ]); +} - $nixpacksExample = Application::where('uuid', 'nodejs')->first(); - - expect($nixpacksExample) - ->not->toBeNull() - ->and($nixpacksExample->name)->toBe('NodeJS Fastify Example') - ->and($nixpacksExample->build_pack)->toBe('nixpacks') - ->and($nixpacksExample->base_directory)->toBe('/nodejs') - ->and($nixpacksExample->ports_exposes)->toBe('3000'); - - expect(Application::query()->where('build_pack', 'railpack')->exists())->toBeFalse(); - expect(Application::query()->whereIn('uuid', ['railpack-nodejs', 'railpack-static'])->exists())->toBeFalse(); - - expect(Application::query() - ->whereIn('git_repository', ['coollabsio/coolify-examples', 'coollabsio/coolify']) - ->pluck('git_branch')->unique()->all())->toBe(['main']); +beforeEach(function () { + config()->set('constants.coolify.dev_seed_ip', '127.0.0.1'); }); + +it('seeds every git example as railpack in the default project', function () { + seedApplicationSeederPrerequisites(); + $this->seed(ApplicationSeeder::class); + + $railpackUuids = collect(ApplicationSeeder::railpackExamples())->pluck('uuid')->sort()->values()->all(); + + expect(Application::query()->where('build_pack', 'railpack')->pluck('uuid')->sort()->values()->all())->toBe($railpackUuids) + ->and(Application::query()->whereNotIn('build_pack', ['railpack', 'dockerfile', 'dockercompose'])->exists())->toBeFalse() + ->and(Project::query()->count())->toBe(1) + ->and(Application::query()->pluck('environment_id')->unique()->all())->toBe([1]) + ->and(Application::query()->whereNotNull('git_branch')->pluck('git_branch')->unique()->all())->toBe(['main']); +}); + +it('seeds railpack examples with the new example repository layout', function () { + seedApplicationSeederPrerequisites(); + $this->seed(ApplicationSeeder::class); + + $nodejs = Application::query()->where('uuid', 'nodejs')->sole(); + $astroServer = Application::query()->where('uuid', 'astro-server')->sole(); + $vueSpa = Application::query()->where('uuid', 'vue-spa')->with('settings')->sole(); + $gitlabPublic = Application::query()->where('uuid', 'gitlab-public-example')->with('settings')->sole(); + $compose = Application::query()->where('uuid', 'docker-compose')->sole(); + + expect($nodejs->git_repository)->toBe(ApplicationSeeder::EXAMPLES_REPOSITORY) + ->and($nodejs->base_directory)->toBe('/javascript/runtimes/nodejs') + ->and($nodejs->fqdn)->toBe('http://nodejs.127.0.0.1.sslip.io') + ->and($astroServer->start_command)->toBe('HOST=0.0.0.0 node dist/server/entry.mjs') + ->and($vueSpa->publish_directory)->toBe('/dist') + ->and($vueSpa->settings->is_static)->toBeTrue() + ->and($vueSpa->settings->is_spa)->toBeTrue() + ->and($gitlabPublic->source_type)->toBe(GitlabApp::class) + ->and($gitlabPublic->repository_project_id)->toBeNull() + ->and($gitlabPublic->settings->is_static)->toBeTrue() + ->and($compose->base_directory)->toBe('/internal/compose'); +}); + +it('uses DEV_SEED_IP for the server wildcard domain and application domains', function () { + config()->set('constants.coolify.dev_seed_ip', '100.64.1.2'); + + seedApplicationSeederPrerequisites(); + $this->seed([ServerSettingSeeder::class, ApplicationSeeder::class]); + + expect(Server::query()->find(0)->settings->wildcard_domain)->toBe('http://100.64.1.2.sslip.io') + ->and(Application::query()->where('uuid', 'nodejs')->value('fqdn'))->toBe('http://nodejs.100.64.1.2.sslip.io') + ->and(Application::query()->where('uuid', 'dockerfile')->value('fqdn'))->toBe('http://dockerfile.100.64.1.2.sslip.io') + ->and(Application::query()->whereNotNull('fqdn')->where('fqdn', 'not like', '%.100.64.1.2.sslip.io')->exists())->toBeFalse(); +}); + +it('rejects a DEV_SEED_IP that is not an IPv4 address', function (string $value) { + config()->set('constants.coolify.dev_seed_ip', $value); + + ServerSettingSeeder::sslipHost(); +})->with(['hostname' => 'devserver.ts.net', 'ipv6' => 'fd7a:115c:a1e0::1', 'injection' => '1.2.3.4; rm -rf /']) + ->throws(InvalidArgumentException::class); diff --git a/tests/Feature/DevEnvironmentExampleTest.php b/tests/Feature/DevEnvironmentExampleTest.php deleted file mode 100644 index a4ef6ee86f..0000000000 --- a/tests/Feature/DevEnvironmentExampleTest.php +++ /dev/null @@ -1,9 +0,0 @@ -after("if (in_array(config('app.env'), ['local', 'development', 'dev'], true)) {")->before(' }')->toString(); - - expect($developmentSeederBlock)->toContain('DevelopmentRailpackExamplesSeeder::class') - ->and($developmentSeederBlock)->not->toContain('V5DevLimaSeeder::class'); -}); diff --git a/tests/Feature/DevelopmentRailpackExamplesSeederTest.php b/tests/Feature/DevelopmentRailpackExamplesSeederTest.php deleted file mode 100644 index a54fddd66f..0000000000 --- a/tests/Feature/DevelopmentRailpackExamplesSeederTest.php +++ /dev/null @@ -1,136 +0,0 @@ -seed([ - UserSeeder::class, - TeamSeeder::class, - PrivateKeySeeder::class, - ServerSeeder::class, - ProjectSeeder::class, - StandaloneDockerSeeder::class, - GithubAppSeeder::class, - ]); -} - -it('can seed the railpack examples directly on a clean development database', function () { - config()->set('app.env', 'local'); - - $this->seed(DevelopmentRailpackExamplesSeeder::class); - - expect(Team::query()->find(0))->not->toBeNull() - ->and(PrivateKey::query()->find(1))->not->toBeNull() - ->and(Server::query()->count())->toBe(1) - ->and(Server::query()->find(0)?->uuid)->toBe('localhost') - ->and(StandaloneDocker::query()->find(0))->not->toBeNull() - ->and(GithubApp::query()->find(0))->not->toBeNull() - ->and(GitlabApp::query()->find(1))->not->toBeNull() - ->and(Application::query()->count())->toBe(count(DevelopmentRailpackExamplesSeeder::examples())); - - $project = Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->firstOrFail(); - - expect($project->environments)->toHaveCount(1) - ->and($project->environments->first()->name)->toBe('production') - ->and($project->applications()->whereRelation('destination.server', 'uuid', 'localhost')->count()) - ->toBe(count(DevelopmentRailpackExamplesSeeder::examples())); -}); - -it('seeds every railpack example in the production environment on testing-host', function () { - config()->set('app.env', 'local'); - - seedRailpackExamplePrerequisites(); - $this->seed(DevelopmentRailpackExamplesSeeder::class); - - $project = Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->firstOrFail(); - $environment = $project->environments()->sole(); - $applications = $environment->applications()->with('settings', 'destination.server')->orderBy('uuid')->get(); - - expect($environment->name)->toBe('production') - ->and($applications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples())) - ->and($applications->every(fn (Application $application) => $application->build_pack === 'railpack'))->toBeTrue() - ->and($applications->every(fn (Application $application) => $application->destination->server->uuid === 'localhost'))->toBeTrue() - ->and($applications->pluck('uuid')->sort()->values()->all()) - ->toBe(collect(DevelopmentRailpackExamplesSeeder::examples())->pluck('uuid')->sort()->values()->all()); - - $nestjs = $applications->firstWhere('uuid', 'railpack-nestjs'); - $angularStatic = $applications->firstWhere('uuid', 'railpack-angular-static'); - $githubDeployKey = $applications->firstWhere('uuid', 'railpack-github-deploy-key'); - $gitlabDeployKey = $applications->firstWhere('uuid', 'railpack-gitlab-deploy-key'); - - expect($applications->where('git_repository', DevelopmentRailpackExamplesSeeder::GIT_REPOSITORY) - ->pluck('git_branch')->unique()->sort()->values()->all())->toBe(['main', 'next']) - ->and($applications->firstWhere('uuid', 'railpack-python-flask')->git_branch)->toBe('main') - ->and($nestjs->git_branch)->toBe('next'); - - expect($nestjs->base_directory)->toBe('/node/nestjs') - ->and($nestjs->build_command)->toBe('npm run build') - ->and($nestjs->start_command)->toBe('npm run start:prod') - ->and($angularStatic->publish_directory)->toBe('/dist/static/browser') - ->and($angularStatic->settings->is_static)->toBeTrue() - ->and($githubDeployKey->private_key_id)->toBe(1) - ->and($githubDeployKey->source_type)->toBe(GithubApp::class) - ->and($gitlabDeployKey->source_type)->toBe(GitlabApp::class); -}); - -it('consolidates legacy railpack environments into production', function () { - config()->set('app.env', 'local'); - - seedRailpackExamplePrerequisites(); - $project = Project::query()->create([ - 'uuid' => DevelopmentRailpackExamplesSeeder::PROJECT_UUID, - 'name' => 'Railpack Examples', - 'team_id' => 0, - ]); - $project->environments()->first()->update(['name' => 'ubuntu24', 'uuid' => 'railpack-examples-ubuntu24']); - $project->environments()->create(['name' => 'ubuntu26', 'uuid' => 'railpack-examples-ubuntu26']); - - $this->seed(DevelopmentRailpackExamplesSeeder::class); - - $project->refresh(); - - expect($project->environments)->toHaveCount(1) - ->and($project->environments->first()->name)->toBe('production') - ->and($project->applications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples())); -}); - -it('skips the railpack examples outside development mode', function () { - config()->set('app.env', 'testing'); - - seedRailpackExamplePrerequisites(); - $this->seed(DevelopmentRailpackExamplesSeeder::class); - - expect(Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->exists())->toBeFalse(); -}); - -it('is idempotent when run multiple times', function () { - config()->set('app.env', 'local'); - - seedRailpackExamplePrerequisites(); - $this->seed(DevelopmentRailpackExamplesSeeder::class); - $this->seed(DevelopmentRailpackExamplesSeeder::class); - - $project = Project::query()->where('uuid', DevelopmentRailpackExamplesSeeder::PROJECT_UUID)->firstOrFail(); - - expect($project->environments)->toHaveCount(1) - ->and($project->applications)->toHaveCount(count(DevelopmentRailpackExamplesSeeder::examples())); -});