chore: inspect commit message guidance

This commit is contained in:
Andras Bacsai 2026-05-26 15:35:09 +02:00
parent 0c6a233b27
commit 6da907f1c8
4 changed files with 289 additions and 208 deletions

View file

@ -76,6 +76,8 @@ class Change extends Component
public string $manifestState = '';
public string $activeTab = 'general';
protected function rules(): array
{
return [
@ -263,10 +265,18 @@ public function mount()
}
}
$this->parameters = get_route_parameters();
$routeName = request()->route()?->getName();
if ($routeName === 'source.github.permissions') {
$this->activeTab = 'permissions';
} elseif ($routeName === 'source.github.resources') {
$this->activeTab = 'resources';
} else {
$this->activeTab = 'general';
}
if (isCloud() && ! isDev()) {
$this->webhook_endpoint = config('app.url');
} else {
$this->webhook_endpoint = $this->fqdn ?? $this->ipv4 ?? '';
$this->webhook_endpoint = $this->fqdn ?? $this->ipv4 ?? $this->ipv6 ?? config('app.url') ?? '';
$this->is_system_wide = $this->github_app->is_system_wide;
}
} catch (\Throwable $e) {

View file

@ -5,7 +5,8 @@
<h1>GitHub App</h1>
<div class="flex gap-2">
@if (data_get($github_app, 'installation_id'))
<x-forms.button canGate="update" :canResource="$github_app" type="submit">Save</x-forms.button>
<x-forms.button canGate="update" :canResource="$github_app" type="submit"
:disabled="$activeTab !== 'general'">Save</x-forms.button>
@endif
@can('delete', $github_app)
@if ($applications->count() > 0)
@ -39,170 +40,187 @@
Install Repositories on GitHub
</a>
@else
<div class="flex flex-col gap-2">
<div class="flex flex-col sm:flex-row gap-2">
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2 w-full">
<x-forms.input canGate="update" :canResource="$github_app" id="name" label="App Name" />
<x-forms.button canGate="update" :canResource="$github_app" wire:click.prevent="updateGithubAppName">
Sync Name
</x-forms.button>
@can('update', $github_app)
<a href="{{ $this->getGithubAppNameUpdatePath() }}">
<x-forms.button
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
Rename
<x-external-link />
</x-forms.button>
</a>
<a href="{{ getInstallationPath($github_app) }}" class="w-fit">
<x-forms.button
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline whitespace-nowrap">
Update Repositories
<div class="navbar-main">
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
<a class="{{ request()->routeIs('source.github.show') ? 'dark:text-white' : '' }}"
{{ wireNavigate() }}
href="{{ route('source.github.show', ['github_app_uuid' => $github_app->uuid]) }}">
General
</a>
<a class="{{ request()->routeIs('source.github.permissions') ? 'dark:text-white' : '' }}"
{{ wireNavigate() }}
href="{{ route('source.github.permissions', ['github_app_uuid' => $github_app->uuid]) }}">
Permissions
</a>
<a class="{{ request()->routeIs('source.github.resources') ? 'dark:text-white' : '' }}"
{{ wireNavigate() }}
href="{{ route('source.github.resources', ['github_app_uuid' => $github_app->uuid]) }}">
Resources
</a>
</nav>
</div>
<div class="pt-4">
<div class="flex flex-col gap-2" @if ($activeTab !== 'general') hidden @endif>
<div class="flex flex-col sm:flex-row gap-2">
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2 w-full">
<x-forms.input canGate="update" :canResource="$github_app" id="name" label="App Name" />
<x-forms.button canGate="update" :canResource="$github_app" wire:click.prevent="updateGithubAppName">
Sync Name
</x-forms.button>
@can('update', $github_app)
<a href="{{ $this->getGithubAppNameUpdatePath() }}">
<x-forms.button
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
Rename
<x-external-link />
</x-forms.button>
</a>
<a href="{{ getInstallationPath($github_app) }}" class="w-fit">
<x-forms.button
class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline whitespace-nowrap">
Update Repositories
<x-external-link />
</x-forms.button>
</a>
@endcan
</div>
</div>
<x-forms.input canGate="update" :canResource="$github_app" id="organization" label="Organization"
placeholder="If empty, personal user will be used" />
@if (!isCloud())
<div class="w-48">
<x-forms.checkbox canGate="update" :canResource="$github_app" label="System Wide?"
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
instantSave id="isSystemWide" />
</div>
@if ($isSystemWide)
<x-callout type="warning" title="Not Recommended">
System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team can use this GitHub App to deploy applications from your repositories. For better security and isolation, it's recommended to create team-specific GitHub Apps instead.
</x-callout>
@endif
@endif
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="htmlUrl" label="HTML Url" />
<x-forms.input canGate="update" :canResource="$github_app" id="apiUrl" label="API Url" />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="customUser" label="User"
required />
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="customPort"
label="Port" required />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="appId"
label="App Id" required />
<x-forms.input canGate="update" :canResource="$github_app" type="number"
id="installationId" label="Installation Id" required />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="clientId" label="Client Id"
type="password" required />
<x-forms.input canGate="update" :canResource="$github_app" id="clientSecret"
label="Client Secret" type="password" required />
<x-forms.input canGate="update" :canResource="$github_app" id="webhookSecret"
label="Webhook Secret" type="password" required />
</div>
<div class="flex gap-2">
<x-forms.select canGate="update" :canResource="$github_app" id="privateKeyId"
label="Private Key" required>
@if (blank($github_app->private_key_id))
<option value="0" selected>Select a private key</option>
@endif
@foreach ($privateKeys as $privateKey)
<option value="{{ $privateKey->id }}">{{ $privateKey->name }}</option>
@endforeach
</x-forms.select>
</div>
</div>
<div class="flex flex-col gap-2" @if ($activeTab !== 'permissions') hidden @endif>
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
<h2>Permissions</h2>
@can('view', $github_app)
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
<a href="{{ getPermissionsPath($github_app) }}">
<x-forms.button class="bg-transparent border-transparent hover:bg-transparent hover:border-transparent hover:underline">
Update
<x-external-link />
</x-forms.button>
</a>
@endcan
</div>
</div>
<x-forms.input canGate="update" :canResource="$github_app" id="organization" label="Organization"
placeholder="If empty, personal user will be used" />
@if (!isCloud())
<div class="w-48">
<x-forms.checkbox canGate="update" :canResource="$github_app" label="System Wide?"
helper="If checked, this GitHub App will be available for everyone in this Coolify instance."
instantSave id="isSystemWide" />
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input id="contents" helper="read - mandatory." label="Content" readonly
placeholder="N/A" />
<x-forms.input id="metadata" helper="read - mandatory." label="Metadata" readonly
placeholder="N/A" />
<x-forms.input id="pullRequests"
helper="write access needed to use deployment status update in previews."
label="Pull Request" readonly placeholder="N/A" />
</div>
@if ($isSystemWide)
<x-callout type="warning" title="Not Recommended">
System-wide GitHub Apps are shared across all teams on this Coolify instance. This means any team can use this GitHub App to deploy applications from your repositories. For better security and isolation, it's recommended to create team-specific GitHub Apps instead.
</x-callout>
</div>
<div class="flex flex-col" @if ($activeTab !== 'resources') hidden @endif x-data="{ search: '' }">
@if ($applications->isEmpty())
<div class="py-4 text-sm opacity-70">
No resources are currently using this GitHub App.
</div>
@else
<x-forms.input placeholder="Search resources..." x-model="search" id="null" />
<div class="overflow-x-auto pt-4">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full">
<thead>
<tr>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Project</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Environment</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type</th>
</tr>
</thead>
<tbody class="divide-y">
@foreach ($applications->sortBy('name', SORT_NATURAL) as $resource)
@php
$projectName = (string) data_get($resource->project(), 'name');
$environmentName = (string) data_get($resource, 'environment.name');
$resourceName = (string) $resource->name;
$resourceType = (string) str($resource->type())->headline();
@endphp
<tr class="dark:hover:bg-coolgray-300 hover:bg-neutral-100"
x-show="search === ''
|| '{{ strtolower(addslashes($projectName)) }}'.includes(search.toLowerCase())
|| '{{ strtolower(addslashes($environmentName)) }}'.includes(search.toLowerCase())
|| '{{ strtolower(addslashes($resourceName)) }}'.includes(search.toLowerCase())
|| '{{ strtolower(addslashes($resourceType)) }}'.includes(search.toLowerCase())">
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $projectName }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $environmentName }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
<a {{ wireNavigate() }} href="{{ $resource->link() }}">
{{ $resourceName }}
<x-internal-link />
</a>
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ $resourceType }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
@endif
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="htmlUrl" label="HTML Url" />
<x-forms.input canGate="update" :canResource="$github_app" id="apiUrl" label="API Url" />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="customUser" label="User"
required />
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="customPort"
label="Port" required />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" type="number" id="appId"
label="App Id" required />
<x-forms.input canGate="update" :canResource="$github_app" type="number"
id="installationId" label="Installation Id" required />
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input canGate="update" :canResource="$github_app" id="clientId" label="Client Id"
type="password" required />
<x-forms.input canGate="update" :canResource="$github_app" id="clientSecret"
label="Client Secret" type="password" required />
<x-forms.input canGate="update" :canResource="$github_app" id="webhookSecret"
label="Webhook Secret" type="password" required />
</div>
<div class="flex gap-2">
<x-forms.select canGate="update" :canResource="$github_app" id="privateKeyId"
label="Private Key" required>
@if (blank($github_app->private_key_id))
<option value="0" selected>Select a private key</option>
@endif
@foreach ($privateKeys as $privateKey)
<option value="{{ $privateKey->id }}">{{ $privateKey->name }}</option>
@endforeach
</x-forms.select>
</div>
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
<h2 class="pt-4">Permissions</h2>
@can('view', $github_app)
<x-forms.button wire:click.prevent="checkPermissions">Refetch</x-forms.button>
<a href="{{ getPermissionsPath($github_app) }}">
<x-forms.button>
Update
<x-external-link />
</x-forms.button>
</a>
@endcan
</div>
<div class="flex flex-col sm:flex-row gap-2">
<x-forms.input id="contents" helper="read - mandatory." label="Content" readonly
placeholder="N/A" />
<x-forms.input id="metadata" helper="read - mandatory." label="Metadata" readonly
placeholder="N/A" />
{{-- <x-forms.input id="administration"
helper="read:write access needed to setup servers as GitHub Runner." label="Administration"
readonly placeholder="N/A" /> --}}
<x-forms.input id="pullRequests"
helper="write access needed to use deployment status update in previews."
label="Pull Request" readonly placeholder="N/A" />
</div>
</div>
@endif
</form>
@if (data_get($github_app, 'installation_id'))
<div class="w-full pt-10">
<div class="h-full">
<div class="flex flex-col">
<div class="flex gap-2">
<h2>Resources</h2>
</div>
<div class="pb-4 title">Here you can find all resources that are using this source.</div>
</div>
@if ($applications->isEmpty())
<div class="py-4 text-sm opacity-70">
No resources are currently using this GitHub App.
</div>
@else
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full">
<thead>
<tr>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
Project
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
Environment</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type
</th>
</tr>
</thead>
<tbody class="divide-y">
@foreach ($applications->sortBy('name',SORT_NATURAL) as $resource)
<tr>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource->project(), 'name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'environment.name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap"><a
class=""
{{ wireNavigate() }}
href="{{ $resource->link() }}">{{ $resource->name }}
<x-internal-link /></a>
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
</div>
@endif
@else
<div class="flex flex-col sm:flex-row sm:items-center gap-2 pb-4">
<h1>GitHub App</h1>
@ -216,31 +234,32 @@ class=""
@endcan
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-center min-h-[calc(100vh-12rem)]">
<div class="mx-auto grid w-full max-w-5xl grid-cols-1 gap-4 lg:grid-cols-2">
@can('create', $github_app)
<h3>Manual Installation</h3>
<div class="flex gap-2 items-center">
If you want to fill the form manually, you can continue below. Only for advanced users.
<x-forms.button wire:click.prevent="createGithubAppManually">
Continue
</x-forms.button>
</div>
<h3>Automated Installation</h3>
<div class=" pb-5 rounded-sm alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>You must complete this step before you can use this source!</span>
</div>
@endcan
<div class="flex flex-col">
<div class="pb-10">
@can('create', $github_app)
@if (!isCloud() || isDev())
<div class="flex flex-col sm:flex-row items-start sm:items-end gap-2">
<x-forms.select wire:model.live='webhook_endpoint' label="Webhook Endpoint"
<section class="box-without-bg flex-col gap-4 p-6 h-full transition-all duration-200"
x-data="{ webhookEndpoint: $wire.entangle('webhook_endpoint').live }">
<div class="flex flex-col gap-4 text-left h-full">
<div class="flex items-center justify-between">
<svg class="size-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
</svg>
<span
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-coollabs/10 dark:bg-warning/20 text-coollabs dark:text-warning rounded">
Recommended
</span>
</div>
<div>
<h3 class="text-xl font-bold mb-2">Automated Installation</h3>
<p class="text-sm dark:text-neutral-400">
Register a GitHub App via GitHub's manifest flow. Permissions and webhooks are pre-configured.
</p>
</div>
<div class="flex flex-col gap-3 pt-4 border-t border-neutral-200 dark:border-coolgray-400">
@if (!isCloud() || isDev())
<x-forms.select wire:model.live='webhook_endpoint' x-model="webhookEndpoint" label="Webhook Endpoint"
helper="All Git webhooks will be sent to this endpoint. <br><br>If you would like to use domain instead of IP address, set your Coolify instance's FQDN in the Settings menu.">
@if ($fqdn)
<option value="{{ $fqdn }}">Use {{ $fqdn }}</option>
@ -255,44 +274,68 @@ class=""
<option value="{{ config('app.url') }}">Use {{ config('app.url') }}</option>
@endif
</x-forms.select>
<x-forms.button isHighlighted
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}',{{ $administration }})">
Register Now
</x-forms.button>
</div>
@else
<div class="flex flex-col sm:flex-row gap-2">
<h2>Register a GitHub App</h2>
<x-forms.button isHighlighted
x-on:click.prevent="createGithubApp('{{ $webhook_endpoint }}','{{ $preview_deployment_permissions }}',{{ $administration }})">
Register Now
</x-forms.button>
</div>
<div>You need to register a GitHub App before using this source.</div>
@endif
@else
<div class="text-sm dark:text-neutral-400">You need to register a GitHub App before using this source.</div>
@endif
<div class="flex w-full flex-col gap-2 pt-4 sm:w-96">
<x-forms.checkbox disabled id="default_permissions" label="Mandatory"
helper="Contents: read<br>Metadata: read<br>Email: read" />
<x-forms.checkbox id="preview_deployment_permissions" label="Preview Deployments "
helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
{{-- <x-forms.checkbox id="administration" label="Administration (for Github Runners)"
helper="Necessary for adding Github Runners to repositories.<br><br>Administration: read & write" /> --}}
<div class="flex w-full flex-col gap-2">
<x-forms.checkbox disabled id="default_permissions" label="Mandatory"
helper="Contents: read<br>Metadata: read<br>Email: read" />
<x-forms.checkbox id="preview_deployment_permissions" label="Preview Deployments"
helper="Necessary for updating pull requests with useful comments (deployment status, links, etc.)<br><br>Pull Request: read & write" />
</div>
</div>
@else
<x-callout type="danger" title="Insufficient Permissions">
You don't have permission to create new GitHub Apps. Please contact your team administrator.
</x-callout>
@endcan
<div class="mt-auto pt-2">
<x-forms.button class="w-full justify-center" isHighlighted
x-on:click.prevent="createGithubApp(webhookEndpoint, {{ Illuminate\Support\Js::from($preview_deployment_permissions) }}, {{ Illuminate\Support\Js::from($administration) }})">
Register Now
</x-forms.button>
</div>
</div>
</section>
<section class="box-without-bg flex-col gap-4 p-6 h-full transition-all duration-200">
<div class="flex flex-col gap-4 text-left h-full">
<div class="flex items-center justify-between">
<svg class="size-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
</svg>
<span
class="px-2 py-1 text-xs font-bold uppercase tracking-wide bg-neutral-100 dark:bg-coolgray-300 dark:text-neutral-400 rounded">
Advanced
</span>
</div>
<div>
<h3 class="text-xl font-bold mb-2">Manual Installation</h3>
<p class="text-sm dark:text-neutral-400">
Fill the GitHub App form manually. For self-hosted GitHub Enterprise or custom permission setups.
</p>
</div>
<div class="mt-auto pt-2">
<x-forms.button class="w-full justify-center" wire:click.prevent="createGithubAppManually">
Continue
</x-forms.button>
</div>
</div>
</section>
@else
<div class="pb-10">
<x-callout type="danger" title="Insufficient Permissions">
You don't have permission to create new GitHub Apps. Please contact your team administrator.
</x-callout>
</div>
@endcan
</div>
</div>
<script>
function createGithubApp(webhook_endpoint, preview_deployment_permissions, administration) {
const {
organization,
html_url,
uuid
} = @json($github_app);
} = @js($github_app->only(['organization', 'html_url', 'uuid']));
if (!webhook_endpoint) {
alert('Please select a webhook endpoint.');
return;

View file

@ -33,7 +33,6 @@
use App\Livewire\Project\Service\Index as ServiceIndex;
use App\Livewire\Project\Shared\ExecuteContainerCommand;
use App\Livewire\Project\Shared\Logs;
use App\Livewire\Project\Shared\ScheduledTask\Show as ScheduledTaskShow;
use App\Livewire\Project\Show as ProjectShow;
use App\Livewire\Security\ApiTokens;
use App\Livewire\Security\CloudInitScripts;
@ -320,6 +319,8 @@
]);
})->name('source.all');
Route::get('/source/github/{github_app_uuid}', GitHubChange::class)->name('source.github.show');
Route::get('/source/github/{github_app_uuid}/permissions', GitHubChange::class)->name('source.github.permissions');
Route::get('/source/github/{github_app_uuid}/resources', GitHubChange::class)->name('source.github.resources');
});
Route::middleware(['auth'])->group(function () {

View file

@ -2,6 +2,7 @@
use App\Livewire\Source\Github\Change;
use App\Models\GithubApp;
use App\Models\InstanceSettings;
use App\Models\PrivateKey;
use App\Models\Team;
use App\Models\User;
@ -47,6 +48,32 @@
->assertSet('privateKeyId', null);
});
test('defaults webhook endpoint to app url when it is the first available endpoint', function () {
config(['app.url' => 'http://localhost:8000']);
InstanceSettings::forceCreate([
'id' => 0,
'fqdn' => null,
'public_ipv4' => null,
'public_ipv6' => null,
]);
$githubApp = GithubApp::create([
'name' => 'Test GitHub App',
'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com',
'custom_user' => 'git',
'custom_port' => 22,
'team_id' => $this->team->id,
'is_system_wide' => false,
]);
Livewire::withQueryParams(['github_app_uuid' => $githubApp->uuid])
->test(Change::class)
->assertSuccessful()
->assertSet('webhook_endpoint', 'http://localhost:8000');
});
test('can mount with fully configured github app', function () {
$privateKey = PrivateKey::create([
'name' => 'Test Key',