Skip to content

Conversation

@Boy132
Copy link
Member

@Boy132 Boy132 commented Sep 4, 2025

Closes #1655

Example with Garry's Mod egg:
Egg:
grafik

Create Server:
grafik

Edit Server:
grafik

Client Area:
grafik

Activity log when user changes startup
grafik

Shown changes to the gmod egg are not included!

@Boy132 Boy132 self-assigned this Sep 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 4, 2025

📝 Walkthrough

Walkthrough

Replaces the single egg startup string with a startup_commands map across models, migrations, seeders, forms, services, transformers, and UI; adds select/custom startup UX and preview handling; migrates persisted data and updates import/export, factories, and translations.

Changes

Cohort / File(s) Summary
Model & Persistence
app/Models/Egg.php, database/migrations/2025_09_03_090706_support_multiple_startup_commands.php, database/Factories/EggFactory.php
Replace startup (string) with startup_commands (array) in docblocks, $fillable, casts and validation; factory emits an array; migration adds startup_commands populated from existing startup and drops/restores columns on down().
Admin Egg forms
app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php, .../EditEgg.php
Swap Textarea::make('startup') for KeyValue::make('startup_commands') (live, required, per-entry key/value labels/placeholders; helper text preserved).
Admin Server forms
app/Filament/Admin/Resources/Servers/Pages/CreateServer.php, .../EditServer.php
Add select_startup Select sourced from egg startup_commands with a "Custom Startup" option; keep startup textarea (live/autosize/required); implement two‑way sync; add Hidden::make('previewing') in edit.
Server UI & Preview
app/Filament/Components/Actions/PreviewStartupAction.php, app/Filament/Server/Pages/Startup.php
Preview action label switched to dynamic setup; startup UI split into select vs custom modes; use Set in afterStateUpdated; log activity and send notifications on changes; expose preview textarea.
Startup-related services
app/Services/Servers/StartupCommandService.php, StartupModificationService.php, ServerCreationService.php
StartupCommandService::handle signature extended to accept optional $startup; services default to Arr::first($egg->startup_commands) and use Arr::first / null-coalescing assignment for defaults.
Egg services: change, import/export
app/Services/Eggs/EggChangerService.php, .../EggExporterService.php, .../EggImporterService.php
Replace startup usages with startup_commands (exporter emits startup_commands); importer adds convertLegacy and convertToV3 to normalize legacy formats into v3 and fills model using startup_commands.
Transformers & API
app/Transformers/Api/Application/EggTransformer.php, app/Transformers/Api/Client/ServerTransformer.php
EggTransformer exposes startup_commands and derives deprecated startup from its first value; ServerTransformer calls StartupCommandService::handle using a named hideAllValues argument.
Seeder YAMLs (eggs)
database/Seeders/eggs/**/*.yaml (e.g., minecraft/*, rust/*, source-engine/*, voice-servers/*)
Bump meta.version PLCN_v2→PLCN_v3, update exported_at timestamps, migrate startup string → startup_commands mapping with Default key; some seeders also update docker_images/metadata/config.
Localization
lang/en/activity.php, lang/en/admin/egg.php, lang/en/admin/server.php, lang/en/server/startup.php
Add/rename translation keys for multi-startup UI (startup_commands, add_startup, startup_name, startup_command, startup_placeholder, activity messages, preview enable/disable and notifications).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as Filament UI
  participant API as Backend
  participant DB as Database
  Note over UI: User selects Egg → UI loads egg.startup_commands
  User->>UI: choose Egg
  UI->>API: GET /egg (includes `startup_commands`)
  API->>DB: SELECT egg.startup_commands
  DB-->>API: return startup_commands
  API-->>UI: return options list
  Note over UI: User selects predefined startup or types custom
  User->>UI: select_startup / edit startup textarea
  UI-->>UI: sync select_startup ↔ startup textarea
  Note over UI,API: Persist change
  UI->>API: PATCH server.startup
  API->>API: StartupCommandService.handle(server, startup)
  API->>DB: UPDATE servers.startup
  DB-->>API: updated
  API-->>User: emit activity + send notification
Loading
sequenceDiagram
  participant User
  participant Preview as PreviewStartupAction
  participant Store as Filament Store
  participant Service as StartupCommandService
  User->>Preview: click Preview
  Preview->>Store: get('startup')
  Store-->>Preview: current startup
  Preview->>Service: handle(server, startup)
  Service-->>Preview: rendered preview
  Preview-->>User: display preview toggle/result
Loading

Possibly related PRs

Pre-merge checks

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning This pull request also introduces unrelated modifications in several YAML seeders, including adding new metadata fields (description, tags, features), expanding docker_images with extra Java runtimes, and inserting server.properties parser configuration in egg-paper.yaml, which are not part of the multiple startup commands objective. These enhancements extend beyond the scope defined by issue #1655 and could distract from the intended feature implementation. Maintaining focus on startup_commands functionality would improve clarity and reviewability. Please remove or split out the unrelated seeder enhancements into a separate pull request so that this change set remains focused on adding and supporting multiple startup commands.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarizes the primary change of the pull request by stating that eggs can now support multiple startup commands, matching the core feature implemented in the changeset. It is specific to the main functional enhancement without extraneous detail. The phrasing is succinct and understandable in isolation.
Linked Issues Check ✅ Passed The pull request fully implements the objectives of issue #1655 by replacing the single startup field with a JSON-like startup_commands map, updating the database schema and model casts, providing KeyValue input for eggs, exposing a Select control in server creation and editing flows, and preserving the ability to choose a default or custom startup command. Supporting services, transformers, and migrations have been updated to use startup_commands throughout the codebase. These changes align directly with the requirement to support multiple named startup commands per egg.
Description Check ✅ Passed The description provides relevant context by showing example screenshots for creating and editing servers with the new multi-startup command feature and references the linked issue #1655, making it clearly related to the changeset. It does not include unrelated or off-topic information. The detail level is sufficient to understand the user-facing impact without being generic or vague.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Boy132 Boy132 marked this pull request as ready for review September 12, 2025 08:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (18)
lang/en/activity.php (1)

115-116: Make quoting consistent within the startup group.

Currently, only :new is quoted. Quote both values (like the nearby "edit" entry) or neither. Suggest quoting both for clarity.

-            'command' => 'Updated the Startup Command for the server from <b>:old</b> to <b>:new</b>',
+            'command' => 'Updated the Startup Command for the server from "<b>:old</b>" to "<b>:new</b>"',
app/Services/Servers/StartupModificationService.php (1)

88-90: Prefer explicit "Default" startup/image when present; fall back safely.

Associative order can vary. Use the "Default" key when available, then first value, then current server values.

-            $data['docker_image'] ??= Arr::first($egg->docker_images);
-            $data['startup'] ??= Arr::first($egg->startup_commands);
+            $data['docker_image'] ??= Arr::get($egg->docker_images, 'Default')
+                ?? Arr::first($egg->docker_images);
+            $data['startup'] ??= Arr::get($egg->startup_commands, 'Default')
+                ?? Arr::first($egg->startup_commands)
+                ?? $server->startup;

Minor: use the already-normalized $eggId variable above instead of $data['egg_id'] for consistency.

database/Seeders/eggs/rust/egg-rust.yaml (1)

22-23: Complex command string: validate escaping.

The single-quoted YAML with embedded " and command substitution looks valid, but easy to regress. Consider adding a seed parse/smoke test to catch quoting errors.

Would you like a tiny CI check that parses all egg YAML and asserts startup_commands contain non-empty strings?

database/Seeders/eggs/source-engine/egg-garrys-mod.yaml (1)

21-22: Prefer POSIX-compatible test operator in embedded shell.

Use single = for broader shell compatibility (dash/ash), since the startup is executed under sh -c in many images.

Apply this diff in the exporter so generated seeds use POSIX =:

-  Default: './srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ "$LUA_REFRESH" == "1" ] || printf %s ''-disableluarefresh'' )'
+  Default: './srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}}  -tickrate {{TICKRATE}}  $( [ "$LUA_REFRESH" = "1" ] || printf %s ''-disableluarefresh'' )'
database/Seeders/eggs/minecraft/egg-vanilla-minecraft.yaml (1)

27-28: Consider appending 'nogui' to the default Java invocation.

Prevents attempting to launch a GUI and matches common vanilla server practice.

-  Default: 'java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}'
+  Default: 'java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}} nogui'
database/Factories/EggFactory.php (1)

26-34: Use a named map for startup_commands to mirror production seeds/UI.

Factory currently sets a numeric array; switching to a name=>command map helps catch shape issues in tests and aligns with KeyValue UI.

-            'startup_commands' => ['java -jar test.jar'],
+            // Use named commands to mirror production seeds and UI expectations.
+            'startup_commands' => ['Default' => 'java -jar test.jar'],
app/Services/Eggs/EggChangerService.php (1)

25-27: Guard against empty docker_images/startup_commands when switching eggs.

Arr::first can return null; avoid persisting null image/startup or regress to prior values.

-        $server->forceFill([
-            'egg_id' => $newEgg->id,
-            'image' => Arr::first($newEgg->docker_images),
-            'startup' => Arr::first($newEgg->startup_commands),
-        ])->saveOrFail();
+        $defaultImage = Arr::first($newEgg->docker_images) ?? $server->image;
+        $defaultStartup = Arr::first($newEgg->startup_commands) ?? $server->startup;
+        $server->forceFill([
+            'egg_id' => $newEgg->id,
+            'image' => $defaultImage,
+            'startup' => $defaultStartup,
+        ])->saveOrFail();

If you prefer a hard failure instead of fallback, I can wire a DisplayException with a localized message.

app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php (1)

83-91: Switch to KeyValue startup_commands is good; consider UX placeholders

Optional: add key/value placeholders (e.g., "Default" and a typical command) to match CreateEgg for consistency.

app/Transformers/Api/Application/EggTransformer.php (1)

51-52: Transformer defaults — LGTM; tiny comment nit.

Code looks correct. Optional: clarify the inline comments for readability.

Apply this diff to tighten comments:

-            'docker_image' => Arr::first($model->docker_images, default: ''), // docker_images, use startup_commands
+            'docker_image' => Arr::first($model->docker_images, default: ''), // first image is the default
@@
-            'startup' => Arr::first($model->startup_commands, default: ''), // deprecated, use startup_commands
+            'startup' => Arr::first($model->startup_commands, default: ''), // deprecated; prefer startup_commands

Also applies to: 61-63

database/migrations/2025_09_03_090706_support_multiple_startup_commands.php (1)

17-18: Note: Column positioning with after() may fail on SQLite.

If your CI uses SQLite for tests, after() is ignored/unsupported. Not a blocker, but be aware.

Would you like a test-friendly variant that skips after() on SQLite?

Also applies to: 35-36

app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

616-633: Don’t persist select_startup; add dehydrated(false).

select_startup is a helper UI field; prevent accidental persistence/validation.

 Select::make('select_startup')
     ->label(trans('admin/server.startup_cmd'))
     ->live()
     ->afterStateUpdated(fn (Set $set, $state) => $set('startup', $state))
+    ->dehydrated(false)

637-653: Reduce reactive churn on free-typing in startup.

live() + afterStateUpdated runs on every keystroke. Debounce to avoid flicker when syncing select_startup.

-    ->live()
+    ->live(debounce: 500)
app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (3)

323-324: Also reset select_startup when egg changes.

Prevents stale selection before options() re-evaluates.

-    $set('startup', '');
+    $set('startup', '');
+    $set('select_startup', '');

397-417: Do not dehydrate select_startup; UI-only field.

Avoid persisting it to ServerCreationService payload.

 Select::make('select_startup')
     ->label(trans('admin/server.startup_cmd'))
     ->hidden(fn (Get $get) => $get('egg_id') === null)
     ->live()
     ->afterStateUpdated(fn (Set $set, $state) => $set('startup', $state))
+    ->dehydrated(false)

418-436: Debounce live typing to reduce unnecessary recomputations.

Same rationale as EditServer; improves UX on slower devices.

-    ->live()
+    ->live(debounce: 500)
app/Services/Eggs/Sharing/EggImporterService.php (1)

184-187: Reserved env redaction applied to each startup command.

Approach is sound. Minor: consider using preg_quote with delimiter to be explicit.

-                $pattern = '/\b(' . collect($forbidden)->map(fn ($variable) => preg_quote($variable['env_variable']))->join('|') . ')\b/';
+                $pattern = '/\b(' . collect($forbidden)->map(fn ($v) => preg_quote($v['env_variable'], '/'))->join('|') . ')\b/';
app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (1)

86-96: Great UX for multiple commands; add a hint about uniqueness.

Because the server UI flips command values, duplicate command strings will collapse options. Add a brief helper note to warn admins that command values must be unique. Model-side distinct rule recommended in Egg::$validationRules (see other comment).

app/Filament/Server/Pages/Startup.php (1)

45-49: Show a snippet of the custom command instead of a static string.

This improves clarity without exposing a huge command (full command still shown below).

-                TextInput::make('custom_startup')
+                TextInput::make('custom_startup')
                     ->label(trans('server/startup.command'))
-                    ->readOnly()
-                    ->visible(fn (Server $server) => !in_array($server->startup, $server->egg->startup_commands))
-                    ->formatStateUsing(fn () => 'Custom Startup'),
+                    ->readOnly()
+                    ->visible(fn (Server $server) => !in_array($server->startup, $server->egg->startup_commands))
+                    ->formatStateUsing(fn (Server $server) => mb_strimwidth($server->startup ?? '', 0, 120, '…')),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f1ec20 and 0880868.

📒 Files selected for processing (33)
  • app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (2 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1 hunks)
  • app/Filament/Components/Actions/PreviewStartupAction.php (1 hunks)
  • app/Filament/Server/Pages/Startup.php (3 hunks)
  • app/Models/Egg.php (6 hunks)
  • app/Services/Eggs/EggChangerService.php (2 hunks)
  • app/Services/Eggs/Sharing/EggExporterService.php (1 hunks)
  • app/Services/Eggs/Sharing/EggImporterService.php (5 hunks)
  • app/Services/Servers/ServerCreationService.php (1 hunks)
  • app/Services/Servers/StartupCommandService.php (2 hunks)
  • app/Services/Servers/StartupModificationService.php (1 hunks)
  • app/Transformers/Api/Application/EggTransformer.php (2 hunks)
  • app/Transformers/Api/Client/ServerTransformer.php (1 hunks)
  • database/Factories/EggFactory.php (1 hunks)
  • database/Seeders/eggs/minecraft/egg-bungeecord.yaml (2 hunks)
  • database/Seeders/eggs/minecraft/egg-forge-minecraft.yaml (2 hunks)
  • database/Seeders/eggs/minecraft/egg-paper.yaml (2 hunks)
  • database/Seeders/eggs/minecraft/egg-sponge.yaml (2 hunks)
  • database/Seeders/eggs/minecraft/egg-vanilla-minecraft.yaml (2 hunks)
  • database/Seeders/eggs/rust/egg-rust.yaml (2 hunks)
  • database/Seeders/eggs/source-engine/egg-custom-source-engine-game.yaml (2 hunks)
  • database/Seeders/eggs/source-engine/egg-garrys-mod.yaml (2 hunks)
  • database/Seeders/eggs/source-engine/egg-insurgency.yaml (2 hunks)
  • database/Seeders/eggs/source-engine/egg-team-fortress2.yaml (2 hunks)
  • database/Seeders/eggs/voice-servers/egg-mumble-server.yaml (2 hunks)
  • database/Seeders/eggs/voice-servers/egg-teamspeak3-server.yaml (2 hunks)
  • database/migrations/2025_09_03_090706_support_multiple_startup_commands.php (1 hunks)
  • lang/en/activity.php (1 hunks)
  • lang/en/admin/egg.php (2 hunks)
  • lang/en/admin/server.php (1 hunks)
  • lang/en/server/startup.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/Services/Servers/StartupCommandService.php (5)
app/Services/Servers/ServerCreationService.php (1)
  • handle (54-134)
app/Services/Servers/StartupModificationService.php (1)
  • handle (30-64)
app/Services/Servers/VariableValidatorService.php (1)
  • handle (25-56)
app/Services/Servers/ServerConfigurationStructureService.php (1)
  • handle (23-36)
app/Models/Server.php (1)
  • Server (132-504)
app/Services/Servers/StartupModificationService.php (1)
app/Models/Server.php (1)
  • egg (295-298)
app/Transformers/Api/Client/ServerTransformer.php (1)
app/Services/Servers/ServerConfigurationStructureService.php (1)
  • handle (23-36)
app/Services/Eggs/Sharing/EggImporterService.php (1)
app/Models/Egg.php (1)
  • Egg (59-329)
app/Filament/Server/Pages/Startup.php (5)
app/Models/Server.php (2)
  • Server (132-504)
  • egg (295-298)
app/Models/User.php (1)
  • can (360-373)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Facades/Activity.php (1)
  • Activity (8-14)
app/Services/Activity/ActivityLogService.php (3)
  • event (48-53)
  • property (109-117)
  • log (136-155)
🪛 YAMLlint (1.37.1)
database/Seeders/eggs/source-engine/egg-garrys-mod.yaml

[warning] 20-20: too many spaces inside empty braces

(braces)

🔇 Additional comments (40)
lang/en/admin/server.php (1)

29-31: Translations added look good; align with UI usage.

Keys read clearly and match existing naming (e.g., image_placeholder). No issues.

database/Seeders/eggs/minecraft/egg-bungeecord.yaml (2)

3-5: Version/export timestamp bump to PLCN_v3 is fine.

Matches the schema shift to startup_commands.


29-30: startup_commands migration looks correct.

Default command preserved verbatim. No YAML quoting issues spotted.

database/Seeders/eggs/voice-servers/egg-mumble-server.yaml (2)

3-5: PLCN_v3/version bump is consistent with the migration.


18-19: startup_commands Default preserved; looks good.

database/Seeders/eggs/rust/egg-rust.yaml (2)

3-5: PLCN_v3/version bump OK.


20-20: Labeling docker image as "Rust" improves UX.

No functional concerns.

database/Seeders/eggs/voice-servers/egg-teamspeak3-server.yaml (2)

3-5: PLCN_v3/version bump OK.


18-19: startup_commands migration looks correct.

Default command matches prior scalar.

database/Seeders/eggs/minecraft/egg-sponge.yaml (1)

23-24: startup_commands Default retained; looks good.

database/Seeders/eggs/source-engine/egg-garrys-mod.yaml (2)

3-5: Exporter version bump looks consistent with schema migration.

PLCN_v3 and updated exported_at align with the startup_commands migration. No action needed.


19-19: docker_images mapping change is OK.

Switching to a labeled map (Source => image) works with Arr::first usage downstream.

database/Seeders/eggs/source-engine/egg-team-fortress2.yaml (2)

3-5: v3 export + docker_images map LGTM.

Matches model changes and Arr::first usage.

Also applies to: 19-19


21-22: startup_commands migration looks correct.

Default command preserved; no issues spotted.

database/Seeders/eggs/minecraft/egg-vanilla-minecraft.yaml (1)

3-5: PLCN_v3 export metadata is consistent.

No action needed.

app/Transformers/Api/Client/ServerTransformer.php (1)

63-64: Approve — named-argument usage is correct.

Signature present: app/Services/Servers/StartupCommandService.php:12 — public function handle(Server $server, ?string $startup = null, bool $hideAllValues = false): string. Using hideAllValues by name is safe and future‑proof.

lang/en/server/startup.php (1)

6-7: New notification strings read well.

Clear and consistent with existing Docker image notifications.

app/Services/Eggs/Sharing/EggExporterService.php (1)

36-36: Export field rename to startup_commands — LGTM

The exporter now emits startup_commands from the model. Looks correct and consistent with the migration.

database/Seeders/eggs/minecraft/egg-forge-minecraft.yaml (1)

3-5: Seed migrated to PLCN_v3 with startup_commands mapping — LGTM

Version bump and Default startup command mapping look correct.

Also applies to: 25-26

lang/en/admin/egg.php (1)

40-45: New startup_commands strings — LGTM

Wording is clear and matches behavior (first entry is default).

Also applies to: 57-57

app/Services/Servers/StartupCommandService.php (1)

12-29: Make allocation access null-safe; callers verified

  • Apply the nullsafe change in StartupCommandService (diff below).
  • I searched the repo: no positional boolean second-arg calls to StartupCommandService->handle were found. Call sites checked: app/Transformers/Api/Client/ServerTransformer.php (uses named param), app/Filament/Components/Actions/PreviewStartupAction.php (passes $startup string), app/Http/Controllers/Api/Client/Servers/StartupController.php (single-arg). No signature-breaking callers detected.
-            $server->allocation->ip ?? '127.0.0.1',
-            (string) ($server->allocation->port ?? '0'),
+            $server->allocation?->ip ?? '127.0.0.1',
+            (string) ($server->allocation?->port ?? '0'),
database/Seeders/eggs/minecraft/egg-paper.yaml (1)

3-5: Paper seed migrated to PLCN_v3 with Default startup mapping — LGTM

Matches the new schema and default-selection semantics.

Also applies to: 23-24

database/Seeders/eggs/source-engine/egg-insurgency.yaml (1)

3-3: Seeder aligns with PLCN_v3 and startup_commands. LGTM.

Meta bump, docker_images restructuring, and Default startup_commands mapping look consistent with UI/transformer expectations.

Also applies to: 5-5, 19-19, 21-22

database/Seeders/eggs/source-engine/egg-custom-source-engine-game.yaml (1)

3-3: Seeder migration to startup_commands and named docker image is consistent.

Default startup preserved; docker_images now keyed. Matches UI that flips arrays for labels/values.

Also applies to: 5-5, 18-18, 20-21

app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

631-632: Guard against duplicate startup command strings collapsing options.

array_flip will drop duplicates if two entries share identical command strings.

Are all values in Egg::$startup_commands guaranteed unique per egg? If not, consider returning ['name' => 'command'] and mapping state by name instead of command.


621-622: Ensure Egg::$casts includes 'startup_commands' => 'array'.

Without a cast, $egg->startup_commands may be a JSON string; in_array/array_flip would error.

If missing, add in App\Models\Egg: protected $casts = ['startup_commands' => 'array', 'docker_images' => 'array'];

Also applies to: 644-645

app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (2)

403-414: Potential duplicate-value collapse in array_flip.

Duplicate command strings will be lost.

If duplicates are possible, return options keyed by the command name and set the select’s state to the name, not the command string.


403-405: Confirm model casts for Egg startup_commands/docker_images.

Both components expect arrays; ensure casts exist.

If not already present, add: protected $casts = ['startup_commands' => 'array', 'docker_images' => 'array'];

Also applies to: 426-427

app/Models/Egg.php (4)

25-25: Docblocks updated correctly.

Types for docker_images and startup_commands look right for downstream static analysis.

Also applies to: 34-34


92-92: Fillable updated for startup_commands.

Correct replacement of startup with startup_commands.


146-147: Cast added.

Casting startup_commands to array is correct.


73-73: Update docs/exporters to PLCN_v3; legacy import mappings are present

Importer still matches older versions (app/Services/Eggs/Sharing/EggImporterService.php lines 134–139: 'PTDL_v1','PTDL_v2','PLCN_v1','PLCN_v2' => convertToV3). Egg::EXPORT_VERSION is used for current exports — ensure docs and any external importers/exporters are updated to 'PLCN_v3'.

app/Services/Eggs/Sharing/EggImporterService.php (3)

136-139: Import version routing LGTM.

PTDL_v1 → legacy + v3; PTDL_v2/PLCN_v1/PLCN_v2 → v3; PLCN_v3 passthrough. Good layering.


210-210: Model mapping updated to startup_commands.

Looks correct and matches Egg::$fillable and casts.


221-238: Legacy image conversion rename is fine.

No functional concerns; clear separation from v3 conversion.

app/Filament/Server/Pages/Startup.php (5)

20-20: Set import added.

Good.


39-39: Form columns change.

OK.


81-81: Custom image formatter.

Good: shows actual image.


106-107: No functional change.

Formatting-only diff near options() closing brace.


107-113: Read-only startup textarea with preview.

Nice separation of selection vs. preview.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
database/Seeders/eggs/minecraft/egg-sponge.yaml (1)

98-102: Checksum comparison is incorrect and will always fail.

basename $(md5sum ...) returns the whole "hash filename" string. Compare only the first field (the digest).

-      if [ $(basename $(md5sum ${SERVER_JARFILE})) = ${CHECKSUM} ] ; then
+      if [ "$(md5sum "${SERVER_JARFILE}" | awk '{print $1}')" = "${CHECKSUM}" ] ; then
         echo "Checksum passed"
       else
         echo "Checksum failed"
       fi
🧹 Nitpick comments (1)
database/Seeders/eggs/minecraft/egg-sponge.yaml (1)

3-5: Approve — update_url is valid; optional: prefer canonical raw host

Both endpoints returned HTTP 200; prefer raw.githubusercontent.com to reduce redirects and improve caching.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0880868 and ec96047.

📒 Files selected for processing (3)
  • app/Models/Egg.php (6 hunks)
  • database/Seeders/eggs/minecraft/egg-sponge.yaml (2 hunks)
  • database/migrations/2025_09_03_090706_support_multiple_startup_commands.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • database/migrations/2025_09_03_090706_support_multiple_startup_commands.php
🔇 Additional comments (6)
app/Models/Egg.php (6)

25-25: Docblock type for docker_images is accurate.

Using array<string, string> better reflects the label=>image mapping.


35-35: Docblock for startup_commands added correctly.

The map of label=>command is clearly typed.


92-92: Mass-assignable startup_commands added.

Necessary for creates/updates via forms and import.


113-115: Validation updated; distinct values prevent UI collisions.

Good catch adding distinct to avoid duplicate command strings collapsing in selection.


146-146: Casts include startup_commands as array.

Ensures consistent serialization.


73-73: Approve: EXPORT_VERSION bumped to PLCN_v3 — no action required

Bump matches YAML meta.version; the only PLCN_v2 occurrence is the backward-compatibility mapping in app/Services/Eggs/Sharing/EggImporterService.php (maps 'PLCN_v2' => $this->convertToV3(...)), so nothing to change.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (3)

86-96: Normalize and validate entries (trim, drop empties, require ≥1).

Prevents blank keys/values and enforces at least one command. Also marks the field as an array for validation.

Apply this diff:

 KeyValue::make('startup_commands')
   ->label(trans('admin/egg.startup_commands'))
-  ->live()
   ->columnSpanFull()
-  ->required()
+  ->rules(['required', 'array', 'min:1'])
+  ->dehydrateStateUsing(fn ($state) => collect($state ?? [])
+      ->mapWithKeys(fn ($v, $k) => [trim((string) $k) => is_string($v) ? trim($v) : $v])
+      ->reject(fn ($v, $k) => $k === '' || $v === '')
+      ->all()
+  )
   ->addActionLabel(trans('admin/egg.add_startup'))
   ->keyLabel(trans('admin/egg.startup_name'))
   ->keyPlaceholder('Default')
   ->valueLabel(trans('admin/egg.startup_command'))
   ->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
   ->helperText(trans('admin/egg.startup_help')),

86-96: Seed a sensible default entry.

Pre-populating a single “Default” row reduces empty-form errors and clarifies intent.

Apply this diff:

   ->keyLabel(trans('admin/egg.startup_name'))
   ->keyPlaceholder('Default')
   ->valueLabel(trans('admin/egg.startup_command'))
   ->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
+  ->default(['Default' => ''])
   ->helperText(trans('admin/egg.startup_help')),

86-96: Drop live() unless something else reacts to this field.

->live() can cause unnecessary re-renders; remove if no reactive dependencies rely on this state.

-  ->live()
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)

617-636: Consider handling edge cases in startup command selection logic.

The implementation handles selection and synchronization well, but there are a few edge cases to consider:

  1. When no egg is found ($egg is null), accessing $egg->startup_commands could cause an error
  2. The empty string key ('') for "Custom Startup" could be confusing; consider using a more descriptive constant

Consider adding null checks and using a constant for the custom startup key:

 Select::make('select_startup')
     ->label(trans('admin/server.startup_cmd'))
     ->live()
     ->afterStateUpdated(fn (Set $set, $state) => $set('startup', $state))
     ->options(function ($state, Get $get, Set $set) {
         $egg = Egg::query()->find($get('egg_id'));
-        $startups = $egg->startup_commands ?? [];
+        if (!$egg) {
+            return [];
+        }
+        $startups = $egg->startup_commands ?? [];

         $currentStartup = $get('startup');
         if (!$currentStartup && $startups) {
             $currentStartup = collect($startups)->first();
             $set('startup', $currentStartup);
             $set('select_startup', $currentStartup);
         }

-        return array_flip($startups) + ['' => 'Custom Startup'];
+        const CUSTOM_STARTUP_KEY = '__custom__';
+        return array_flip($startups) + [self::CUSTOM_STARTUP_KEY => 'Custom Startup'];
     })

638-654: Startup textarea synchronization looks good with one suggestion.

The bidirectional synchronization between startup textarea and select_startup is well-implemented. The live updates and proper state management ensure consistency.

Consider adding debouncing to the afterStateUpdated callback to reduce unnecessary state updates during typing:

 Textarea::make('startup')
     ->hiddenLabel()
     ->required()
     ->live()
+    ->debounce(500)
     ->autosize()
     ->afterStateUpdated(function ($state, Get $get, Set $set) {
         $egg = Egg::query()->find($get('egg_id'));
         $startups = $egg->startup_commands ?? [];

         if (in_array($state, $startups)) {
             $set('select_startup', $state);
         } else {
             $set('select_startup', '');
         }
     })

643-652: Add null check for egg retrieval in textarea callback.

Similar to the select field, the textarea's afterStateUpdated callback should handle the case where the egg is not found.

Apply this fix to prevent potential errors:

 ->afterStateUpdated(function ($state, Get $get, Set $set) {
     $egg = Egg::query()->find($get('egg_id'));
+    if (!$egg) {
+        $set('select_startup', '');
+        return;
+    }
     $startups = $egg->startup_commands ?? [];

     if (in_array($state, $startups)) {
         $set('select_startup', $state);
     } else {
         $set('select_startup', '');
     }
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec96047 and 94def16.

📒 Files selected for processing (4)
  • app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (2 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/Filament/Admin/Resources/Servers/Pages/CreateServer.php
  • app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php
🧰 Additional context used
🧬 Code graph analysis (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)
app/Models/Egg.php (1)
  • Egg (59-329)
🔇 Additional comments (4)
app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (2)

86-96: Translations/help copy: ensure keys exist and reflect “multiple commands.”

Confirm new i18n keys exist: admin/egg.startup_commands, admin/egg.add_startup, admin/egg.startup_name, admin/egg.startup_command. Also verify admin/egg.startup_help text no longer implies a single command.


86-96: Persisting startup_commands verified — no action required.

Egg model casts startup_commands to an array and includes it in $fillable; a migration creates a nullable JSON startup_commands column and seeds/factories use it.

  • app/Models/Egg.php — docblock, $fillable and cast present.
  • database/migrations/2025_09_03_090706_support_multiple_startup_commands.php — creates/populates startup_commands.
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)

614-616: LGTM! Proper state management for startup preview.

The addition of the previewing hidden field is appropriate for tracking the internal state during startup-related UI interactions.


621-630: No action required — startup_commands are guaranteed non-empty. Migration populates startup_commands from the legacy startup field and model validation enforces 'startup_commands' as a required array with min:1, so the UI's conditional defaulting is correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/Filament/Server/Pages/Startup.php (1)

44-113: Restore ability to change custom startup commands

With startup_select hidden whenever the server’s current command is not one of the egg presets, and the startup textarea now read-only, anyone using a bespoke command can no longer switch back to a predefined option or edit the command at all. That’s a regression from the previous behaviour and blocks normal administration flows. Please keep the selector visible (e.g. include a “Custom” option) and make the textarea editable for users who hold startup.update, updating select_startup when they type so both paths stay in sync.

🧹 Nitpick comments (1)
app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (1)

86-96: Consider adding key validation for startup commands.

The KeyValue field implementation is well-structured with good labels and placeholders. However, consider adding explicit validation to ensure startup command names (keys) are not empty and are unique within the set.

While Filament's KeyValue component may handle some of these cases by default, you can add explicit validation for clarity:

 KeyValue::make('startup_commands')
     ->label(trans('admin/egg.startup_commands'))
     ->live()
     ->columnSpanFull()
     ->required()
     ->addActionLabel(trans('admin/egg.add_startup'))
     ->keyLabel(trans('admin/egg.startup_name'))
     ->keyPlaceholder('Default')
     ->valueLabel(trans('admin/egg.startup_command'))
     ->valuePlaceholder('java -Xms128M -XX:MaxRAMPercentage=95.0 -jar {{SERVER_JARFILE}}')
+    ->rules([
+        function () {
+            return function (string $attribute, $value, $fail) {
+                if (is_array($value)) {
+                    $keys = array_keys($value);
+                    if (in_array('', $keys, true)) {
+                        $fail('Startup command names cannot be empty.');
+                    }
+                    if (count($keys) !== count(array_unique($keys))) {
+                        $fail('Startup command names must be unique.');
+                    }
+                }
+            };
+        },
+    ])
     ->helperText(trans('admin/egg.startup_help')),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94def16 and 6f278ed.

📒 Files selected for processing (10)
  • app/Filament/Admin/Resources/Eggs/Pages/CreateEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Eggs/Pages/EditEgg.php (1 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (2 hunks)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1 hunks)
  • app/Filament/Server/Pages/Startup.php (3 hunks)
  • app/Models/Egg.php (6 hunks)
  • app/Services/Eggs/Sharing/EggExporterService.php (1 hunks)
  • app/Services/Eggs/Sharing/EggImporterService.php (5 hunks)
  • app/Services/Servers/ServerCreationService.php (1 hunks)
  • app/Services/Servers/StartupModificationService.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/Services/Servers/ServerCreationService.php
  • app/Services/Servers/StartupModificationService.php
  • app/Services/Eggs/Sharing/EggExporterService.php
🧰 Additional context used
🧬 Code graph analysis (4)
app/Filament/Admin/Resources/Servers/Pages/CreateServer.php (1)
app/Models/Egg.php (1)
  • Egg (59-329)
app/Services/Eggs/Sharing/EggImporterService.php (1)
app/Models/Egg.php (1)
  • Egg (59-329)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (2)
app/Models/Server.php (1)
  • egg (295-298)
app/Models/Egg.php (1)
  • Egg (59-329)
app/Filament/Server/Pages/Startup.php (5)
app/Models/Server.php (2)
  • Server (132-516)
  • egg (295-298)
app/Models/User.php (1)
  • can (357-370)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Facades/Activity.php (1)
  • Activity (8-14)
app/Services/Activity/ActivityLogService.php (3)
  • event (48-53)
  • property (109-117)
  • log (136-155)
🔇 Additional comments (1)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)

637-653: Approve startup field logic as-is. The null-coalescing ($egg->startup_commands ?? []) guarantees $startups is always an array, and Filament’s afterStateUpdated only fires when the state actually changes—preventing any infinite-loop risk.

Copy link
Member

@notAreYouScared notAreYouScared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work in the testing I've done.

Copy link
Member

@notAreYouScared notAreYouScared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client side does not rotate between preview modes. Once you click preview, it stays on the preview until a page refresh.

Migration rollback fails.
image

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1)

623-635: Refactor state initialization out of the options closure.

The options closure contains side effects (lines 628-631) that set form state during options computation. This is problematic because Filament may call the options closure multiple times during the form lifecycle (on re-renders, validation, etc.), leading to unexpected state resets.

Move the initialization logic to a default() closure:

 Select::make('select_startup')
     ->label(trans('admin/server.startup_cmd'))
     ->live()
+    ->default(function (Get $get, Set $set) {
+        $egg = Egg::find($get('egg_id'));
+        $startups = $egg->startup_commands ?? [];
+        
+        if (empty($startups)) {
+            return '';
+        }
+        
+        $currentStartup = $get('startup');
+        if (!$currentStartup) {
+            $defaultStartup = collect($startups)->first();
+            $set('startup', $defaultStartup);
+            return $defaultStartup;
+        }
+        
+        return in_array($currentStartup, $startups) ? $currentStartup : '';
+    })
     ->afterStateUpdated(fn (Set $set, $state) => $set('startup', $state))
     ->options(function ($state, Get $get, Set $set) {
         $egg = Egg::query()->find($get('egg_id'));
         $startups = $egg->startup_commands ?? [];
-
-        $currentStartup = $get('startup');
-        if (!$currentStartup && $startups) {
-            $currentStartup = collect($startups)->first();
-            $set('startup', $currentStartup);
-            $set('select_startup', $currentStartup);
-        }

         return array_flip($startups) + ['' => 'Custom Startup'];
     })
     ->selectablePlaceholder(false)
     ->columnSpanFull()
     ->hintAction(PreviewStartupAction::make('preview')),
app/Filament/Server/Pages/Startup.php (1)

56-68: Guard against undefined index errors in activity logging.

Lines 64-66 perform array_flip and direct array access that can fail if:

  1. startup_commands is null (though unlikely given validation, data integrity issues can occur)
  2. $original is a custom startup value (possible in edge cases despite visibility logic)

Apply defensive coding:

         if ($original !== $server->startup) {
-            $startups = array_flip($server->egg->startup_commands);
+            $startups = array_flip($server->egg->startup_commands ?? []);
             Activity::event('server:startup.command')
-                ->property(['old' => $startups[$original], 'new' => $startups[$state]])
+                ->property(['old' => $startups[$original] ?? $original, 'new' => $startups[$state] ?? $state])
                 ->log();
         }

This ensures the activity log gracefully falls back to the raw command if the label lookup fails.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24677eb and 09a5d8d.

📒 Files selected for processing (4)
  • app/Filament/Admin/Resources/Servers/Pages/EditServer.php (1 hunks)
  • app/Filament/Components/Actions/PreviewStartupAction.php (1 hunks)
  • app/Filament/Server/Pages/Startup.php (3 hunks)
  • lang/en/server/startup.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lang/en/server/startup.php
🧰 Additional context used
🧬 Code graph analysis (3)
app/Filament/Components/Actions/PreviewStartupAction.php (2)
app/Models/Server.php (1)
  • Server (132-516)
app/Services/Servers/StartupCommandService.php (1)
  • StartupCommandService (7-30)
app/Filament/Admin/Resources/Servers/Pages/EditServer.php (3)
app/Models/Server.php (1)
  • egg (295-298)
app/Models/Egg.php (1)
  • Egg (59-329)
app/Filament/Components/Actions/PreviewStartupAction.php (1)
  • PreviewStartupAction (11-31)
app/Filament/Server/Pages/Startup.php (5)
app/Models/Server.php (4)
  • Server (132-516)
  • egg (295-298)
  • user (259-262)
  • send (452-457)
app/Filament/Components/Actions/PreviewStartupAction.php (1)
  • PreviewStartupAction (11-31)
app/Models/Permission.php (1)
  • Permission (11-221)
app/Facades/Activity.php (1)
  • Activity (8-14)
app/Services/Activity/ActivityLogService.php (3)
  • event (48-53)
  • property (109-117)
  • log (136-155)
🔇 Additional comments (1)
app/Filament/Components/Actions/PreviewStartupAction.php (1)

22-29: Preview toggle logic correctly fixed.

The action now properly restores $server->startup (the persisted database value) when disabling preview, avoiding the state corruption issue flagged in the previous review. When enabling preview, it correctly sets a closure that calls the service to render variables.

Minor UX consideration: In the EditServer context where the form is saved as a whole, if a user types a new startup command but hasn't saved the form yet, clicking preview will show the previously saved command rather than the typed value. This appears intentional—preview reflects the currently persisted state—but consider whether this might confuse users expecting to preview unsaved changes.

@notAreYouScared notAreYouScared self-requested a review October 7, 2025 12:38
@Boy132 Boy132 merged commit cd96567 into main Oct 7, 2025
25 checks passed
@Boy132 Boy132 deleted the boy132/multiple-startup-commands branch October 7, 2025 21:42
@github-actions github-actions bot locked and limited conversation to collaborators Oct 7, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiple startup commands per egg

3 participants