From 93b50254e562d5c4a6ecd2f3988dd13329ea0b73 Mon Sep 17 00:00:00 2001 From: Scrum4Me Agent <30029041+madhura68@users.noreply.github.com> Date: Wed, 13 May 2026 23:28:47 +0200 Subject: [PATCH] feat(caddy): add Caddyfile TextMate grammar and enable Shiki syntax highlighting Adds lib/grammars/caddyfile.json with scopes for directives, named-matchers (@prefix), placeholders, strings, and comments. Updates /caddy page to use createHighlighter with the local grammar instead of the nginx fallback. Co-Authored-By: Claude Sonnet 4.6 --- app/caddy/page.tsx | 14 ++++--- lib/grammars/caddyfile.json | 83 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 lib/grammars/caddyfile.json diff --git a/app/caddy/page.tsx b/app/caddy/page.tsx index d50604c..3e3e411 100644 --- a/app/caddy/page.tsx +++ b/app/caddy/page.tsx @@ -1,6 +1,7 @@ import { redirect } from 'next/navigation' import Link from 'next/link' -import { codeToHtml } from 'shiki' +import { createHighlighter } from 'shiki' +import caddyfileGrammar from '@/lib/grammars/caddyfile.json' import { getCurrentUser } from '@/lib/session' import { execAgent } from '@/lib/agent-client' import { parseCertList, type CertInfo } from '@/lib/parse-caddy' @@ -16,10 +17,13 @@ export default async function CaddyPage() { let configError: string | null = null try { const raw = await execAgent('caddy_show_config') - // shiki 1.29 bundelt geen caddyfile-grammar; nginx is syntactisch het - // dichtst bij (directives + braces + reverse_proxy lijkt op locations) - configHtml = await codeToHtml(raw || '# (empty)', { - lang: 'nginx', + const highlighter = await createHighlighter({ + themes: ['github-dark'], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + langs: [caddyfileGrammar as any], + }) + configHtml = highlighter.codeToHtml(raw || '# (empty)', { + lang: 'caddyfile', theme: 'github-dark', }) } catch (err) { diff --git a/lib/grammars/caddyfile.json b/lib/grammars/caddyfile.json new file mode 100644 index 0000000..5d545a1 --- /dev/null +++ b/lib/grammars/caddyfile.json @@ -0,0 +1,83 @@ +{ + "name": "caddyfile", + "scopeName": "source.Caddyfile", + "fileTypes": ["Caddyfile"], + "patterns": [ + { "include": "#comment" }, + { "include": "#site-address" }, + { "include": "#named-matcher-def" }, + { "include": "#named-matcher-ref" }, + { "include": "#directive" }, + { "include": "#placeholder" }, + { "include": "#string-double" }, + { "include": "#string-backtick" }, + { "include": "#number" }, + { "include": "#braces" } + ], + "repository": { + "comment": { + "name": "comment.line.number-sign.caddyfile", + "match": "#.*$" + }, + "site-address": { + "name": "entity.name.section.caddyfile", + "match": "^(?:https?://)?[a-zA-Z0-9][a-zA-Z0-9.*-]*(?::[0-9]+)?(?=\\s*(?:\\{|,|$))" + }, + "named-matcher-def": { + "name": "entity.other.attribute-name.caddyfile", + "match": "@[a-zA-Z_][a-zA-Z0-9_-]*(?=\\s)" + }, + "named-matcher-ref": { + "name": "entity.other.attribute-name.caddyfile", + "match": "(?<=\\s)@[a-zA-Z_][a-zA-Z0-9_-]*" + }, + "directive": { + "patterns": [ + { + "name": "keyword.control.caddyfile", + "match": "\\b(reverse_proxy|encode|file_server|handle_errors|handle_path|handle|root|header|request_header|response_header|redir|respond|rewrite|uri|try_files|php_fastcgi|push|templates|basicauth|forward_auth|map|vars|log|tls|bind|import|snippet|abort|error|static_response|acme_server|invoke)\\b" + }, + { + "name": "support.function.caddyfile", + "match": "\\b(on_demand|off|auto|internal|force|strip_prefix|replace|path_regexp|method|host|header_regexp|remote_ip|client_ip|not|query|cookie|expression|path|protocol|vars_regexp|file|jwt|geo_ip)\\b" + }, + { + "name": "keyword.other.option.caddyfile", + "match": "\\b(auto_https|admin|debug|grace_period|shutdown_delay|servers|storage|order|email|acme_ca|acme_ca_root|acme_eab|ocsp_stapling|key_type|cert_issuer|local_certs|skip_install_trust|renew_interval|check_interval|persistent_key|insecure_secrets_log|prefer_wildcard|resolvers|max_size|retention|format|output|level|sampling|include|exclude|dial|upstream|transport|lb_policy|health_uri|health_interval|health_timeout|health_status|health_body|flush_interval|buffer_requests|buffer_responses|max_buffer_size|trusted_proxies|to|from|prefix|replacements|gzip|zstd|br)\\b" + } + ] + }, + "placeholder": { + "name": "variable.other.caddyfile", + "match": "\\{(?:http\\.(?:request|response|vars|regexp|handlers)|tls|env|vars|system|time|rand|counter|uuid|path|query|header|cookie|form|file|dir|args|blocks|labels|err|http)[^}]*\\}" + }, + "string-double": { + "name": "string.quoted.double.caddyfile", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.caddyfile", + "match": "\\\\." + }, + { + "name": "variable.other.caddyfile", + "match": "\\{[^}]+\\}" + } + ] + }, + "string-backtick": { + "name": "string.quoted.other.caddyfile", + "begin": "`", + "end": "`" + }, + "number": { + "name": "constant.numeric.caddyfile", + "match": "\\b[0-9]+(?:\\.[0-9]+)?(?:s|ms|m|h|d|kb|mb|gb)?\\b" + }, + "braces": { + "name": "punctuation.section.block.caddyfile", + "match": "[{}]" + } + } +}