From 79b65c5cb0d9f52a86a5f1780f702ce1bd5a9569 Mon Sep 17 00:00:00 2001 From: Andy Newhouse Date: Mon, 11 May 2026 12:51:34 -0500 Subject: [PATCH 1/2] Work on environment specific --- README.md | 8 +++-- config/docify.php | 1 + routes/web.php | 2 ++ .../Middleware/EnsureDocifyCanBeViewed.php | 19 +++++++++++ tests/Feature/DocsComponentTest.php | 33 +++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/Http/Middleware/EnsureDocifyCanBeViewed.php diff --git a/README.md b/README.md index 641414d..521c35f 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,12 @@ php artisan vendor:publish ## Usage -``` -// Usage code and examples here +By default, Docify is only viewable when your Laravel application is running in the `local` environment. + +To allow additional environments, publish the config file and update `environments`: + +```php +'environments' => ['local', 'staging'], ``` ## Testing diff --git a/config/docify.php b/config/docify.php index 5851c0d..7556b6d 100644 --- a/config/docify.php +++ b/config/docify.php @@ -7,4 +7,5 @@ 'route_name' => 'docs', 'prefix' => 'docify', 'folder' => './docs', + 'environments' => ['local'], ]; diff --git a/routes/web.php b/routes/web.php index 7eff88f..e562200 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,7 +3,9 @@ declare(strict_types=1); use Illuminate\Support\Facades\Route; +use TechEnby\Docify\Http\Middleware\EnsureDocifyCanBeViewed; Route::livewire(rtrim(config('docify.route'), '/') . '/{page?}', config('docify.prefix') . '::docs') + ->middleware(EnsureDocifyCanBeViewed::class) ->where('page', '.*') ->name(config('docify.route_name')); diff --git a/src/Http/Middleware/EnsureDocifyCanBeViewed.php b/src/Http/Middleware/EnsureDocifyCanBeViewed.php new file mode 100644 index 0000000..6cb85c1 --- /dev/null +++ b/src/Http/Middleware/EnsureDocifyCanBeViewed.php @@ -0,0 +1,19 @@ +environment(config('docify.environments', ['local'])), 404); + + return $next($request); + } +} diff --git a/tests/Feature/DocsComponentTest.php b/tests/Feature/DocsComponentTest.php index 88ad5cf..c7f18c0 100644 --- a/tests/Feature/DocsComponentTest.php +++ b/tests/Feature/DocsComponentTest.php @@ -3,11 +3,15 @@ declare(strict_types=1); use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Route; use Livewire\Livewire; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use TechEnby\Docify\Http\Middleware\EnsureDocifyCanBeViewed; beforeEach(function (): void { config()->set('app.key', 'base64:' . base64_encode(str_repeat('a', 32))); config()->set('docify.folder', './docs-test'); + config()->set('docify.environments', ['testing']); File::deleteDirectory(base_path('docs-test')); File::ensureDirectoryExists(base_path('docs-test/guides')); @@ -94,3 +98,32 @@ $this->get('/docs/missing')->assertNotFound(); }); + +test('registers the environment guard on the docs route', function (): void { + expect(Route::getRoutes()->getByName('docs')->gatherMiddleware()) + ->toContain(EnsureDocifyCanBeViewed::class); +}); + +test('only allows configured environments to view docs', function (): void { + $middleware = new EnsureDocifyCanBeViewed; + $request = request(); + $next = fn () => response('allowed'); + + $this->app['env'] = 'local'; + config()->set('docify.environments', ['local']); + + expect($middleware->handle($request, $next)->getContent())->toBe('allowed'); + + $this->app['env'] = 'production'; + + $middleware->handle($request, $next); +})->throws(NotFoundHttpException::class); + +test('allows users to configure additional docs environments', function (): void { + $middleware = new EnsureDocifyCanBeViewed; + + $this->app['env'] = 'staging'; + config()->set('docify.environments', ['local', 'staging']); + + expect($middleware->handle(request(), fn () => response('allowed'))->getContent())->toBe('allowed'); +}); From 88180400f6b6dd09e9a7b050e5b37a831d41d030 Mon Sep 17 00:00:00 2001 From: Andy Newhouse Date: Tue, 12 May 2026 11:18:53 -0500 Subject: [PATCH 2/2] Add more test cases, and clean up editor implementation --- README.md | 6 ++++ config/docify.php | 1 + resources/views/livewire/docs.blade.php | 46 +++++++++++++++---------- tests/Feature/DocsComponentTest.php | 41 ++++++++++++++++++++++ 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 521c35f..b5544a6 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ To allow additional environments, publish the config file and update `environmen 'environments' => ['local', 'staging'], ``` +Set the local editor used by the Edit link with `DOCIFY_EDITOR`. If it is not set, Docify will also check `DEBUGBAR_EDITOR` and `IGNITION_EDITOR` before defaulting to VS Code. + +```dotenv +DOCIFY_EDITOR=cursor +``` + ## Testing ```bash diff --git a/config/docify.php b/config/docify.php index 7556b6d..5af761b 100644 --- a/config/docify.php +++ b/config/docify.php @@ -8,4 +8,5 @@ 'prefix' => 'docify', 'folder' => './docs', 'environments' => ['local'], + 'editor' => env('DOCIFY_EDITOR') ?: env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'vscode'), ]; diff --git a/resources/views/livewire/docs.blade.php b/resources/views/livewire/docs.blade.php index fdc8512..0a05e1f 100644 --- a/resources/views/livewire/docs.blade.php +++ b/resources/views/livewire/docs.blade.php @@ -26,31 +26,38 @@ public function mount(?string $page = null): void { $this->page = trim($page ?: 'index', '/') ?: 'index'; - $docsPath = base_path(trim(config('docify.folder'), './')); - $resolvedPath = realpath(sprintf('%s/%s.md', $docsPath, $this->page)); - - abort_unless($resolvedPath && str_starts_with($resolvedPath, $docsPath), 404); + $docsPath = realpath(base_path(trim(config('docify.folder'), './'))); + $resolvedPath = $docsPath + ? realpath(sprintf('%s/%s.md', $docsPath, $this->page)) + : false; + + abort_unless( + $docsPath + && $resolvedPath + && str_starts_with($resolvedPath, $docsPath . DIRECTORY_SEPARATOR), + 404 + ); $this->path = $resolvedPath; } #[Computed] - public function editUrl(): string + public function editUrl(): ?string { - if (App::isLocal()) { - $editors = [ - 'vscode' => 'vscode://file/%s', - 'cursor' => 'cursor://file/%s', - 'phpstorm' => 'phpstorm://open?file=%s', - 'sublime' => 'subl://open?url=file://%s', - 'atom' => 'atom://open?url=file://%s', - 'zed' => 'zed://open?path=%s', - ]; + if (! App::isLocal()) { + return null; + } - $editor = config()->string('app.editor', 'vscode'); + $editor = config()->string('docify.editor', 'vscode'); - return sprintf($editors[$editor] ?? $editors['phpstorm'], $this->path); - } + return match ($editor) { + 'cursor' => 'cursor://file/' . $this->path, + 'phpstorm' => 'phpstorm://open?file=' . $this->path, + 'sublime' => 'subl://open?url=file://' . $this->path, + 'atom' => 'atom://open?url=file://' . $this->path, + 'zed' => 'zed://open?path=' . $this->path, + default => 'vscode://file/' . $this->path, + }; } /** @return array> */ @@ -152,7 +159,10 @@ public function content(): string
- Edit + @if ($this->editUrl) + Edit + @endif +
{!! $this->content !!}
diff --git a/tests/Feature/DocsComponentTest.php b/tests/Feature/DocsComponentTest.php index c7f18c0..3fff31b 100644 --- a/tests/Feature/DocsComponentTest.php +++ b/tests/Feature/DocsComponentTest.php @@ -19,6 +19,7 @@ afterEach(function (): void { File::deleteDirectory(base_path('docs-test')); + File::deleteDirectory(base_path('docs-test-private')); }); test('renders the requested markdown page', function (): void { @@ -99,6 +100,46 @@ $this->get('/docs/missing')->assertNotFound(); }); +test('renders docs in configured non local environments without an edit link', function (): void { + $this->app['env'] = 'staging'; + config()->set('docify.environments', ['local', 'staging']); + + File::put(base_path('docs-test/index.md'), '# Package Docs'); + + Livewire::test('docify::docs') + ->assertSee('Package Docs') + ->assertDontSee('Edit'); +}); + +test('uses the configured docify editor for local edit links', function (): void { + $this->app['env'] = 'local'; + config()->set('docify.editor', 'cursor'); + + File::put(base_path('docs-test/index.md'), '# Package Docs'); + + expect(Livewire::test('docify::docs')->get('editUrl')) + ->toBe('cursor://file/' . realpath(base_path('docs-test/index.md'))); +}); + +test('falls back to vscode for unknown local editors', function (): void { + $this->app['env'] = 'local'; + config()->set('docify.editor', 'unknown'); + + File::put(base_path('docs-test/index.md'), '# Package Docs'); + + expect(Livewire::test('docify::docs')->get('editUrl')) + ->toBe('vscode://file/' . realpath(base_path('docs-test/index.md'))); +}); + +test('does not allow traversing outside the configured docs folder', function (): void { + File::ensureDirectoryExists(base_path('docs-test-private')); + File::put(base_path('docs-test/index.md'), '# Public Docs'); + File::put(base_path('docs-test-private/secret.md'), '# Secret Docs'); + + $this->get('/docs/%2E%2E%2Fdocs-test-private%2Fsecret') + ->assertNotFound(); +}); + test('registers the environment guard on the docs route', function (): void { expect(Route::getRoutes()->getByName('docs')->gatherMiddleware()) ->toContain(EnsureDocifyCanBeViewed::class);