diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index a9eeb2b..229e445 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -1017,7 +1017,10 @@ 'database.redis.cache.backoff_base' => 'integer', 'database.redis.cache.backoff_cap' => 'integer', 'docs.path' => 'string', - 'docs.command_index.cache_ttl_seconds' => 'integer', + 'docs.cache.enabled' => 'boolean', + 'docs.cache.path' => 'string', + 'docs.cache.version' => 'string', + 'docs.cache.lock_timeout_seconds' => 'integer', 'docs.github.repo' => 'string', 'docs.github.branch' => 'string', 'docs.github.dir' => 'string', @@ -1182,6 +1185,10 @@ 'session.partitioned' => 'boolean', 'boost.enabled' => 'boolean', 'boost.browser_logs_watcher' => 'boolean', + 'boost.executable_paths.php' => 'NULL', + 'boost.executable_paths.composer' => 'NULL', + 'boost.executable_paths.npm' => 'NULL', + 'boost.executable_paths.vendor_bin' => 'NULL', 'mcp.redirect_domains' => 'array', 'livewire.component_locations' => 'array', 'livewire.component_namespaces.layouts' => 'string', @@ -1527,7 +1534,10 @@ 'database.redis.cache.backoff_base' => 'integer', 'database.redis.cache.backoff_cap' => 'integer', 'docs.path' => 'string', - 'docs.command_index.cache_ttl_seconds' => 'integer', + 'docs.cache.enabled' => 'boolean', + 'docs.cache.path' => 'string', + 'docs.cache.version' => 'string', + 'docs.cache.lock_timeout_seconds' => 'integer', 'docs.github.repo' => 'string', 'docs.github.branch' => 'string', 'docs.github.dir' => 'string', @@ -1692,6 +1702,10 @@ 'session.partitioned' => 'boolean', 'boost.enabled' => 'boolean', 'boost.browser_logs_watcher' => 'boolean', + 'boost.executable_paths.php' => 'NULL', + 'boost.executable_paths.composer' => 'NULL', + 'boost.executable_paths.npm' => 'NULL', + 'boost.executable_paths.vendor_bin' => 'NULL', 'mcp.redirect_domains' => 'array', 'livewire.component_locations' => 'array', 'livewire.component_namespaces.layouts' => 'string', @@ -2037,7 +2051,10 @@ 'database.redis.cache.backoff_base' => 'integer', 'database.redis.cache.backoff_cap' => 'integer', 'docs.path' => 'string', - 'docs.command_index.cache_ttl_seconds' => 'integer', + 'docs.cache.enabled' => 'boolean', + 'docs.cache.path' => 'string', + 'docs.cache.version' => 'string', + 'docs.cache.lock_timeout_seconds' => 'integer', 'docs.github.repo' => 'string', 'docs.github.branch' => 'string', 'docs.github.dir' => 'string', @@ -2202,6 +2219,10 @@ 'session.partitioned' => 'boolean', 'boost.enabled' => 'boolean', 'boost.browser_logs_watcher' => 'boolean', + 'boost.executable_paths.php' => 'NULL', + 'boost.executable_paths.composer' => 'NULL', + 'boost.executable_paths.npm' => 'NULL', + 'boost.executable_paths.vendor_bin' => 'NULL', 'mcp.redirect_domains' => 'array', 'livewire.component_locations' => 'array', 'livewire.component_namespaces.layouts' => 'string', @@ -2369,54 +2390,55 @@ 'database.redis.default.max_retries','database.redis.default.backoff_algorithm','database.redis.default.backoff_base','database.redis.default.backoff_cap','database.redis.cache.url', 'database.redis.cache.host','database.redis.cache.username','database.redis.cache.password','database.redis.cache.port','database.redis.cache.database', 'database.redis.cache.max_retries','database.redis.cache.backoff_algorithm','database.redis.cache.backoff_base','database.redis.cache.backoff_cap','docs.path', -'docs.command_index.cache_ttl_seconds','docs.github.repo','docs.github.branch','docs.github.dir','filesystems.default', -'filesystems.disks.local.driver','filesystems.disks.local.root','filesystems.disks.local.serve','filesystems.disks.local.throw','filesystems.disks.local.report', -'filesystems.disks.public.driver','filesystems.disks.public.root','filesystems.disks.public.url','filesystems.disks.public.visibility','filesystems.disks.public.throw', -'filesystems.disks.public.report','filesystems.disks.s3.driver','filesystems.disks.s3.key','filesystems.disks.s3.secret','filesystems.disks.s3.region', -'filesystems.disks.s3.bucket','filesystems.disks.s3.url','filesystems.disks.s3.endpoint','filesystems.disks.s3.use_path_style_endpoint','filesystems.disks.s3.throw', -'filesystems.disks.s3.report','filesystems.links./Users/lucian/Developer/deployerphp.com/public/storage','logging.default','logging.deprecations.channel','logging.deprecations.trace', -'logging.channels.stack.driver','logging.channels.stack.channels','logging.channels.stack.ignore_exceptions','logging.channels.single.driver','logging.channels.single.path', -'logging.channels.single.level','logging.channels.single.replace_placeholders','logging.channels.daily.driver','logging.channels.daily.path','logging.channels.daily.level', -'logging.channels.daily.days','logging.channels.daily.replace_placeholders','logging.channels.slack.driver','logging.channels.slack.url','logging.channels.slack.username', -'logging.channels.slack.emoji','logging.channels.slack.level','logging.channels.slack.replace_placeholders','logging.channels.papertrail.driver','logging.channels.papertrail.level', -'logging.channels.papertrail.handler','logging.channels.papertrail.handler_with.host','logging.channels.papertrail.handler_with.port','logging.channels.papertrail.handler_with.connectionString','logging.channels.papertrail.processors', -'logging.channels.stderr.driver','logging.channels.stderr.level','logging.channels.stderr.handler','logging.channels.stderr.handler_with.stream','logging.channels.stderr.formatter', -'logging.channels.stderr.processors','logging.channels.syslog.driver','logging.channels.syslog.level','logging.channels.syslog.facility','logging.channels.syslog.replace_placeholders', -'logging.channels.errorlog.driver','logging.channels.errorlog.level','logging.channels.errorlog.replace_placeholders','logging.channels.null.driver','logging.channels.null.handler', -'logging.channels.emergency.path','logging.channels.browser.driver','logging.channels.browser.path','logging.channels.browser.level','logging.channels.browser.days', -'mail.default','mail.mailers.smtp.transport','mail.mailers.smtp.scheme','mail.mailers.smtp.url','mail.mailers.smtp.host', -'mail.mailers.smtp.port','mail.mailers.smtp.username','mail.mailers.smtp.password','mail.mailers.smtp.timeout','mail.mailers.smtp.local_domain', -'mail.mailers.ses.transport','mail.mailers.postmark.transport','mail.mailers.resend.transport','mail.mailers.sendmail.transport','mail.mailers.sendmail.path', -'mail.mailers.log.transport','mail.mailers.log.channel','mail.mailers.array.transport','mail.mailers.failover.transport','mail.mailers.failover.mailers', -'mail.mailers.failover.retry_after','mail.mailers.roundrobin.transport','mail.mailers.roundrobin.mailers','mail.mailers.roundrobin.retry_after','mail.from.address', -'mail.from.name','mail.markdown.theme','mail.markdown.paths','queue.default','queue.connections.sync.driver', -'queue.connections.database.driver','queue.connections.database.connection','queue.connections.database.table','queue.connections.database.queue','queue.connections.database.retry_after', -'queue.connections.database.after_commit','queue.connections.beanstalkd.driver','queue.connections.beanstalkd.host','queue.connections.beanstalkd.queue','queue.connections.beanstalkd.retry_after', -'queue.connections.beanstalkd.block_for','queue.connections.beanstalkd.after_commit','queue.connections.sqs.driver','queue.connections.sqs.key','queue.connections.sqs.secret', -'queue.connections.sqs.prefix','queue.connections.sqs.queue','queue.connections.sqs.suffix','queue.connections.sqs.region','queue.connections.sqs.after_commit', -'queue.connections.redis.driver','queue.connections.redis.connection','queue.connections.redis.queue','queue.connections.redis.retry_after','queue.connections.redis.block_for', -'queue.connections.redis.after_commit','queue.connections.deferred.driver','queue.connections.failover.driver','queue.connections.failover.connections','queue.connections.background.driver', -'queue.batching.database','queue.batching.table','queue.failed.driver','queue.failed.database','queue.failed.table', -'services.postmark.key','services.resend.key','services.ses.key','services.ses.secret','services.ses.region', -'services.slack.notifications.bot_user_oauth_token','services.slack.notifications.channel','session.driver','session.lifetime','session.expire_on_close', -'session.encrypt','session.files','session.connection','session.table','session.store', -'session.lottery','session.cookie','session.path','session.domain','session.secure', -'session.http_only','session.same_site','session.partitioned','boost.enabled','boost.browser_logs_watcher', -'mcp.redirect_domains','livewire.component_locations','livewire.component_namespaces.layouts','livewire.component_namespaces.pages','livewire.component_layout', -'livewire.component_placeholder','livewire.make_command.type','livewire.make_command.emoji','livewire.make_command.with.js','livewire.make_command.with.css', -'livewire.make_command.with.test','livewire.class_namespace','livewire.class_path','livewire.view_path','livewire.temporary_file_upload.disk', -'livewire.temporary_file_upload.rules','livewire.temporary_file_upload.directory','livewire.temporary_file_upload.middleware','livewire.temporary_file_upload.preview_mimes','livewire.temporary_file_upload.max_upload_time', -'livewire.temporary_file_upload.cleanup','livewire.render_on_redirect','livewire.legacy_model_binding','livewire.inject_assets','livewire.navigate.show_progress_bar', -'livewire.navigate.progress_bar_color','livewire.inject_morph_markers','livewire.smart_wire_keys','livewire.pagination_theme','livewire.release_token', -'livewire.csp_safe','livewire.payload.max_size','livewire.payload.max_nesting_depth','livewire.payload.max_calls','livewire.payload.max_components', -'structure-discoverer.ignored_files','structure-discoverer.structure_scout_directories','structure-discoverer.cache.driver','structure-discoverer.cache.store','ide-helper.filename', -'ide-helper.models_filename','ide-helper.meta_filename','ide-helper.include_fluent','ide-helper.include_factory_builders','ide-helper.write_model_magic_where', -'ide-helper.write_model_external_builder_methods','ide-helper.write_model_relation_count_properties','ide-helper.write_model_relation_exists_properties','ide-helper.write_eloquent_model_mixins','ide-helper.include_helpers', -'ide-helper.helper_files','ide-helper.model_locations','ide-helper.ignored_models','ide-helper.model_hooks','ide-helper.extra.Eloquent', -'ide-helper.extra.Session','ide-helper.magic','ide-helper.interfaces','ide-helper.model_camel_case_properties','ide-helper.type_overrides.integer', -'ide-helper.type_overrides.boolean','ide-helper.include_class_docblocks','ide-helper.force_fqn','ide-helper.use_generics_annotations','ide-helper.macro_default_return_types.Illuminate\\Http\\Client\\Factory', -'ide-helper.additional_relation_types','ide-helper.additional_relation_return_types','ide-helper.enforce_nullable_relationships','ide-helper.post_migrate','tinker.commands', -'tinker.alias','tinker.dont_alias','tinker.trust_project',); +'docs.cache.enabled','docs.cache.path','docs.cache.version','docs.cache.lock_timeout_seconds','docs.github.repo', +'docs.github.branch','docs.github.dir','filesystems.default','filesystems.disks.local.driver','filesystems.disks.local.root', +'filesystems.disks.local.serve','filesystems.disks.local.throw','filesystems.disks.local.report','filesystems.disks.public.driver','filesystems.disks.public.root', +'filesystems.disks.public.url','filesystems.disks.public.visibility','filesystems.disks.public.throw','filesystems.disks.public.report','filesystems.disks.s3.driver', +'filesystems.disks.s3.key','filesystems.disks.s3.secret','filesystems.disks.s3.region','filesystems.disks.s3.bucket','filesystems.disks.s3.url', +'filesystems.disks.s3.endpoint','filesystems.disks.s3.use_path_style_endpoint','filesystems.disks.s3.throw','filesystems.disks.s3.report','filesystems.links./Users/lucian/Developer/deployerphp.com/public/storage', +'logging.default','logging.deprecations.channel','logging.deprecations.trace','logging.channels.stack.driver','logging.channels.stack.channels', +'logging.channels.stack.ignore_exceptions','logging.channels.single.driver','logging.channels.single.path','logging.channels.single.level','logging.channels.single.replace_placeholders', +'logging.channels.daily.driver','logging.channels.daily.path','logging.channels.daily.level','logging.channels.daily.days','logging.channels.daily.replace_placeholders', +'logging.channels.slack.driver','logging.channels.slack.url','logging.channels.slack.username','logging.channels.slack.emoji','logging.channels.slack.level', +'logging.channels.slack.replace_placeholders','logging.channels.papertrail.driver','logging.channels.papertrail.level','logging.channels.papertrail.handler','logging.channels.papertrail.handler_with.host', +'logging.channels.papertrail.handler_with.port','logging.channels.papertrail.handler_with.connectionString','logging.channels.papertrail.processors','logging.channels.stderr.driver','logging.channels.stderr.level', +'logging.channels.stderr.handler','logging.channels.stderr.handler_with.stream','logging.channels.stderr.formatter','logging.channels.stderr.processors','logging.channels.syslog.driver', +'logging.channels.syslog.level','logging.channels.syslog.facility','logging.channels.syslog.replace_placeholders','logging.channels.errorlog.driver','logging.channels.errorlog.level', +'logging.channels.errorlog.replace_placeholders','logging.channels.null.driver','logging.channels.null.handler','logging.channels.emergency.path','logging.channels.browser.driver', +'logging.channels.browser.path','logging.channels.browser.level','logging.channels.browser.days','mail.default','mail.mailers.smtp.transport', +'mail.mailers.smtp.scheme','mail.mailers.smtp.url','mail.mailers.smtp.host','mail.mailers.smtp.port','mail.mailers.smtp.username', +'mail.mailers.smtp.password','mail.mailers.smtp.timeout','mail.mailers.smtp.local_domain','mail.mailers.ses.transport','mail.mailers.postmark.transport', +'mail.mailers.resend.transport','mail.mailers.sendmail.transport','mail.mailers.sendmail.path','mail.mailers.log.transport','mail.mailers.log.channel', +'mail.mailers.array.transport','mail.mailers.failover.transport','mail.mailers.failover.mailers','mail.mailers.failover.retry_after','mail.mailers.roundrobin.transport', +'mail.mailers.roundrobin.mailers','mail.mailers.roundrobin.retry_after','mail.from.address','mail.from.name','mail.markdown.theme', +'mail.markdown.paths','queue.default','queue.connections.sync.driver','queue.connections.database.driver','queue.connections.database.connection', +'queue.connections.database.table','queue.connections.database.queue','queue.connections.database.retry_after','queue.connections.database.after_commit','queue.connections.beanstalkd.driver', +'queue.connections.beanstalkd.host','queue.connections.beanstalkd.queue','queue.connections.beanstalkd.retry_after','queue.connections.beanstalkd.block_for','queue.connections.beanstalkd.after_commit', +'queue.connections.sqs.driver','queue.connections.sqs.key','queue.connections.sqs.secret','queue.connections.sqs.prefix','queue.connections.sqs.queue', +'queue.connections.sqs.suffix','queue.connections.sqs.region','queue.connections.sqs.after_commit','queue.connections.redis.driver','queue.connections.redis.connection', +'queue.connections.redis.queue','queue.connections.redis.retry_after','queue.connections.redis.block_for','queue.connections.redis.after_commit','queue.connections.deferred.driver', +'queue.connections.failover.driver','queue.connections.failover.connections','queue.connections.background.driver','queue.batching.database','queue.batching.table', +'queue.failed.driver','queue.failed.database','queue.failed.table','services.postmark.key','services.resend.key', +'services.ses.key','services.ses.secret','services.ses.region','services.slack.notifications.bot_user_oauth_token','services.slack.notifications.channel', +'session.driver','session.lifetime','session.expire_on_close','session.encrypt','session.files', +'session.connection','session.table','session.store','session.lottery','session.cookie', +'session.path','session.domain','session.secure','session.http_only','session.same_site', +'session.partitioned','boost.enabled','boost.browser_logs_watcher','boost.executable_paths.php','boost.executable_paths.composer', +'boost.executable_paths.npm','boost.executable_paths.vendor_bin','mcp.redirect_domains','livewire.component_locations','livewire.component_namespaces.layouts', +'livewire.component_namespaces.pages','livewire.component_layout','livewire.component_placeholder','livewire.make_command.type','livewire.make_command.emoji', +'livewire.make_command.with.js','livewire.make_command.with.css','livewire.make_command.with.test','livewire.class_namespace','livewire.class_path', +'livewire.view_path','livewire.temporary_file_upload.disk','livewire.temporary_file_upload.rules','livewire.temporary_file_upload.directory','livewire.temporary_file_upload.middleware', +'livewire.temporary_file_upload.preview_mimes','livewire.temporary_file_upload.max_upload_time','livewire.temporary_file_upload.cleanup','livewire.render_on_redirect','livewire.legacy_model_binding', +'livewire.inject_assets','livewire.navigate.show_progress_bar','livewire.navigate.progress_bar_color','livewire.inject_morph_markers','livewire.smart_wire_keys', +'livewire.pagination_theme','livewire.release_token','livewire.csp_safe','livewire.payload.max_size','livewire.payload.max_nesting_depth', +'livewire.payload.max_calls','livewire.payload.max_components','structure-discoverer.ignored_files','structure-discoverer.structure_scout_directories','structure-discoverer.cache.driver', +'structure-discoverer.cache.store','ide-helper.filename','ide-helper.models_filename','ide-helper.meta_filename','ide-helper.include_fluent', +'ide-helper.include_factory_builders','ide-helper.write_model_magic_where','ide-helper.write_model_external_builder_methods','ide-helper.write_model_relation_count_properties','ide-helper.write_model_relation_exists_properties', +'ide-helper.write_eloquent_model_mixins','ide-helper.include_helpers','ide-helper.helper_files','ide-helper.model_locations','ide-helper.ignored_models', +'ide-helper.model_hooks','ide-helper.extra.Eloquent','ide-helper.extra.Session','ide-helper.magic','ide-helper.interfaces', +'ide-helper.model_camel_case_properties','ide-helper.type_overrides.integer','ide-helper.type_overrides.boolean','ide-helper.include_class_docblocks','ide-helper.force_fqn', +'ide-helper.use_generics_annotations','ide-helper.macro_default_return_types.Illuminate\\Http\\Client\\Factory','ide-helper.additional_relation_types','ide-helper.additional_relation_return_types','ide-helper.enforce_nullable_relationships', +'ide-helper.post_migrate','tinker.commands','tinker.alias','tinker.dont_alias','tinker.trust_project',); registerArgumentsSet('middleware', 'web','api','auth','auth.basic','auth.session', 'cache.headers','can','guest','password.confirm','precognitive', @@ -2426,12 +2448,14 @@ 'livewire.upload-file','livewire.preview-file','home','docs.show','command-index', 'storage.local',); registerArgumentsSet('views', -'8c026b058f5337eae33f20fdb97d2c88::docs-viewer','command-index','components.docs.alert','components.docs.code-block','components.docs.content', -'components.docs.headings','components.docs.toc','components.layouts.docs','docs-viewer','docs.alert', -'docs.code-block','docs.content','docs.headings','docs.toc','e60dd9d2c3a62d619c9acb38f20d5aa5::icon.github', -'e60dd9d2c3a62d619c9acb38f20d5aa5::icon.reddit','e60dd9d2c3a62d619c9acb38f20d5aa5::icon.x','f3dff5f3846978e0b3612bcd64f9871e::docs.alert','f3dff5f3846978e0b3612bcd64f9871e::docs.code-block','f3dff5f3846978e0b3612bcd64f9871e::docs.content', -'f3dff5f3846978e0b3612bcd64f9871e::docs.headings','f3dff5f3846978e0b3612bcd64f9871e::docs.toc','f3dff5f3846978e0b3612bcd64f9871e::layouts.docs','flux.icon.github','flux.icon.reddit', -'flux.icon.x','layouts.docs','livewire.docs-viewer','welcome','e60dd9d2c3a62d619c9acb38f20d5aa5::accent', +'8c026b058f5337eae33f20fdb97d2c88::command-index','8c026b058f5337eae33f20fdb97d2c88::docs-viewer','command-index','components.docs.alert','components.docs.code-block', +'components.docs.content','components.docs.footer','components.docs.header','components.docs.headings','components.docs.toc', +'components.layouts.command-index','components.layouts.docs','docs-viewer','docs.alert','docs.code-block', +'docs.content','docs.footer','docs.header','docs.headings','docs.toc', +'e60dd9d2c3a62d619c9acb38f20d5aa5::icon.github','e60dd9d2c3a62d619c9acb38f20d5aa5::icon.reddit','e60dd9d2c3a62d619c9acb38f20d5aa5::icon.x','f3dff5f3846978e0b3612bcd64f9871e::docs.alert','f3dff5f3846978e0b3612bcd64f9871e::docs.code-block', +'f3dff5f3846978e0b3612bcd64f9871e::docs.content','f3dff5f3846978e0b3612bcd64f9871e::docs.footer','f3dff5f3846978e0b3612bcd64f9871e::docs.header','f3dff5f3846978e0b3612bcd64f9871e::docs.headings','f3dff5f3846978e0b3612bcd64f9871e::docs.toc', +'f3dff5f3846978e0b3612bcd64f9871e::layouts.command-index','f3dff5f3846978e0b3612bcd64f9871e::layouts.docs','flux.icon.github','flux.icon.reddit','flux.icon.x', +'layouts.command-index','layouts.docs','livewire.command-index','livewire.docs-viewer','e60dd9d2c3a62d619c9acb38f20d5aa5::accent', 'e60dd9d2c3a62d619c9acb38f20d5aa5::accordion.content','e60dd9d2c3a62d619c9acb38f20d5aa5::accordion.heading','e60dd9d2c3a62d619c9acb38f20d5aa5::accordion.icon','e60dd9d2c3a62d619c9acb38f20d5aa5::accordion.index','e60dd9d2c3a62d619c9acb38f20d5aa5::accordion.item', 'e60dd9d2c3a62d619c9acb38f20d5aa5::aside','e60dd9d2c3a62d619c9acb38f20d5aa5::autocomplete.index','e60dd9d2c3a62d619c9acb38f20d5aa5::autocomplete.item','e60dd9d2c3a62d619c9acb38f20d5aa5::autocomplete.items','e60dd9d2c3a62d619c9acb38f20d5aa5::avatar.group', 'e60dd9d2c3a62d619c9acb38f20d5aa5::avatar.index','e60dd9d2c3a62d619c9acb38f20d5aa5::badge.close','e60dd9d2c3a62d619c9acb38f20d5aa5::badge.index','e60dd9d2c3a62d619c9acb38f20d5aa5::brand','e60dd9d2c3a62d619c9acb38f20d5aa5::breadcrumbs.index', diff --git a/_ide_helper.php b/_ide_helper.php index 68c73b6..301bd63 100644 --- a/_ide_helper.php +++ b/_ide_helper.php @@ -24282,6 +24282,10 @@ public static function response($callback) */ class DocsViewer extends \Livewire\Component { } + /** + */ + class CommandIndex extends \Livewire\Component { + } } diff --git a/app/Services/DocumentService.php b/app/Services/DocumentService.php index ca4a788..b7dd22e 100644 --- a/app/Services/DocumentService.php +++ b/app/Services/DocumentService.php @@ -81,7 +81,7 @@ private function loadFromPath(string $filePath, string $documentIdentity, string return null; } - $html = $this->markdown->toHtml($content); + $html = $this->markdown->toHtml($content, $documentIdentity === 'readme' ? null : $titleFallback, $filePath); $headings = $this->headingExtractor->extract($html); $title = $this->extractTitle($html, $titleFallback); diff --git a/app/Services/MarkdownService.php b/app/Services/MarkdownService.php index 971d375..675e143 100644 --- a/app/Services/MarkdownService.php +++ b/app/Services/MarkdownService.php @@ -45,15 +45,18 @@ public function __construct() /** * Convert markdown to HTML with GitHub-style alert boxes, code block wrappers, and link transformation. */ - public function toHtml(string $markdown, ?string $currentSection = null): string - { + public function toHtml( + string $markdown, + ?string $currentSection = null, + ?string $sourceFilePath = null, + ): string { $markdown = $this->stripMarkdownArtifacts($markdown); $html = $this->converter->convert($markdown)->getContent(); $html = $this->stripContentBeforeH1($html); $html = $this->convertGitHubAlerts($html); $html = $this->wrapCodeBlocks($html); - return $this->transformLinks($html, $currentSection); + return $this->transformLinks($html, $currentSection, $sourceFilePath); } /** @@ -104,7 +107,7 @@ private function stripMarkdownArtifacts(string $markdown): string * Convert GitHub-style alerts to styled callout boxes. * * Transforms blockquotes like: - * > [!INFO] + * > [!NOTE] * > Content here * * Into styled alert boxes with icons using Blade component. @@ -117,7 +120,7 @@ private function convertGitHubAlerts(string $html): string // ...optional additional block elements (e.g.
,
)...
//
return preg_replace_callback(
- '/\s*\s*\[!(INFO|IMPORTANT)\]\s*(.*?)<\/p>(.*?)<\/blockquote>/is',
+ '/
\s*\s*\[!(NOTE|IMPORTANT)\]\s*(.*?)<\/p>(.*?)<\/blockquote>/is',
function (array $matches): string {
$type = strtolower(trim($matches[1]));
$firstParagraph = trim($matches[2]);
@@ -178,11 +181,11 @@ function (array $matches): string {
* Converts links like `section/file.md` or `/docs/section/page` to full
* GitHub URLs pointing to the source repository.
*/
- private function transformLinks(string $html, ?string $currentSection): string
+ private function transformLinks(string $html, ?string $currentSection, ?string $sourceFilePath): string
{
return preg_replace_callback(
'/]*)>/i',
- fn (array $matches): string => $this->transformLink($matches, $currentSection),
+ fn (array $matches): string => $this->transformLink($matches, $currentSection, $sourceFilePath),
$html
) ?? $html;
}
@@ -195,7 +198,7 @@ private function transformLinks(string $html, ?string $currentSection): string
*
* @param array $matches
*/
- private function transformLink(array $matches, ?string $currentSection): string
+ private function transformLink(array $matches, ?string $currentSection, ?string $sourceFilePath): string
{
$href = $matches[1];
$attributes = $matches[2];
@@ -212,6 +215,12 @@ private function transformLink(array $matches, ?string $currentSection): string
return sprintf('', $internalUrl, $attributes);
}
+ $relativeDocsUrl = $this->resolveRelativeDocsUrl($href, $sourceFilePath);
+
+ if ($relativeDocsUrl !== null) {
+ return sprintf('', $relativeDocsUrl, $attributes);
+ }
+
// All other links → GitHub with target="_blank"
$githubUrl = $this->resolveGitHubUrl($href, $currentSection);
@@ -222,6 +231,84 @@ private function transformLink(array $matches, ?string $currentSection): string
);
}
+ private function resolveRelativeDocsUrl(string $href, ?string $sourceFilePath): ?string
+ {
+ if ($sourceFilePath === null || str_starts_with($href, '/')) {
+ return null;
+ }
+
+ [$path, $fragment] = $this->splitFragment($href);
+
+ if ($path === '' || preg_match('/^[a-z][a-z0-9+.-]*:/i', $path) === 1) {
+ return null;
+ }
+
+ $docsDirectory = config('docs.path');
+
+ if (! is_string($docsDirectory) || $docsDirectory === '') {
+ return null;
+ }
+
+ $absoluteDocsDirectory = str_starts_with($docsDirectory, '/')
+ ? $docsDirectory
+ : base_path($docsDirectory);
+
+ $resolvedDocsDirectory = realpath($absoluteDocsDirectory);
+ $resolvedSourcePath = realpath($sourceFilePath);
+
+ if ($resolvedDocsDirectory === false || $resolvedSourcePath === false) {
+ return null;
+ }
+
+ $sourceDirectory = dirname($resolvedSourcePath);
+ $candidatePath = $path;
+
+ if (! str_ends_with($candidatePath, '.md')) {
+ $candidatePath .= '.md';
+ }
+
+ $resolvedTargetPath = realpath($sourceDirectory.'/'.$candidatePath);
+
+ if ($resolvedTargetPath === false || ! str_ends_with($resolvedTargetPath, '.md')) {
+ return null;
+ }
+
+ $resolvedDocsParentReadme = realpath(dirname($resolvedDocsDirectory).'/README.md');
+
+ if ($resolvedDocsParentReadme !== false && $resolvedTargetPath === $resolvedDocsParentReadme) {
+ return '/'.$fragment;
+ }
+
+ $docsPrefix = rtrim($resolvedDocsDirectory, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
+
+ if (! str_starts_with($resolvedTargetPath, $docsPrefix)) {
+ return null;
+ }
+
+ $relativeTargetPath = substr($resolvedTargetPath, strlen($docsPrefix));
+ $page = preg_replace('/\.md$/', '', $relativeTargetPath);
+
+ if ($page === null || $page === '' || preg_match('/^[a-z0-9-]+$/', $page) !== 1) {
+ return null;
+ }
+
+ return "/docs/{$page}{$fragment}";
+ }
+
+ /**
+ * @return array{0: string, 1: string}
+ */
+ private function splitFragment(string $href): array
+ {
+ if (! str_contains($href, '#')) {
+ return [$href, ''];
+ }
+
+ [$path, $fragment] = explode('#', $href, 2);
+
+ return [$path, '#'.$fragment];
+ }
+
/**
* Determine if a link points to a docs page by path prefix.
*/
diff --git a/composer.lock b/composer.lock
index 481b5ee..03ba171 100644
--- a/composer.lock
+++ b/composer.lock
@@ -9096,16 +9096,16 @@
},
{
"name": "laravel/boost",
- "version": "v1.8.10",
+ "version": "v1.8.11",
"source": {
"type": "git",
"url": "https://github.com/laravel/boost.git",
- "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32"
+ "reference": "485dd7c834bde865a8a174249fc6ffc56e79e63c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/boost/zipball/aad8b2a423b0a886c2ce7ee92abbfde69992ff32",
- "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32",
+ "url": "https://api.github.com/repos/laravel/boost/zipball/485dd7c834bde865a8a174249fc6ffc56e79e63c",
+ "reference": "485dd7c834bde865a8a174249fc6ffc56e79e63c",
"shasum": ""
},
"require": {
@@ -9158,20 +9158,20 @@
"issues": "https://github.com/laravel/boost/issues",
"source": "https://github.com/laravel/boost"
},
- "time": "2026-01-14T14:51:16+00:00"
+ "time": "2026-02-20T07:28:22+00:00"
},
{
"name": "laravel/mcp",
- "version": "v0.5.7",
+ "version": "v0.5.9",
"source": {
"type": "git",
"url": "https://github.com/laravel/mcp.git",
- "reference": "97fcdacbce93c572e3d25457cf30395dede67088"
+ "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/mcp/zipball/97fcdacbce93c572e3d25457cf30395dede67088",
- "reference": "97fcdacbce93c572e3d25457cf30395dede67088",
+ "url": "https://api.github.com/repos/laravel/mcp/zipball/39e8da60eb7bce4737c5d868d35a3fe78938c129",
+ "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129",
"shasum": ""
},
"require": {
@@ -9231,7 +9231,7 @@
"issues": "https://github.com/laravel/mcp/issues",
"source": "https://github.com/laravel/mcp"
},
- "time": "2026-02-13T14:08:37+00:00"
+ "time": "2026-02-17T19:05:53+00:00"
},
{
"name": "laravel/pail",
diff --git a/tests/Feature/DocsSidebarNavigationTest.php b/tests/Feature/DocsSidebarNavigationTest.php
index 812b9a9..e4f253c 100644
--- a/tests/Feature/DocsSidebarNavigationTest.php
+++ b/tests/Feature/DocsSidebarNavigationTest.php
@@ -39,6 +39,20 @@
->assertSeeText('Requirements');
});
+it('converts relative markdown docs links to internal routes', function (): void {
+ app()->forgetInstance(DocsPathService::class);
+ app()->forgetInstance(TocParserService::class);
+ app()->forgetInstance(DocumentService::class);
+
+ $response = $this->get(route('docs.show', ['page' => 'documentation']));
+
+ $response->assertOk()
+ ->assertSee('href="'.route('home').'"', false)
+ ->assertSee('href="'.route('docs.show', ['page' => 'installation']).'"', false)
+ ->assertSee('href="'.route('docs.show', ['page' => 'link-behavior']).'"', false)
+ ->assertSee('href="https://github.com/loadinglucian/deployer-php/blob/main/docs/operations/runbooks.md" target="_blank" rel="noopener noreferrer"', false);
+});
+
it('redirects missing docs pages to the docs home', function (): void {
app()->forgetInstance(DocsPathService::class);
app()->forgetInstance(TocParserService::class);
diff --git a/tests/Fixtures/docs/docs/documentation.md b/tests/Fixtures/docs/docs/documentation.md
index 27a3321..9dc586f 100644
--- a/tests/Fixtures/docs/docs/documentation.md
+++ b/tests/Fixtures/docs/docs/documentation.md
@@ -1,6 +1,6 @@
## Guides
-- [Introduction](README.md)
+- [Introduction](../README.md)
- [Installation](installation.md)
- [Link Behavior](link-behavior.md)
diff --git a/tests/Unit/MarkdownServiceTest.php b/tests/Unit/MarkdownServiceTest.php
index 7e94925..992ec29 100644
--- a/tests/Unit/MarkdownServiceTest.php
+++ b/tests/Unit/MarkdownServiceTest.php
@@ -107,6 +107,23 @@
->toContain('Second item ');
});
+it('renders note alerts with block content', function (): void {
+ $markdown = <<<'MARKDOWN'
+> [!NOTE]
+> First note callout.
+>
+> - First item
+> - Second item
+MARKDOWN;
+
+ $html = app(MarkdownService::class)->toHtml($markdown);
+
+ expect(substr_count($html, 'dark:border-l-cyan-400'))->toBe(1)
+ ->and($html)
+ ->toContain('First item ')
+ ->toContain('Second item ');
+});
+
it('keeps empty github alert markers unchanged when there is no content', function (): void {
$markdown = <<<'MARKDOWN'
> [!IMPORTANT]