├── .github └── FUNDING.yml ├── .gitignore ├── Caddyfile ├── LICENSE ├── Makefile ├── README.md ├── frontend ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ └── assets │ │ ├── favicon.png │ │ ├── logo-dark.svg │ │ └── logo-light.svg ├── src │ ├── App.svelte │ ├── app.css │ ├── lib │ │ ├── Node.svelte │ │ └── Root.svelte │ ├── main.ts │ └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── go.mod ├── go.sum ├── middleware.go ├── page.go ├── port.go ├── response.go ├── screenshot.png ├── server.go ├── static ├── assets │ ├── favicon.png │ ├── index-B254VF7R.css │ ├── index-CLXqnKY8.js │ ├── logo-dark.svg │ └── logo-light.svg └── index.html ├── store.go └── xcaddy.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: abiosoft 2 | custom: 3 | - "https://buymeacoffee.com/abiosoft" 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | caddy 2 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | # example Caddyfile for debugging purposes 2 | 3 | :8888 4 | 5 | inspect 6 | 7 | route /sample { 8 | request_header +Extra-header "Extra values" 9 | header +My-header "Some header value" 10 | rewrite /sample/* /sample/secondrequest 11 | respond "Hello from caddy server" 12 | } 13 | 14 | route /assets/* { 15 | header Cache-Control "public, max-age=3600" 16 | inspect 17 | file_server { 18 | root /var/www/assets 19 | } 20 | } 21 | 22 | # Rewrite rule for custom API versioning 23 | rewrite /api/* /api/v1/* 24 | 25 | # Route for custom API with rate limiting 26 | route /api/v1/* { 27 | respond "API request received" 28 | } 29 | 30 | # Route for a custom endpoint like a webhook 31 | route /webhook/* { 32 | respond "Webhook received" 33 | } 34 | 35 | route /error* { 36 | inspect 37 | error "This is user triggered error" 503 38 | } 39 | 40 | # Custom headers for enhanced security and performance 41 | header { 42 | Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 43 | X-Content-Type-Options "nosniff" 44 | X-Frame-Options "DENY" 45 | Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self';" 46 | Referrer-Policy "no-referrer" 47 | Permissions-Policy "geolocation=(self)" 48 | } 49 | 50 | handle_errors { 51 | inspect 52 | respond {err.message} {err.status_code} 53 | } 54 | 55 | # Catch-all route for everything else 56 | route { 57 | inspect 58 | respond "404 Page Not Found" 404 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Abiola Ibrahim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: 4 | bash xcaddy.sh 5 | 6 | .PHONY: frontend 7 | frontend: 8 | cd frontend && rm -rf dist && npm run build 9 | rm -rf static/assets && cp -R frontend/dist/. static 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # caddy-inspect 2 | 3 | The Inspect plugin provides an easy way to inspect HTTP requests in Caddy. 4 | 5 | The Caddyfile is already easy to write. The Inspect plugin takes it a step further by allowing you to place breakpoints in the Caddyfile and ascertain the behaviour of Caddy. 6 | 7 | ![Screenshot](screenshot.png) 8 | 9 | ## Features 10 | 11 | - Insert a breakpoint anywhere in the Caddyfile with the `inspect` keyword. 12 | - Inspecting HTTP and Caddy contexts 13 | - Intercepting and terminating HTTP requests and responses 14 | - Works in the browser, zero setup required. 15 | 16 | ## Installation 17 | 18 | ``` 19 | xcaddy build --with github.com/abiosoft/caddy-inspect 20 | ``` 21 | 22 | ## Getting Started 23 | 24 | ### Specify breakpoints in the Caddyfile 25 | 26 | Place the `inspect` keyword anywhere an [HTTP directive](https://caddyserver.com/docs/caddyfile/directives#caddyfile-directives) is supported in the Caddyfile. The keyword can be specified multiple times. 27 | 28 | ```caddy 29 | :8080 30 | 31 | route /api { 32 | rewrite /api/* /api/v1{uri} 33 | inspect 34 | ... 35 | } 36 | ``` 37 | 38 | ### Start Caddy 39 | 40 | Caddy can be started with the `--watch` flag to autoreload the Caddyfile on each modification. 41 | 42 | ```sh 43 | caddy run --watch 44 | ``` 45 | 46 | ### Access the Inspect console 47 | 48 | Open http://localhost:2020 in the browser to access the console. 49 | 50 | > [!NOTE] 51 | > Another port would be assigned if `2020` is not available. 52 | > The URL can be confirmed in the Caddy logs. 53 | 54 | ### Enjoy 55 | 56 | Any HTTP request(s) made to a route containing the `inspect` keyword would pause the request and activate the console. 57 | 58 | ## Caveats 59 | 60 | > [!CAUTION] 61 | > This plugin is intended for development purposes only, sensitive information may be exposed if used in a production environment. 62 | 63 | - The plugin is tailored towards Caddyfile config. However, JSON config can be used with limited experience. 64 | - Due to the in-built [order of directives](https://caddyserver.com/docs/caddyfile/directives#directive-order) in Caddy, `inspect` is more predictable in a `route` block. Otherwise, it is ordered after the `encode` directive. 65 | - The information displayed are read-only and cannot be modified. 66 | - HTTP request and response bodies cannot be inspected. It is a deliberate limitation until there is a strong argument in favour. 67 | - The plugin stemmed from a personal use-case. Feedbacks would be appreciated to accommodate more use-cases. 68 | 69 | ## Attribution 70 | 71 | The Caddy logo is a trademark of Caddy Web Server. 72 | 73 | ## License 74 | 75 | MIT 76 | 77 | ## Sponsoring 78 | 79 | You can support the author by donating on [Github Sponsors](https://github.com/sponsors/abiosoft) or [Buy me a coffee](https://www.buymeacoffee.com/abiosoft). 80 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + TS + Vite 2 | 3 | This template should help get you started developing with Svelte and TypeScript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | 9 | ## Need an official Svelte framework? 10 | 11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. 12 | 13 | ## Technical considerations 14 | 15 | **Why use this over SvelteKit?** 16 | 17 | - It brings its own routing solution which might not be preferable for some users. 18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 19 | 20 | This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. 21 | 22 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. 23 | 24 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 25 | 26 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. 27 | 28 | **Why include `.vscode/extensions.json`?** 29 | 30 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. 31 | 32 | **Why enable `allowJs` in the TS template?** 33 | 34 | While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. 35 | 36 | **Why is HMR not preserving my local component state?** 37 | 38 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). 39 | 40 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. 41 | 42 | ```ts 43 | // store.ts 44 | // An extremely simple external store 45 | import { writable } from 'svelte/store' 46 | export default writable(0) 47 | ``` 48 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Caddy Inspect 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspect", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "inspect", 9 | "version": "0.0.0", 10 | "devDependencies": { 11 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 12 | "@tsconfig/svelte": "^5.0.4", 13 | "svelte": "^5.19.6", 14 | "svelte-check": "^4.1.4", 15 | "typescript": "~5.7.2", 16 | "vite": "^6.1.0" 17 | } 18 | }, 19 | "node_modules/@ampproject/remapping": { 20 | "version": "2.3.0", 21 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 22 | "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 23 | "dev": true, 24 | "license": "Apache-2.0", 25 | "dependencies": { 26 | "@jridgewell/gen-mapping": "^0.3.5", 27 | "@jridgewell/trace-mapping": "^0.3.24" 28 | }, 29 | "engines": { 30 | "node": ">=6.0.0" 31 | } 32 | }, 33 | "node_modules/@esbuild/aix-ppc64": { 34 | "version": "0.24.2", 35 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", 36 | "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", 37 | "cpu": [ 38 | "ppc64" 39 | ], 40 | "dev": true, 41 | "license": "MIT", 42 | "optional": true, 43 | "os": [ 44 | "aix" 45 | ], 46 | "engines": { 47 | "node": ">=18" 48 | } 49 | }, 50 | "node_modules/@esbuild/android-arm": { 51 | "version": "0.24.2", 52 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", 53 | "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", 54 | "cpu": [ 55 | "arm" 56 | ], 57 | "dev": true, 58 | "license": "MIT", 59 | "optional": true, 60 | "os": [ 61 | "android" 62 | ], 63 | "engines": { 64 | "node": ">=18" 65 | } 66 | }, 67 | "node_modules/@esbuild/android-arm64": { 68 | "version": "0.24.2", 69 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", 70 | "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", 71 | "cpu": [ 72 | "arm64" 73 | ], 74 | "dev": true, 75 | "license": "MIT", 76 | "optional": true, 77 | "os": [ 78 | "android" 79 | ], 80 | "engines": { 81 | "node": ">=18" 82 | } 83 | }, 84 | "node_modules/@esbuild/android-x64": { 85 | "version": "0.24.2", 86 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", 87 | "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", 88 | "cpu": [ 89 | "x64" 90 | ], 91 | "dev": true, 92 | "license": "MIT", 93 | "optional": true, 94 | "os": [ 95 | "android" 96 | ], 97 | "engines": { 98 | "node": ">=18" 99 | } 100 | }, 101 | "node_modules/@esbuild/darwin-arm64": { 102 | "version": "0.24.2", 103 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", 104 | "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", 105 | "cpu": [ 106 | "arm64" 107 | ], 108 | "dev": true, 109 | "license": "MIT", 110 | "optional": true, 111 | "os": [ 112 | "darwin" 113 | ], 114 | "engines": { 115 | "node": ">=18" 116 | } 117 | }, 118 | "node_modules/@esbuild/darwin-x64": { 119 | "version": "0.24.2", 120 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", 121 | "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", 122 | "cpu": [ 123 | "x64" 124 | ], 125 | "dev": true, 126 | "license": "MIT", 127 | "optional": true, 128 | "os": [ 129 | "darwin" 130 | ], 131 | "engines": { 132 | "node": ">=18" 133 | } 134 | }, 135 | "node_modules/@esbuild/freebsd-arm64": { 136 | "version": "0.24.2", 137 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", 138 | "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", 139 | "cpu": [ 140 | "arm64" 141 | ], 142 | "dev": true, 143 | "license": "MIT", 144 | "optional": true, 145 | "os": [ 146 | "freebsd" 147 | ], 148 | "engines": { 149 | "node": ">=18" 150 | } 151 | }, 152 | "node_modules/@esbuild/freebsd-x64": { 153 | "version": "0.24.2", 154 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", 155 | "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", 156 | "cpu": [ 157 | "x64" 158 | ], 159 | "dev": true, 160 | "license": "MIT", 161 | "optional": true, 162 | "os": [ 163 | "freebsd" 164 | ], 165 | "engines": { 166 | "node": ">=18" 167 | } 168 | }, 169 | "node_modules/@esbuild/linux-arm": { 170 | "version": "0.24.2", 171 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", 172 | "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", 173 | "cpu": [ 174 | "arm" 175 | ], 176 | "dev": true, 177 | "license": "MIT", 178 | "optional": true, 179 | "os": [ 180 | "linux" 181 | ], 182 | "engines": { 183 | "node": ">=18" 184 | } 185 | }, 186 | "node_modules/@esbuild/linux-arm64": { 187 | "version": "0.24.2", 188 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", 189 | "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", 190 | "cpu": [ 191 | "arm64" 192 | ], 193 | "dev": true, 194 | "license": "MIT", 195 | "optional": true, 196 | "os": [ 197 | "linux" 198 | ], 199 | "engines": { 200 | "node": ">=18" 201 | } 202 | }, 203 | "node_modules/@esbuild/linux-ia32": { 204 | "version": "0.24.2", 205 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", 206 | "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", 207 | "cpu": [ 208 | "ia32" 209 | ], 210 | "dev": true, 211 | "license": "MIT", 212 | "optional": true, 213 | "os": [ 214 | "linux" 215 | ], 216 | "engines": { 217 | "node": ">=18" 218 | } 219 | }, 220 | "node_modules/@esbuild/linux-loong64": { 221 | "version": "0.24.2", 222 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", 223 | "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", 224 | "cpu": [ 225 | "loong64" 226 | ], 227 | "dev": true, 228 | "license": "MIT", 229 | "optional": true, 230 | "os": [ 231 | "linux" 232 | ], 233 | "engines": { 234 | "node": ">=18" 235 | } 236 | }, 237 | "node_modules/@esbuild/linux-mips64el": { 238 | "version": "0.24.2", 239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", 240 | "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", 241 | "cpu": [ 242 | "mips64el" 243 | ], 244 | "dev": true, 245 | "license": "MIT", 246 | "optional": true, 247 | "os": [ 248 | "linux" 249 | ], 250 | "engines": { 251 | "node": ">=18" 252 | } 253 | }, 254 | "node_modules/@esbuild/linux-ppc64": { 255 | "version": "0.24.2", 256 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", 257 | "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", 258 | "cpu": [ 259 | "ppc64" 260 | ], 261 | "dev": true, 262 | "license": "MIT", 263 | "optional": true, 264 | "os": [ 265 | "linux" 266 | ], 267 | "engines": { 268 | "node": ">=18" 269 | } 270 | }, 271 | "node_modules/@esbuild/linux-riscv64": { 272 | "version": "0.24.2", 273 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", 274 | "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", 275 | "cpu": [ 276 | "riscv64" 277 | ], 278 | "dev": true, 279 | "license": "MIT", 280 | "optional": true, 281 | "os": [ 282 | "linux" 283 | ], 284 | "engines": { 285 | "node": ">=18" 286 | } 287 | }, 288 | "node_modules/@esbuild/linux-s390x": { 289 | "version": "0.24.2", 290 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", 291 | "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", 292 | "cpu": [ 293 | "s390x" 294 | ], 295 | "dev": true, 296 | "license": "MIT", 297 | "optional": true, 298 | "os": [ 299 | "linux" 300 | ], 301 | "engines": { 302 | "node": ">=18" 303 | } 304 | }, 305 | "node_modules/@esbuild/linux-x64": { 306 | "version": "0.24.2", 307 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", 308 | "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", 309 | "cpu": [ 310 | "x64" 311 | ], 312 | "dev": true, 313 | "license": "MIT", 314 | "optional": true, 315 | "os": [ 316 | "linux" 317 | ], 318 | "engines": { 319 | "node": ">=18" 320 | } 321 | }, 322 | "node_modules/@esbuild/netbsd-arm64": { 323 | "version": "0.24.2", 324 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", 325 | "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", 326 | "cpu": [ 327 | "arm64" 328 | ], 329 | "dev": true, 330 | "license": "MIT", 331 | "optional": true, 332 | "os": [ 333 | "netbsd" 334 | ], 335 | "engines": { 336 | "node": ">=18" 337 | } 338 | }, 339 | "node_modules/@esbuild/netbsd-x64": { 340 | "version": "0.24.2", 341 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", 342 | "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", 343 | "cpu": [ 344 | "x64" 345 | ], 346 | "dev": true, 347 | "license": "MIT", 348 | "optional": true, 349 | "os": [ 350 | "netbsd" 351 | ], 352 | "engines": { 353 | "node": ">=18" 354 | } 355 | }, 356 | "node_modules/@esbuild/openbsd-arm64": { 357 | "version": "0.24.2", 358 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", 359 | "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", 360 | "cpu": [ 361 | "arm64" 362 | ], 363 | "dev": true, 364 | "license": "MIT", 365 | "optional": true, 366 | "os": [ 367 | "openbsd" 368 | ], 369 | "engines": { 370 | "node": ">=18" 371 | } 372 | }, 373 | "node_modules/@esbuild/openbsd-x64": { 374 | "version": "0.24.2", 375 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", 376 | "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", 377 | "cpu": [ 378 | "x64" 379 | ], 380 | "dev": true, 381 | "license": "MIT", 382 | "optional": true, 383 | "os": [ 384 | "openbsd" 385 | ], 386 | "engines": { 387 | "node": ">=18" 388 | } 389 | }, 390 | "node_modules/@esbuild/sunos-x64": { 391 | "version": "0.24.2", 392 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", 393 | "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", 394 | "cpu": [ 395 | "x64" 396 | ], 397 | "dev": true, 398 | "license": "MIT", 399 | "optional": true, 400 | "os": [ 401 | "sunos" 402 | ], 403 | "engines": { 404 | "node": ">=18" 405 | } 406 | }, 407 | "node_modules/@esbuild/win32-arm64": { 408 | "version": "0.24.2", 409 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", 410 | "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", 411 | "cpu": [ 412 | "arm64" 413 | ], 414 | "dev": true, 415 | "license": "MIT", 416 | "optional": true, 417 | "os": [ 418 | "win32" 419 | ], 420 | "engines": { 421 | "node": ">=18" 422 | } 423 | }, 424 | "node_modules/@esbuild/win32-ia32": { 425 | "version": "0.24.2", 426 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", 427 | "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", 428 | "cpu": [ 429 | "ia32" 430 | ], 431 | "dev": true, 432 | "license": "MIT", 433 | "optional": true, 434 | "os": [ 435 | "win32" 436 | ], 437 | "engines": { 438 | "node": ">=18" 439 | } 440 | }, 441 | "node_modules/@esbuild/win32-x64": { 442 | "version": "0.24.2", 443 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", 444 | "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", 445 | "cpu": [ 446 | "x64" 447 | ], 448 | "dev": true, 449 | "license": "MIT", 450 | "optional": true, 451 | "os": [ 452 | "win32" 453 | ], 454 | "engines": { 455 | "node": ">=18" 456 | } 457 | }, 458 | "node_modules/@jridgewell/gen-mapping": { 459 | "version": "0.3.8", 460 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", 461 | "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", 462 | "dev": true, 463 | "license": "MIT", 464 | "dependencies": { 465 | "@jridgewell/set-array": "^1.2.1", 466 | "@jridgewell/sourcemap-codec": "^1.4.10", 467 | "@jridgewell/trace-mapping": "^0.3.24" 468 | }, 469 | "engines": { 470 | "node": ">=6.0.0" 471 | } 472 | }, 473 | "node_modules/@jridgewell/resolve-uri": { 474 | "version": "3.1.2", 475 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 476 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 477 | "dev": true, 478 | "license": "MIT", 479 | "engines": { 480 | "node": ">=6.0.0" 481 | } 482 | }, 483 | "node_modules/@jridgewell/set-array": { 484 | "version": "1.2.1", 485 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 486 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 487 | "dev": true, 488 | "license": "MIT", 489 | "engines": { 490 | "node": ">=6.0.0" 491 | } 492 | }, 493 | "node_modules/@jridgewell/sourcemap-codec": { 494 | "version": "1.5.0", 495 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 496 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 497 | "dev": true, 498 | "license": "MIT" 499 | }, 500 | "node_modules/@jridgewell/trace-mapping": { 501 | "version": "0.3.25", 502 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 503 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 504 | "dev": true, 505 | "license": "MIT", 506 | "dependencies": { 507 | "@jridgewell/resolve-uri": "^3.1.0", 508 | "@jridgewell/sourcemap-codec": "^1.4.14" 509 | } 510 | }, 511 | "node_modules/@rollup/rollup-android-arm-eabi": { 512 | "version": "4.34.3", 513 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.3.tgz", 514 | "integrity": "sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==", 515 | "cpu": [ 516 | "arm" 517 | ], 518 | "dev": true, 519 | "license": "MIT", 520 | "optional": true, 521 | "os": [ 522 | "android" 523 | ] 524 | }, 525 | "node_modules/@rollup/rollup-android-arm64": { 526 | "version": "4.34.3", 527 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.3.tgz", 528 | "integrity": "sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==", 529 | "cpu": [ 530 | "arm64" 531 | ], 532 | "dev": true, 533 | "license": "MIT", 534 | "optional": true, 535 | "os": [ 536 | "android" 537 | ] 538 | }, 539 | "node_modules/@rollup/rollup-darwin-arm64": { 540 | "version": "4.34.3", 541 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.3.tgz", 542 | "integrity": "sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==", 543 | "cpu": [ 544 | "arm64" 545 | ], 546 | "dev": true, 547 | "license": "MIT", 548 | "optional": true, 549 | "os": [ 550 | "darwin" 551 | ] 552 | }, 553 | "node_modules/@rollup/rollup-darwin-x64": { 554 | "version": "4.34.3", 555 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.3.tgz", 556 | "integrity": "sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==", 557 | "cpu": [ 558 | "x64" 559 | ], 560 | "dev": true, 561 | "license": "MIT", 562 | "optional": true, 563 | "os": [ 564 | "darwin" 565 | ] 566 | }, 567 | "node_modules/@rollup/rollup-freebsd-arm64": { 568 | "version": "4.34.3", 569 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.3.tgz", 570 | "integrity": "sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==", 571 | "cpu": [ 572 | "arm64" 573 | ], 574 | "dev": true, 575 | "license": "MIT", 576 | "optional": true, 577 | "os": [ 578 | "freebsd" 579 | ] 580 | }, 581 | "node_modules/@rollup/rollup-freebsd-x64": { 582 | "version": "4.34.3", 583 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.3.tgz", 584 | "integrity": "sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==", 585 | "cpu": [ 586 | "x64" 587 | ], 588 | "dev": true, 589 | "license": "MIT", 590 | "optional": true, 591 | "os": [ 592 | "freebsd" 593 | ] 594 | }, 595 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 596 | "version": "4.34.3", 597 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.3.tgz", 598 | "integrity": "sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==", 599 | "cpu": [ 600 | "arm" 601 | ], 602 | "dev": true, 603 | "license": "MIT", 604 | "optional": true, 605 | "os": [ 606 | "linux" 607 | ] 608 | }, 609 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 610 | "version": "4.34.3", 611 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.3.tgz", 612 | "integrity": "sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==", 613 | "cpu": [ 614 | "arm" 615 | ], 616 | "dev": true, 617 | "license": "MIT", 618 | "optional": true, 619 | "os": [ 620 | "linux" 621 | ] 622 | }, 623 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 624 | "version": "4.34.3", 625 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.3.tgz", 626 | "integrity": "sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==", 627 | "cpu": [ 628 | "arm64" 629 | ], 630 | "dev": true, 631 | "license": "MIT", 632 | "optional": true, 633 | "os": [ 634 | "linux" 635 | ] 636 | }, 637 | "node_modules/@rollup/rollup-linux-arm64-musl": { 638 | "version": "4.34.3", 639 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.3.tgz", 640 | "integrity": "sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==", 641 | "cpu": [ 642 | "arm64" 643 | ], 644 | "dev": true, 645 | "license": "MIT", 646 | "optional": true, 647 | "os": [ 648 | "linux" 649 | ] 650 | }, 651 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 652 | "version": "4.34.3", 653 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.3.tgz", 654 | "integrity": "sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==", 655 | "cpu": [ 656 | "loong64" 657 | ], 658 | "dev": true, 659 | "license": "MIT", 660 | "optional": true, 661 | "os": [ 662 | "linux" 663 | ] 664 | }, 665 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 666 | "version": "4.34.3", 667 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.3.tgz", 668 | "integrity": "sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==", 669 | "cpu": [ 670 | "ppc64" 671 | ], 672 | "dev": true, 673 | "license": "MIT", 674 | "optional": true, 675 | "os": [ 676 | "linux" 677 | ] 678 | }, 679 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 680 | "version": "4.34.3", 681 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.3.tgz", 682 | "integrity": "sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==", 683 | "cpu": [ 684 | "riscv64" 685 | ], 686 | "dev": true, 687 | "license": "MIT", 688 | "optional": true, 689 | "os": [ 690 | "linux" 691 | ] 692 | }, 693 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 694 | "version": "4.34.3", 695 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.3.tgz", 696 | "integrity": "sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==", 697 | "cpu": [ 698 | "s390x" 699 | ], 700 | "dev": true, 701 | "license": "MIT", 702 | "optional": true, 703 | "os": [ 704 | "linux" 705 | ] 706 | }, 707 | "node_modules/@rollup/rollup-linux-x64-gnu": { 708 | "version": "4.34.3", 709 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.3.tgz", 710 | "integrity": "sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==", 711 | "cpu": [ 712 | "x64" 713 | ], 714 | "dev": true, 715 | "license": "MIT", 716 | "optional": true, 717 | "os": [ 718 | "linux" 719 | ] 720 | }, 721 | "node_modules/@rollup/rollup-linux-x64-musl": { 722 | "version": "4.34.3", 723 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.3.tgz", 724 | "integrity": "sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==", 725 | "cpu": [ 726 | "x64" 727 | ], 728 | "dev": true, 729 | "license": "MIT", 730 | "optional": true, 731 | "os": [ 732 | "linux" 733 | ] 734 | }, 735 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 736 | "version": "4.34.3", 737 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.3.tgz", 738 | "integrity": "sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==", 739 | "cpu": [ 740 | "arm64" 741 | ], 742 | "dev": true, 743 | "license": "MIT", 744 | "optional": true, 745 | "os": [ 746 | "win32" 747 | ] 748 | }, 749 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 750 | "version": "4.34.3", 751 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.3.tgz", 752 | "integrity": "sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==", 753 | "cpu": [ 754 | "ia32" 755 | ], 756 | "dev": true, 757 | "license": "MIT", 758 | "optional": true, 759 | "os": [ 760 | "win32" 761 | ] 762 | }, 763 | "node_modules/@rollup/rollup-win32-x64-msvc": { 764 | "version": "4.34.3", 765 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.3.tgz", 766 | "integrity": "sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==", 767 | "cpu": [ 768 | "x64" 769 | ], 770 | "dev": true, 771 | "license": "MIT", 772 | "optional": true, 773 | "os": [ 774 | "win32" 775 | ] 776 | }, 777 | "node_modules/@sveltejs/vite-plugin-svelte": { 778 | "version": "5.0.3", 779 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", 780 | "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", 781 | "dev": true, 782 | "license": "MIT", 783 | "dependencies": { 784 | "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", 785 | "debug": "^4.4.0", 786 | "deepmerge": "^4.3.1", 787 | "kleur": "^4.1.5", 788 | "magic-string": "^0.30.15", 789 | "vitefu": "^1.0.4" 790 | }, 791 | "engines": { 792 | "node": "^18.0.0 || ^20.0.0 || >=22" 793 | }, 794 | "peerDependencies": { 795 | "svelte": "^5.0.0", 796 | "vite": "^6.0.0" 797 | } 798 | }, 799 | "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 800 | "version": "4.0.1", 801 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", 802 | "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", 803 | "dev": true, 804 | "license": "MIT", 805 | "dependencies": { 806 | "debug": "^4.3.7" 807 | }, 808 | "engines": { 809 | "node": "^18.0.0 || ^20.0.0 || >=22" 810 | }, 811 | "peerDependencies": { 812 | "@sveltejs/vite-plugin-svelte": "^5.0.0", 813 | "svelte": "^5.0.0", 814 | "vite": "^6.0.0" 815 | } 816 | }, 817 | "node_modules/@tsconfig/svelte": { 818 | "version": "5.0.4", 819 | "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.4.tgz", 820 | "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==", 821 | "dev": true, 822 | "license": "MIT" 823 | }, 824 | "node_modules/@types/estree": { 825 | "version": "1.0.6", 826 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 827 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 828 | "dev": true, 829 | "license": "MIT" 830 | }, 831 | "node_modules/acorn": { 832 | "version": "8.14.0", 833 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", 834 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", 835 | "dev": true, 836 | "license": "MIT", 837 | "bin": { 838 | "acorn": "bin/acorn" 839 | }, 840 | "engines": { 841 | "node": ">=0.4.0" 842 | } 843 | }, 844 | "node_modules/acorn-typescript": { 845 | "version": "1.4.13", 846 | "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", 847 | "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", 848 | "dev": true, 849 | "license": "MIT", 850 | "peerDependencies": { 851 | "acorn": ">=8.9.0" 852 | } 853 | }, 854 | "node_modules/aria-query": { 855 | "version": "5.3.2", 856 | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", 857 | "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", 858 | "dev": true, 859 | "license": "Apache-2.0", 860 | "engines": { 861 | "node": ">= 0.4" 862 | } 863 | }, 864 | "node_modules/axobject-query": { 865 | "version": "4.1.0", 866 | "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 867 | "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 868 | "dev": true, 869 | "license": "Apache-2.0", 870 | "engines": { 871 | "node": ">= 0.4" 872 | } 873 | }, 874 | "node_modules/chokidar": { 875 | "version": "4.0.3", 876 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 877 | "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", 878 | "dev": true, 879 | "license": "MIT", 880 | "dependencies": { 881 | "readdirp": "^4.0.1" 882 | }, 883 | "engines": { 884 | "node": ">= 14.16.0" 885 | }, 886 | "funding": { 887 | "url": "https://paulmillr.com/funding/" 888 | } 889 | }, 890 | "node_modules/clsx": { 891 | "version": "2.1.1", 892 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 893 | "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 894 | "dev": true, 895 | "license": "MIT", 896 | "engines": { 897 | "node": ">=6" 898 | } 899 | }, 900 | "node_modules/debug": { 901 | "version": "4.4.0", 902 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 903 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 904 | "dev": true, 905 | "license": "MIT", 906 | "dependencies": { 907 | "ms": "^2.1.3" 908 | }, 909 | "engines": { 910 | "node": ">=6.0" 911 | }, 912 | "peerDependenciesMeta": { 913 | "supports-color": { 914 | "optional": true 915 | } 916 | } 917 | }, 918 | "node_modules/deepmerge": { 919 | "version": "4.3.1", 920 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 921 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 922 | "dev": true, 923 | "license": "MIT", 924 | "engines": { 925 | "node": ">=0.10.0" 926 | } 927 | }, 928 | "node_modules/esbuild": { 929 | "version": "0.24.2", 930 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", 931 | "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", 932 | "dev": true, 933 | "hasInstallScript": true, 934 | "license": "MIT", 935 | "bin": { 936 | "esbuild": "bin/esbuild" 937 | }, 938 | "engines": { 939 | "node": ">=18" 940 | }, 941 | "optionalDependencies": { 942 | "@esbuild/aix-ppc64": "0.24.2", 943 | "@esbuild/android-arm": "0.24.2", 944 | "@esbuild/android-arm64": "0.24.2", 945 | "@esbuild/android-x64": "0.24.2", 946 | "@esbuild/darwin-arm64": "0.24.2", 947 | "@esbuild/darwin-x64": "0.24.2", 948 | "@esbuild/freebsd-arm64": "0.24.2", 949 | "@esbuild/freebsd-x64": "0.24.2", 950 | "@esbuild/linux-arm": "0.24.2", 951 | "@esbuild/linux-arm64": "0.24.2", 952 | "@esbuild/linux-ia32": "0.24.2", 953 | "@esbuild/linux-loong64": "0.24.2", 954 | "@esbuild/linux-mips64el": "0.24.2", 955 | "@esbuild/linux-ppc64": "0.24.2", 956 | "@esbuild/linux-riscv64": "0.24.2", 957 | "@esbuild/linux-s390x": "0.24.2", 958 | "@esbuild/linux-x64": "0.24.2", 959 | "@esbuild/netbsd-arm64": "0.24.2", 960 | "@esbuild/netbsd-x64": "0.24.2", 961 | "@esbuild/openbsd-arm64": "0.24.2", 962 | "@esbuild/openbsd-x64": "0.24.2", 963 | "@esbuild/sunos-x64": "0.24.2", 964 | "@esbuild/win32-arm64": "0.24.2", 965 | "@esbuild/win32-ia32": "0.24.2", 966 | "@esbuild/win32-x64": "0.24.2" 967 | } 968 | }, 969 | "node_modules/esm-env": { 970 | "version": "1.2.2", 971 | "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 972 | "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 973 | "dev": true, 974 | "license": "MIT" 975 | }, 976 | "node_modules/esrap": { 977 | "version": "1.4.3", 978 | "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz", 979 | "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==", 980 | "dev": true, 981 | "license": "MIT", 982 | "dependencies": { 983 | "@jridgewell/sourcemap-codec": "^1.4.15" 984 | } 985 | }, 986 | "node_modules/fdir": { 987 | "version": "6.4.3", 988 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", 989 | "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", 990 | "dev": true, 991 | "license": "MIT", 992 | "peerDependencies": { 993 | "picomatch": "^3 || ^4" 994 | }, 995 | "peerDependenciesMeta": { 996 | "picomatch": { 997 | "optional": true 998 | } 999 | } 1000 | }, 1001 | "node_modules/fsevents": { 1002 | "version": "2.3.3", 1003 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1004 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1005 | "dev": true, 1006 | "hasInstallScript": true, 1007 | "license": "MIT", 1008 | "optional": true, 1009 | "os": [ 1010 | "darwin" 1011 | ], 1012 | "engines": { 1013 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1014 | } 1015 | }, 1016 | "node_modules/is-reference": { 1017 | "version": "3.0.3", 1018 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 1019 | "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 1020 | "dev": true, 1021 | "license": "MIT", 1022 | "dependencies": { 1023 | "@types/estree": "^1.0.6" 1024 | } 1025 | }, 1026 | "node_modules/kleur": { 1027 | "version": "4.1.5", 1028 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 1029 | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 1030 | "dev": true, 1031 | "license": "MIT", 1032 | "engines": { 1033 | "node": ">=6" 1034 | } 1035 | }, 1036 | "node_modules/locate-character": { 1037 | "version": "3.0.0", 1038 | "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 1039 | "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 1040 | "dev": true, 1041 | "license": "MIT" 1042 | }, 1043 | "node_modules/magic-string": { 1044 | "version": "0.30.17", 1045 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1046 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1047 | "dev": true, 1048 | "license": "MIT", 1049 | "dependencies": { 1050 | "@jridgewell/sourcemap-codec": "^1.5.0" 1051 | } 1052 | }, 1053 | "node_modules/mri": { 1054 | "version": "1.2.0", 1055 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", 1056 | "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", 1057 | "dev": true, 1058 | "license": "MIT", 1059 | "engines": { 1060 | "node": ">=4" 1061 | } 1062 | }, 1063 | "node_modules/ms": { 1064 | "version": "2.1.3", 1065 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1066 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1067 | "dev": true, 1068 | "license": "MIT" 1069 | }, 1070 | "node_modules/nanoid": { 1071 | "version": "3.3.8", 1072 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 1073 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 1074 | "dev": true, 1075 | "funding": [ 1076 | { 1077 | "type": "github", 1078 | "url": "https://github.com/sponsors/ai" 1079 | } 1080 | ], 1081 | "license": "MIT", 1082 | "bin": { 1083 | "nanoid": "bin/nanoid.cjs" 1084 | }, 1085 | "engines": { 1086 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1087 | } 1088 | }, 1089 | "node_modules/picocolors": { 1090 | "version": "1.1.1", 1091 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1092 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1093 | "dev": true, 1094 | "license": "ISC" 1095 | }, 1096 | "node_modules/postcss": { 1097 | "version": "8.5.1", 1098 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", 1099 | "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", 1100 | "dev": true, 1101 | "funding": [ 1102 | { 1103 | "type": "opencollective", 1104 | "url": "https://opencollective.com/postcss/" 1105 | }, 1106 | { 1107 | "type": "tidelift", 1108 | "url": "https://tidelift.com/funding/github/npm/postcss" 1109 | }, 1110 | { 1111 | "type": "github", 1112 | "url": "https://github.com/sponsors/ai" 1113 | } 1114 | ], 1115 | "license": "MIT", 1116 | "dependencies": { 1117 | "nanoid": "^3.3.8", 1118 | "picocolors": "^1.1.1", 1119 | "source-map-js": "^1.2.1" 1120 | }, 1121 | "engines": { 1122 | "node": "^10 || ^12 || >=14" 1123 | } 1124 | }, 1125 | "node_modules/readdirp": { 1126 | "version": "4.1.1", 1127 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", 1128 | "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", 1129 | "dev": true, 1130 | "license": "MIT", 1131 | "engines": { 1132 | "node": ">= 14.18.0" 1133 | }, 1134 | "funding": { 1135 | "type": "individual", 1136 | "url": "https://paulmillr.com/funding/" 1137 | } 1138 | }, 1139 | "node_modules/rollup": { 1140 | "version": "4.34.3", 1141 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.3.tgz", 1142 | "integrity": "sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==", 1143 | "dev": true, 1144 | "license": "MIT", 1145 | "dependencies": { 1146 | "@types/estree": "1.0.6" 1147 | }, 1148 | "bin": { 1149 | "rollup": "dist/bin/rollup" 1150 | }, 1151 | "engines": { 1152 | "node": ">=18.0.0", 1153 | "npm": ">=8.0.0" 1154 | }, 1155 | "optionalDependencies": { 1156 | "@rollup/rollup-android-arm-eabi": "4.34.3", 1157 | "@rollup/rollup-android-arm64": "4.34.3", 1158 | "@rollup/rollup-darwin-arm64": "4.34.3", 1159 | "@rollup/rollup-darwin-x64": "4.34.3", 1160 | "@rollup/rollup-freebsd-arm64": "4.34.3", 1161 | "@rollup/rollup-freebsd-x64": "4.34.3", 1162 | "@rollup/rollup-linux-arm-gnueabihf": "4.34.3", 1163 | "@rollup/rollup-linux-arm-musleabihf": "4.34.3", 1164 | "@rollup/rollup-linux-arm64-gnu": "4.34.3", 1165 | "@rollup/rollup-linux-arm64-musl": "4.34.3", 1166 | "@rollup/rollup-linux-loongarch64-gnu": "4.34.3", 1167 | "@rollup/rollup-linux-powerpc64le-gnu": "4.34.3", 1168 | "@rollup/rollup-linux-riscv64-gnu": "4.34.3", 1169 | "@rollup/rollup-linux-s390x-gnu": "4.34.3", 1170 | "@rollup/rollup-linux-x64-gnu": "4.34.3", 1171 | "@rollup/rollup-linux-x64-musl": "4.34.3", 1172 | "@rollup/rollup-win32-arm64-msvc": "4.34.3", 1173 | "@rollup/rollup-win32-ia32-msvc": "4.34.3", 1174 | "@rollup/rollup-win32-x64-msvc": "4.34.3", 1175 | "fsevents": "~2.3.2" 1176 | } 1177 | }, 1178 | "node_modules/sade": { 1179 | "version": "1.8.1", 1180 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", 1181 | "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", 1182 | "dev": true, 1183 | "license": "MIT", 1184 | "dependencies": { 1185 | "mri": "^1.1.0" 1186 | }, 1187 | "engines": { 1188 | "node": ">=6" 1189 | } 1190 | }, 1191 | "node_modules/source-map-js": { 1192 | "version": "1.2.1", 1193 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1194 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1195 | "dev": true, 1196 | "license": "BSD-3-Clause", 1197 | "engines": { 1198 | "node": ">=0.10.0" 1199 | } 1200 | }, 1201 | "node_modules/svelte": { 1202 | "version": "5.19.7", 1203 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.7.tgz", 1204 | "integrity": "sha512-I0UUp2MpB5gF8aqHJVklOcRcoLgQNnBolSwLMMqDepE9gVwmGeYBmJp1/obzae72QpxdPIymA4AunIm2x70LBg==", 1205 | "dev": true, 1206 | "license": "MIT", 1207 | "dependencies": { 1208 | "@ampproject/remapping": "^2.3.0", 1209 | "@jridgewell/sourcemap-codec": "^1.5.0", 1210 | "@types/estree": "^1.0.5", 1211 | "acorn": "^8.12.1", 1212 | "acorn-typescript": "^1.4.13", 1213 | "aria-query": "^5.3.1", 1214 | "axobject-query": "^4.1.0", 1215 | "clsx": "^2.1.1", 1216 | "esm-env": "^1.2.1", 1217 | "esrap": "^1.4.3", 1218 | "is-reference": "^3.0.3", 1219 | "locate-character": "^3.0.0", 1220 | "magic-string": "^0.30.11", 1221 | "zimmerframe": "^1.1.2" 1222 | }, 1223 | "engines": { 1224 | "node": ">=18" 1225 | } 1226 | }, 1227 | "node_modules/svelte-check": { 1228 | "version": "4.1.4", 1229 | "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", 1230 | "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", 1231 | "dev": true, 1232 | "license": "MIT", 1233 | "dependencies": { 1234 | "@jridgewell/trace-mapping": "^0.3.25", 1235 | "chokidar": "^4.0.1", 1236 | "fdir": "^6.2.0", 1237 | "picocolors": "^1.0.0", 1238 | "sade": "^1.7.4" 1239 | }, 1240 | "bin": { 1241 | "svelte-check": "bin/svelte-check" 1242 | }, 1243 | "engines": { 1244 | "node": ">= 18.0.0" 1245 | }, 1246 | "peerDependencies": { 1247 | "svelte": "^4.0.0 || ^5.0.0-next.0", 1248 | "typescript": ">=5.0.0" 1249 | } 1250 | }, 1251 | "node_modules/typescript": { 1252 | "version": "5.7.3", 1253 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", 1254 | "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", 1255 | "dev": true, 1256 | "license": "Apache-2.0", 1257 | "bin": { 1258 | "tsc": "bin/tsc", 1259 | "tsserver": "bin/tsserver" 1260 | }, 1261 | "engines": { 1262 | "node": ">=14.17" 1263 | } 1264 | }, 1265 | "node_modules/vite": { 1266 | "version": "6.1.0", 1267 | "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", 1268 | "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", 1269 | "dev": true, 1270 | "license": "MIT", 1271 | "dependencies": { 1272 | "esbuild": "^0.24.2", 1273 | "postcss": "^8.5.1", 1274 | "rollup": "^4.30.1" 1275 | }, 1276 | "bin": { 1277 | "vite": "bin/vite.js" 1278 | }, 1279 | "engines": { 1280 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1281 | }, 1282 | "funding": { 1283 | "url": "https://github.com/vitejs/vite?sponsor=1" 1284 | }, 1285 | "optionalDependencies": { 1286 | "fsevents": "~2.3.3" 1287 | }, 1288 | "peerDependencies": { 1289 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1290 | "jiti": ">=1.21.0", 1291 | "less": "*", 1292 | "lightningcss": "^1.21.0", 1293 | "sass": "*", 1294 | "sass-embedded": "*", 1295 | "stylus": "*", 1296 | "sugarss": "*", 1297 | "terser": "^5.16.0", 1298 | "tsx": "^4.8.1", 1299 | "yaml": "^2.4.2" 1300 | }, 1301 | "peerDependenciesMeta": { 1302 | "@types/node": { 1303 | "optional": true 1304 | }, 1305 | "jiti": { 1306 | "optional": true 1307 | }, 1308 | "less": { 1309 | "optional": true 1310 | }, 1311 | "lightningcss": { 1312 | "optional": true 1313 | }, 1314 | "sass": { 1315 | "optional": true 1316 | }, 1317 | "sass-embedded": { 1318 | "optional": true 1319 | }, 1320 | "stylus": { 1321 | "optional": true 1322 | }, 1323 | "sugarss": { 1324 | "optional": true 1325 | }, 1326 | "terser": { 1327 | "optional": true 1328 | }, 1329 | "tsx": { 1330 | "optional": true 1331 | }, 1332 | "yaml": { 1333 | "optional": true 1334 | } 1335 | } 1336 | }, 1337 | "node_modules/vitefu": { 1338 | "version": "1.0.5", 1339 | "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz", 1340 | "integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==", 1341 | "dev": true, 1342 | "license": "MIT", 1343 | "workspaces": [ 1344 | "tests/deps/*", 1345 | "tests/projects/*" 1346 | ], 1347 | "peerDependencies": { 1348 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" 1349 | }, 1350 | "peerDependenciesMeta": { 1351 | "vite": { 1352 | "optional": true 1353 | } 1354 | } 1355 | }, 1356 | "node_modules/zimmerframe": { 1357 | "version": "1.1.2", 1358 | "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", 1359 | "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", 1360 | "dev": true, 1361 | "license": "MIT" 1362 | } 1363 | } 1364 | } 1365 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inspect", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 14 | "@tsconfig/svelte": "^5.0.4", 15 | "svelte": "^5.19.6", 16 | "svelte-check": "^4.1.4", 17 | "typescript": "~5.7.2", 18 | "vite": "^6.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abiosoft/caddy-inspect/96cdb1dfb122f79913d60ecb34030d302a4f4ec1/frontend/public/assets/favicon.png -------------------------------------------------------------------------------- /frontend/public/assets/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /frontend/public/assets/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /frontend/src/App.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg-color: #ffffff; 3 | --text-color: #000000; 4 | --border-color: #ddd; 5 | --button-bg-color: #007bff; 6 | --button-hover-bg-color: #0056b3; 7 | --button-active-bg-color: #004085; 8 | --button-danger-color: #c73636; 9 | --button-hover-danger-color: #a62d2d; 10 | --calm-color: #7e7e7e; 11 | --input-focus-border-color: #007bff; 12 | --input-focus-shadow-color: rgba(0, 123, 255, 0.5); 13 | --snippet-highlight-color: rgba(150, 150, 150, 0.5); 14 | } 15 | 16 | .logo { 17 | content: url("/assets/logo-light.svg"); 18 | height: 30px; 19 | margin-right: 5px; 20 | } 21 | 22 | @media (prefers-color-scheme: dark) { 23 | :root { 24 | --bg-color: #121212; 25 | --text-color: #ffffff; 26 | --border-color: #333; 27 | --button-bg-color: #1a73e8; 28 | --button-hover-bg-color: #135ba1; 29 | --button-active-bg-color: #0e447a; 30 | --button-danger-color: #a0222f; 31 | --button-hover-danger-color: #881c28; 32 | --calm-color: #7e7e7e; 33 | --input-focus-border-color: #1a73e8; 34 | --input-focus-shadow-color: rgba(26, 115, 232, 0.5); 35 | --snippet-highlight-color: rgba(150, 150, 150, 0.5); 36 | } 37 | 38 | .logo { 39 | content: url("/assets/logo-dark.svg"); 40 | } 41 | 42 | } 43 | 44 | body { 45 | font-family: Arial, sans-serif; 46 | margin: 0; 47 | padding: 0; 48 | box-sizing: border-box; 49 | background-color: var(--bg-color); 50 | color: var(--text-color); 51 | } 52 | 53 | .inspect { 54 | padding: 10px; 55 | } 56 | 57 | button { 58 | padding: 10px 20px; 59 | font-size: 16px; 60 | color: white; 61 | background-color: var(--button-bg-color); 62 | border: none; 63 | border-radius: 4px; 64 | cursor: pointer; 65 | } 66 | 67 | button:hover { 68 | background-color: var(--button-hover-bg-color); 69 | } 70 | 71 | button:active { 72 | background-color: var(--button-active-bg-color); 73 | transform: translateY(1px); 74 | } 75 | 76 | button.danger { 77 | background-color: var(--button-danger-color); 78 | } 79 | 80 | button.danger:hover { 81 | background-color: var(--button-hover-danger-color); 82 | } 83 | 84 | 85 | input[type="text"] { 86 | width: 100%; 87 | padding: 10px; 88 | font-size: 14px; 89 | border: 1px solid var(--border-color); 90 | border-radius: 4px; 91 | background-color: var(--textarea-bg-color); 92 | color: var(--text-color); 93 | } 94 | 95 | input[type="text"]:focus { 96 | outline: none; 97 | border-color: var(--input-focus-border-color); 98 | box-shadow: 0 0 4px var(--input-focus-shadow-color); 99 | } 100 | 101 | .loading { 102 | display: flex; 103 | justify-content: center; 104 | align-items: center; 105 | height: 30vh; 106 | font-size: 20px; 107 | color: var(--text-color); 108 | } 109 | -------------------------------------------------------------------------------- /frontend/src/lib/Node.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |
23 | {#if hasChildren} 24 | {isOpen ? "▼" : "▶"} 25 | {:else} 26 |     27 | {/if} 28 |
29 | {#if itemIsArray} 30 | 31 | {:else} 32 | {key} 33 | {#if !hasChildren} 34 | 35 | {/if} 36 | {/if} 37 |
38 |
39 | 40 | {#if hasChildren && isOpen} 41 |
42 | {#if nodeIsArray} 43 | {#each node as node} 44 | 45 | {/each} 46 | {:else} 47 | {#each nodeEntries as [key, node]} 48 | 49 | {/each} 50 | {/if} 51 |
52 | {/if} 53 |
54 | 55 | 108 | -------------------------------------------------------------------------------- /frontend/src/lib/Root.svelte: -------------------------------------------------------------------------------- 1 | 126 | 127 |
128 |
129 | Inspect 130 |
131 |
132 | {#if hasData} 133 |
134 | 135 | 141 | 142 | {#if !hasResponse} 143 | 144 | 150 | 151 | {/if} 152 | 153 | 160 | 161 |
162 |
163 | {#if hasSourceFile} 164 |
165 | {sourceFile}:{sourceLine} 166 |
167 |
168 | {#each sources as line, i} 169 |
175 | 176 | {#if i + sourceLineStart == sourceLine} 177 | {#if hasResponse} 178 | ↑ 179 | {:else} 180 | ↓ 181 | {/if} 182 | ◉ 183 | {/if} 184 | 185 | {i + sourceLineStart} 188 | {line} 189 |
190 | {:else} 191 |   192 | {/each} 193 |
194 | {/if} 195 | 196 | {#each treeData as [key, node]} 197 | 198 | {/each} 199 |
200 | {:else} 201 |
Waiting for request...
202 | {/if} 203 |
204 | 205 | 310 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'svelte' 2 | import './app.css' 3 | import App from './App.svelte' 4 | 5 | const app = mount(App, { 6 | target: document.getElementById('app')!, 7 | }) 8 | 9 | export default app 10 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /frontend/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force" 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | server: { 8 | host: '0.0.0.0', 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/abiosoft/caddy-inspect 2 | 3 | go 1.22.3 4 | 5 | toolchain go1.23.4 6 | 7 | require github.com/caddyserver/caddy/v2 v2.9.1 8 | 9 | require ( 10 | dario.cat/mergo v1.0.1 // indirect 11 | filippo.io/edwards25519 v1.1.0 // indirect 12 | github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect 13 | github.com/Masterminds/goutils v1.1.1 // indirect 14 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 15 | github.com/Masterminds/sprig/v3 v3.3.0 // indirect 16 | github.com/Microsoft/go-winio v0.6.0 // indirect 17 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 18 | github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/caddyserver/certmagic v0.21.6 // indirect 21 | github.com/caddyserver/zerossl v0.1.3 // indirect 22 | github.com/cespare/xxhash v1.1.0 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/chzyer/readline v1.5.1 // indirect 25 | github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect 26 | github.com/dgraph-io/badger v1.6.2 // indirect 27 | github.com/dgraph-io/badger/v2 v2.2007.4 // indirect 28 | github.com/dgraph-io/ristretto v0.1.0 // indirect 29 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect 30 | github.com/dustin/go-humanize v1.0.1 // indirect 31 | github.com/francoispqt/gojay v1.2.13 // indirect 32 | github.com/go-jose/go-jose/v3 v3.0.3 // indirect 33 | github.com/go-kit/kit v0.13.0 // indirect 34 | github.com/go-kit/log v0.2.1 // indirect 35 | github.com/go-logfmt/logfmt v0.6.0 // indirect 36 | github.com/go-sql-driver/mysql v1.7.1 // indirect 37 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 38 | github.com/golang/glog v1.2.2 // indirect 39 | github.com/golang/protobuf v1.5.4 // indirect 40 | github.com/golang/snappy v0.0.4 // indirect 41 | github.com/google/cel-go v0.21.0 // indirect 42 | github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect 43 | github.com/google/uuid v1.6.0 // indirect 44 | github.com/huandu/xstrings v1.5.0 // indirect 45 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 46 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 47 | github.com/jackc/pgconn v1.14.3 // indirect 48 | github.com/jackc/pgio v1.0.0 // indirect 49 | github.com/jackc/pgpassfile v1.0.0 // indirect 50 | github.com/jackc/pgproto3/v2 v2.3.3 // indirect 51 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 52 | github.com/jackc/pgtype v1.14.0 // indirect 53 | github.com/jackc/pgx/v4 v4.18.3 // indirect 54 | github.com/klauspost/compress v1.17.11 // indirect 55 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 56 | github.com/libdns/libdns v0.2.2 // indirect 57 | github.com/manifoldco/promptui v0.9.0 // indirect 58 | github.com/mattn/go-colorable v0.1.13 // indirect 59 | github.com/mattn/go-isatty v0.0.20 // indirect 60 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 61 | github.com/mholt/acmez/v3 v3.0.0 // indirect 62 | github.com/miekg/dns v1.1.62 // indirect 63 | github.com/mitchellh/copystructure v1.2.0 // indirect 64 | github.com/mitchellh/go-ps v1.0.0 // indirect 65 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 66 | github.com/onsi/ginkgo/v2 v2.13.2 // indirect 67 | github.com/pkg/errors v0.9.1 // indirect 68 | github.com/prometheus/client_golang v1.19.1 // indirect 69 | github.com/prometheus/client_model v0.5.0 // indirect 70 | github.com/prometheus/common v0.48.0 // indirect 71 | github.com/prometheus/procfs v0.12.0 // indirect 72 | github.com/quic-go/qpack v0.5.1 // indirect 73 | github.com/quic-go/quic-go v0.48.2 // indirect 74 | github.com/rs/xid v1.5.0 // indirect 75 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 76 | github.com/shopspring/decimal v1.4.0 // indirect 77 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 78 | github.com/slackhq/nebula v1.6.1 // indirect 79 | github.com/smallstep/certificates v0.26.1 // indirect 80 | github.com/smallstep/nosql v0.6.1 // indirect 81 | github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect 82 | github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect 83 | github.com/smallstep/truststore v0.13.0 // indirect 84 | github.com/spf13/cast v1.7.0 // indirect 85 | github.com/spf13/cobra v1.8.1 // indirect 86 | github.com/spf13/pflag v1.0.5 // indirect 87 | github.com/stoewer/go-strcase v1.2.0 // indirect 88 | github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect 89 | github.com/urfave/cli v1.22.14 // indirect 90 | github.com/zeebo/blake3 v0.2.4 // indirect 91 | go.etcd.io/bbolt v1.3.9 // indirect 92 | go.step.sm/cli-utils v0.9.0 // indirect 93 | go.step.sm/crypto v0.45.0 // indirect 94 | go.step.sm/linkedca v0.20.1 // indirect 95 | go.uber.org/automaxprocs v1.6.0 // indirect 96 | go.uber.org/mock v0.4.0 // indirect 97 | go.uber.org/multierr v1.11.0 // indirect 98 | go.uber.org/zap v1.27.0 // indirect 99 | go.uber.org/zap/exp v0.3.0 // indirect 100 | golang.org/x/crypto v0.31.0 // indirect 101 | golang.org/x/crypto/x509roots/fallback v0.0.0-20241104001025-71ed71b4faf9 // indirect 102 | golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect 103 | golang.org/x/mod v0.18.0 // indirect 104 | golang.org/x/net v0.33.0 // indirect 105 | golang.org/x/sync v0.10.0 // indirect 106 | golang.org/x/sys v0.28.0 // indirect 107 | golang.org/x/term v0.27.0 // indirect 108 | golang.org/x/text v0.21.0 // indirect 109 | golang.org/x/time v0.7.0 // indirect 110 | golang.org/x/tools v0.22.0 // indirect 111 | google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect 112 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect 113 | google.golang.org/grpc v1.67.1 // indirect 114 | google.golang.org/protobuf v1.35.1 // indirect 115 | gopkg.in/yaml.v3 v3.0.1 // indirect 116 | howett.net/plist v1.0.0 // indirect 117 | ) 118 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/caddyserver/caddy/v2" 11 | "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" 12 | "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" 13 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" 14 | "go.uber.org/zap" 15 | ) 16 | 17 | func init() { 18 | caddy.RegisterModule(Middleware{}) 19 | httpcaddyfile.RegisterHandlerDirective("inspect", parseCaddyfile) 20 | httpcaddyfile.RegisterDirectiveOrder("inspect", httpcaddyfile.After, "encode") 21 | } 22 | 23 | var errRequestTerminated = errors.New("request terminated") 24 | 25 | // Middleware implements an HTTP handler that 26 | // inspects the current request. 27 | type Middleware struct { 28 | logger *zap.Logger 29 | ctx caddy.Context 30 | 31 | Key string 32 | snippetDetails 33 | } 34 | 35 | // CaddyModule returns the Caddy module information. 36 | func (Middleware) CaddyModule() caddy.ModuleInfo { 37 | return caddy.ModuleInfo{ 38 | ID: "http.handlers.inspect", 39 | New: func() caddy.Module { return new(Middleware) }, 40 | } 41 | } 42 | 43 | // Provision implements caddy.Provisioner. 44 | func (m *Middleware) Provision(ctx caddy.Context) error { 45 | m.logger = ctx.Logger() 46 | m.ctx = ctx 47 | m.snippetDetails = configMap.get(m.Key) 48 | 49 | if err := setUpServer(ctx); err != nil { 50 | return err 51 | } 52 | 53 | server := getServerInstance() 54 | port, started, err := server.start() 55 | if err != nil { 56 | return fmt.Errorf("error occured during provision: %w", err) 57 | } 58 | 59 | // print the server listen address if not previously running 60 | if !started { 61 | m.logger.Info(fmt.Sprintf("inspect console available at http://127.0.0.1:%d", port)) 62 | } 63 | 64 | return nil 65 | } 66 | 67 | // Validate implements caddy.Validator. 68 | func (m *Middleware) Validate() error { 69 | return nil 70 | } 71 | 72 | // ServeHTTP implements caddyhttp.MiddlewareHandler. 73 | func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { 74 | logger := m.logger.With(zap.String("file", m.file), zap.Int("line", m.line)) 75 | 76 | logger.Info("inspecting") 77 | 78 | server := getServerInstance() 79 | action := server.handle(m, nil, r) 80 | 81 | switch action { 82 | case requestActionResume: 83 | logger.Info("resumed") 84 | return next.ServeHTTP(w, r) 85 | 86 | case requestActionStep: 87 | logger.Info("proceeding to response") 88 | 89 | // process middleware chain 90 | err := next.ServeHTTP(w, r) 91 | 92 | // handle the updated request details 93 | action := server.handle(m, w, r) 94 | if err != nil { 95 | return err 96 | } 97 | if action == requestActionResume { 98 | return nil 99 | } 100 | 101 | // request stopped 102 | fallthrough 103 | case requestActionStop: 104 | logger.Info("stopped") 105 | } 106 | 107 | return caddyhttp.Error(http.StatusServiceUnavailable, errRequestTerminated) 108 | } 109 | 110 | // UnmarshalCaddyfile implements caddyfile.Unmarshaler. 111 | func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { 112 | d.Next() // consume directive name 113 | 114 | // persist the file name and line number 115 | var s snippetDetails 116 | s.file = d.File() 117 | s.line = d.Line() 118 | 119 | m.Key = configKey(s.file, s.line) 120 | 121 | if s.file != "" { 122 | if src, lineStart, err := loadCaddyfileSnippet(s.file, s.line); err == nil { 123 | s.source = src 124 | s.sourceLineStart = lineStart 125 | } 126 | 127 | configMap.set(m.Key, s) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | // parseCaddyfile unmarshals tokens from h into a new Middleware. 134 | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { 135 | var m Middleware 136 | err := m.UnmarshalCaddyfile(h.Dispenser) 137 | return m, err 138 | } 139 | 140 | // loadCaddyfileSnippet reads 5 lines within context in the loaded Caddyfile. 141 | func loadCaddyfileSnippet(file string, line int) (snippet []string, firstLine int, err error) { 142 | f, err := os.ReadFile(file) 143 | if err != nil { 144 | return nil, 0, fmt.Errorf("error reading Caddyfile: %w", err) 145 | } 146 | 147 | lines := strings.Split(string(f), "\n") 148 | if len(lines) < line { 149 | return nil, 0, fmt.Errorf("invalid line: %d", line) 150 | } 151 | 152 | lineIndex := line - 1 // slice index 153 | 154 | start := lineIndex - 2 155 | if start < 0 { 156 | start = 0 157 | } 158 | 159 | end := (lineIndex + 1) + 2 // the extra 1 is for ending index which is not inclusive. 160 | if end > len(lines) { 161 | end = len(lines) 162 | } 163 | 164 | return lines[start:end], start + 1, nil 165 | } 166 | 167 | type snippetDetails struct { 168 | file string 169 | line int 170 | source []string 171 | sourceLineStart int // for lack of a better name 172 | } 173 | 174 | func (c snippetDetails) valid() bool { 175 | return c.file != "" && c.line > 0 176 | } 177 | 178 | // Interface guards 179 | var ( 180 | _ caddy.Provisioner = (*Middleware)(nil) 181 | _ caddy.Validator = (*Middleware)(nil) 182 | _ caddyhttp.MiddlewareHandler = (*Middleware)(nil) 183 | _ caddyfile.Unmarshaler = (*Middleware)(nil) 184 | ) 185 | -------------------------------------------------------------------------------- /page.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "embed" 5 | ) 6 | 7 | //go:embed static/assets static/index.html 8 | var staticFS embed.FS 9 | -------------------------------------------------------------------------------- /port.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | const httpServerListenPort = 2020 9 | 10 | // findAvailablePort returns an available port on the host machine. 11 | // it attempts port 2020 up till 2029 12 | func findAvailablePort() (int, error) { 13 | allocatePort := func(port int) error { 14 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) 15 | if err != nil { 16 | return fmt.Errorf("error picking an available port: %w", err) 17 | } 18 | 19 | if err := listener.Close(); err != nil { 20 | return fmt.Errorf("error closing temporary port listener: %w", err) 21 | } 22 | 23 | return nil 24 | } 25 | 26 | var err error 27 | for i := 0; i < 10; i++ { 28 | port := httpServerListenPort + i 29 | err = allocatePort(port) 30 | if err == nil { 31 | return port, nil 32 | } 33 | } 34 | 35 | return 0, err 36 | } 37 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "runtime" 7 | 8 | "github.com/caddyserver/caddy/v2" 9 | "github.com/caddyserver/caddy/v2/modules/caddyhttp" 10 | ) 11 | 12 | type Response struct { 13 | URL string `json:"url,omitempty"` 14 | Method string `json:"method,omitempty"` 15 | Host string `json:"host,omitempty"` 16 | RequestHeaders http.Header `json:"request_headers,omitempty"` 17 | ResponseHeaders http.Header `json:"response_headers,omitempty"` 18 | RemoteAddress string `json:"remote_address,omitempty"` 19 | Form string `json:"form,omitempty"` 20 | Proto string `json:"proto,omitempty"` 21 | UserAgent string `json:"user_agent,omitempty"` 22 | Referer string `json:"referer,omitempty"` 23 | ContentLength int64 `json:"content_length,omitempty"` 24 | BasicAuth *struct { 25 | Username string `json:"username,omitempty"` 26 | Password string `json:"password,omitempty"` 27 | } `json:"basic_auth,omitempty"` 28 | Cookies []*http.Cookie `json:"cookies,omitempty"` 29 | CaddyVersion string `json:"caddy_version,omitempty"` 30 | CaddyContext struct { 31 | Variables map[string]any `json:"Variables,omitempty"` 32 | Modules []string `json:"Modules,omitempty"` 33 | Error any `json:"Error,omitempty"` 34 | } `json:"caddy_context,omitempty"` 35 | CaddyModules []string `json:"caddy_modules,omitempty"` 36 | Caddyfile *struct { 37 | File string `json:"file,omitempty"` 38 | Line int `json:"line,omitempty"` 39 | Source []string `json:"source,omitempty"` 40 | SourceLineStart int `json:"source_line_start,omitempty"` 41 | } `json:"caddyfile,omitempty"` 42 | MemoryUsage string `json:"memory_usage,omitempty"` 43 | responseMode bool 44 | } 45 | 46 | func buildResponse(m Middleware, w http.ResponseWriter, r *http.Request) (d Response) { 47 | d.URL = r.URL.String() 48 | d.Method = r.Method 49 | d.Host = r.Host 50 | d.RequestHeaders = r.Header 51 | d.RemoteAddress = r.RemoteAddr 52 | d.Form = r.Form.Encode() 53 | d.Proto = r.Proto 54 | d.UserAgent = r.UserAgent() 55 | d.Referer = r.Referer() 56 | d.ContentLength = r.ContentLength 57 | d.Cookies = r.Cookies() 58 | 59 | if w != nil { 60 | d.ResponseHeaders = w.Header() 61 | d.responseMode = true 62 | } 63 | 64 | username, password, _ := r.BasicAuth() 65 | if username != "" || password != "" { 66 | d.BasicAuth = &struct { 67 | Username string `json:"username,omitempty"` 68 | Password string `json:"password,omitempty"` 69 | }{Username: username, Password: password} 70 | } 71 | 72 | d.CaddyModules = caddy.Modules() 73 | for _, m := range m.ctx.Modules() { 74 | d.CaddyContext.Modules = append(d.CaddyContext.Modules, m.CaddyModule().String()) 75 | } 76 | 77 | _, d.CaddyVersion = caddy.Version() 78 | 79 | if m.valid() { 80 | d.Caddyfile = &struct { 81 | File string `json:"file,omitempty"` 82 | Line int `json:"line,omitempty"` 83 | Source []string `json:"source,omitempty"` 84 | SourceLineStart int `json:"source_line_start,omitempty"` 85 | }{ 86 | File: m.file, 87 | Line: m.line, 88 | Source: m.source, 89 | SourceLineStart: m.sourceLineStart, 90 | } 91 | } 92 | 93 | vars, _ := r.Context().Value(caddyhttp.VarsCtxKey).(map[string]any) 94 | d.CaddyContext.Variables = vars 95 | 96 | if err, _ := r.Context().Value(caddyhttp.ErrorCtxKey).(error); err != nil { 97 | d.CaddyContext.Error = err.Error() 98 | 99 | // if it is an handler error, set specific error 100 | if err, ok := err.(caddyhttp.HandlerError); ok { 101 | herr := handlerErr{HandlerError: err} 102 | if err := err.Err; err != nil { 103 | herr.Err = err.Error() 104 | } 105 | d.CaddyContext.Error = herr 106 | } 107 | } 108 | 109 | d.MemoryUsage = getMemUsage().String() 110 | 111 | return 112 | } 113 | 114 | type handlerErr struct { 115 | Err string 116 | caddyhttp.HandlerError 117 | } 118 | 119 | type memNum uint64 120 | 121 | func (m memNum) String() string { return fmt.Sprintf("%d MiB", m/1024/1024) } 122 | 123 | func getMemUsage() memNum { 124 | var m runtime.MemStats 125 | runtime.ReadMemStats(&m) 126 | 127 | return memNum(m.Sys) 128 | } 129 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abiosoft/caddy-inspect/96cdb1dfb122f79913d60ecb34030d302a4f4ec1/screenshot.png -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "net/http" 8 | "sync" 9 | "time" 10 | 11 | "github.com/caddyserver/caddy/v2" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | type Server struct { 16 | // http handler 17 | request *Response 18 | requestID int64 19 | action chan requestAction 20 | 21 | // static handler 22 | static http.Handler 23 | 24 | // instance 25 | port int 26 | logger *zap.Logger 27 | err error 28 | 29 | instanceMutex sync.Mutex 30 | requestMutex sync.RWMutex 31 | } 32 | 33 | type requestAction = int 34 | 35 | const ( 36 | requestActionResume requestAction = iota 37 | requestActionStep 38 | requestActionStop 39 | ) 40 | 41 | func (s *Server) hasRequest() bool { 42 | s.requestMutex.RLock() 43 | defer s.requestMutex.RUnlock() 44 | 45 | return s.request != nil 46 | } 47 | 48 | func (s *Server) hasResponse() bool { 49 | s.requestMutex.RLock() 50 | defer s.requestMutex.RUnlock() 51 | 52 | return s.request != nil && s.request.responseMode 53 | } 54 | 55 | // start starts the server. If the server is already running, nothing is done. 56 | // 57 | // returns the port the server is listening on, 58 | // whether the server has been previously started, 59 | // and an error if any 60 | func (s *Server) start() (port int, started bool, err error) { 61 | s.instanceMutex.Lock() 62 | defer s.instanceMutex.Unlock() 63 | 64 | // server already started 65 | if s.port > 0 { 66 | return s.port, true, nil 67 | } 68 | 69 | port, err = findAvailablePort() 70 | if err != nil { 71 | return port, false, fmt.Errorf("cannot start caddy-inspect server: %w", err) 72 | } 73 | s.port = port 74 | 75 | errChan := make(chan error) 76 | go func(server *Server) { 77 | errChan <- http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", port), server) 78 | s.instanceMutex.Lock() 79 | s.err = <-errChan 80 | s.instanceMutex.Unlock() 81 | }(s) 82 | 83 | return port, false, nil 84 | } 85 | 86 | func (s *Server) handle(m Middleware, w http.ResponseWriter, r *http.Request) requestAction { 87 | s.instanceMutex.Lock() 88 | defer s.instanceMutex.Unlock() 89 | 90 | request := buildResponse(m, w, r) 91 | 92 | s.requestMutex.Lock() 93 | s.request = &request 94 | s.requestID = time.Now().UnixNano() 95 | s.requestMutex.Unlock() 96 | 97 | action := <-s.action 98 | 99 | s.requestMutex.Lock() 100 | s.request = nil 101 | s.requestID = 0 102 | s.requestMutex.Unlock() 103 | 104 | return action 105 | } 106 | 107 | func writeJson(w http.ResponseWriter, body any) error { 108 | w.Header().Add("content-type", "application/json") 109 | w.Header().Add("Access-Control-Allow-Origin", "*") // this should probably be removed. 110 | return json.NewEncoder(w).Encode(body) 111 | } 112 | 113 | // ServeHTTP implements http.Handler. 114 | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 115 | // post requests 116 | // resume and stop 117 | if r.Method == http.MethodPost { 118 | var action requestAction 119 | 120 | switch r.URL.Path { 121 | case "/stop": 122 | action = requestActionStop 123 | case "/step": 124 | action = requestActionStep 125 | default: 126 | action = requestActionResume 127 | } 128 | 129 | if s.hasRequest() { 130 | s.action <- action 131 | } 132 | 133 | if err := writeJson(w, "ok"); err != nil { 134 | s.logger.Error("error writing http response", zap.Error(err)) 135 | } 136 | return 137 | } 138 | 139 | // handle /request 140 | if r.URL.Path == "/request" { 141 | var response struct { 142 | HasRequest bool `json:"has_request"` 143 | HasResponse bool `json:"has_response"` 144 | Request *Response `json:"request,omitempty"` 145 | ID int64 `json:"id"` 146 | } 147 | 148 | response.HasRequest = s.hasRequest() 149 | response.HasResponse = s.hasResponse() 150 | response.Request = s.request 151 | response.ID = s.requestID 152 | 153 | if err := writeJson(w, response); err != nil { 154 | s.logger.Error("error writing http response", zap.Error(err)) 155 | } 156 | return 157 | } 158 | 159 | s.static.ServeHTTP(w, r) 160 | } 161 | 162 | var _ http.Handler = (*Server)(nil) 163 | 164 | var defaultServer *Server 165 | 166 | func setUpServer(ctx caddy.Context) error { 167 | // setup already done 168 | if defaultServer != nil { 169 | return nil 170 | } 171 | 172 | dir, err := fs.Sub(staticFS, "static") 173 | if err != nil { 174 | return fmt.Errorf("error setting up static file server: %w", err) 175 | } 176 | 177 | defaultServer = &Server{ 178 | logger: ctx.Logger(), 179 | action: make(chan requestAction), 180 | static: http.FileServerFS(dir), 181 | } 182 | return nil 183 | } 184 | 185 | func getServerInstance() *Server { return defaultServer } 186 | -------------------------------------------------------------------------------- /static/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abiosoft/caddy-inspect/96cdb1dfb122f79913d60ecb34030d302a4f4ec1/static/assets/favicon.png -------------------------------------------------------------------------------- /static/assets/index-B254VF7R.css: -------------------------------------------------------------------------------- 1 | :root{--bg-color: #ffffff;--text-color: #000000;--border-color: #ddd;--button-bg-color: #007bff;--button-hover-bg-color: #0056b3;--button-active-bg-color: #004085;--button-danger-color: #c73636;--button-hover-danger-color: #a62d2d;--calm-color: #7e7e7e;--input-focus-border-color: #007bff;--input-focus-shadow-color: rgba(0, 123, 255, .5);--snippet-highlight-color: rgba(150, 150, 150, .5)}.logo{content:url(/assets/logo-light.svg);height:30px;margin-right:5px}@media (prefers-color-scheme: dark){:root{--bg-color: #121212;--text-color: #ffffff;--border-color: #333;--button-bg-color: #1a73e8;--button-hover-bg-color: #135ba1;--button-active-bg-color: #0e447a;--button-danger-color: #a0222f;--button-hover-danger-color: #881c28;--calm-color: #7e7e7e;--input-focus-border-color: #1a73e8;--input-focus-shadow-color: rgba(26, 115, 232, .5);--snippet-highlight-color: rgba(150, 150, 150, .5)}.logo{content:url(/assets/logo-dark.svg)}}body{font-family:Arial,sans-serif;margin:0;padding:0;box-sizing:border-box;background-color:var(--bg-color);color:var(--text-color)}.inspect{padding:10px}button{padding:10px 20px;font-size:16px;color:#fff;background-color:var(--button-bg-color);border:none;border-radius:4px;cursor:pointer}button:hover{background-color:var(--button-hover-bg-color)}button:active{background-color:var(--button-active-bg-color);transform:translateY(1px)}button.danger{background-color:var(--button-danger-color)}button.danger:hover{background-color:var(--button-hover-danger-color)}input[type=text]{width:100%;padding:10px;font-size:14px;border:1px solid var(--border-color);border-radius:4px;background-color:var(--textarea-bg-color);color:var(--text-color)}input[type=text]:focus{outline:none;border-color:var(--input-focus-border-color);box-shadow:0 0 4px var(--input-focus-shadow-color)}.loading{display:flex;justify-content:center;align-items:center;height:30vh;font-size:20px;color:var(--text-color)}.tree-node.svelte-1sbg00l{margin-left:20px;display:flex;flex-direction:column}.node-header.svelte-1sbg00l{cursor:pointer;display:flex;align-items:center}.toggle-icon.svelte-1sbg00l{width:15px;font-size:10px}.key-value-container.svelte-1sbg00l{display:flex;justify-content:space-between;width:100%}.key.svelte-1sbg00l{flex:1;margin-right:10px;font-family:monospace;padding:5px;font-weight:700}.value.svelte-1sbg00l,.value-only.svelte-1sbg00l{flex:2;margin-left:10px;border:none;background:transparent;font-family:monospace;font-size:inherit;color:inherit;padding:5px;border-radius:4px}.key-value-container.svelte-1sbg00l:has(.value-only:where(.svelte-1sbg00l)):not(:has(.key)) .value-only:where(.svelte-1sbg00l){flex:1 1 100%;margin-left:0}.children.svelte-1sbg00l{margin-left:20px}.tree.svelte-4p6snr{font-family:Arial,sans-serif}.json-tree.svelte-4p6snr{font-family:monospace;font-size:15px;padding:20px;border:1px solid var(--border-color);border-radius:4px;background-color:var(--textarea-bg-color);color:var(--text-color)}.container.svelte-4p6snr{max-width:800px;margin:20px auto;padding:20px;border-radius:8px;box-shadow:0 4px 8px #0000001a;background-color:var(--bg-color)}.top-row.svelte-4p6snr{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.top-row-item.svelte-4p6snr{padding:5px}.top-row-item.svelte-4p6snr button:where(.svelte-4p6snr){width:50px;font-size:25px;padding:5px 0;line-height:1.2}.top-row-right.svelte-4p6snr{margin-left:auto}.header.svelte-4p6snr{display:flex;text-align:center;align-items:center;justify-content:center;font-size:20px;font-weight:700;color:var(--text-color)}.source-file.svelte-4p6snr{text-align:center;font-size:12px;font-weight:700;padding-bottom:10px}.snippet.svelte-4p6snr{display:flex;flex-direction:column;font-family:monospace;font-size:13px;padding:10px;border-radius:5px;overflow:auto;border:solid 1px var(--calm-color);margin-bottom:10px}.code-line.svelte-4p6snr{display:flex;padding:2px 5px}.mark.svelte-4p6snr{width:30px;text-align:right;margin-right:0}.highlight.svelte-4p6snr{background-color:var(--snippet-highlight-color)}.line-number.svelte-4p6snr{width:30px;text-align:right;margin-right:10px;color:var(--calm-color)}.highlight.svelte-4p6snr .line-number:where(.svelte-4p6snr){color:var(--text-color)}.line-content.svelte-4p6snr{white-space:pre;-moz-tab-size:4;tab-size:4;width:200px} 2 | -------------------------------------------------------------------------------- /static/assets/index-CLXqnKY8.js: -------------------------------------------------------------------------------- 1 | (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const a of l)if(a.type==="childList")for(const u of a.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&r(u)}).observe(document,{childList:!0,subtree:!0});function n(l){const a={};return l.integrity&&(a.integrity=l.integrity),l.referrerPolicy&&(a.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?a.credentials="include":l.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function r(l){if(l.ep)return;l.ep=!0;const a=n(l);fetch(l.href,a)}})();const At=!1;var ct=Array.isArray,bn=Array.prototype.indexOf,_t=Array.from,xn=Object.defineProperty,ye=Object.getOwnPropertyDescriptor,kn=Object.prototype,Tn=Array.prototype,An=Object.getPrototypeOf;function Nn(e){for(var t=0;t=N.v&&w(N,R+1)}Ct(u)}return!0},ownKeys(f){h(u);var i=Reflect.ownKeys(f).filter(s=>{var c=l.get(s);return c===void 0||c.v!==D});for(var[o,v]of l)v.v!==D&&!(o in f)&&i.push(o);return i},setPrototypeOf(){Fn()}})}function Ct(e,t=1){w(e,e.v+t)}var qt,Kt,Wt;function Gn(){if(qt===void 0){qt=window;var e=Element.prototype,t=Node.prototype;Kt=ye(t,"firstChild").get,Wt=ye(t,"nextSibling").get,e.__click=void 0,e.__className="",e.__attributes=null,e.__styles=null,e.__e=void 0,Text.prototype.__t=void 0}}function Ke(e=""){return document.createTextNode(e)}function Se(e){return Kt.call(e)}function We(e){return Wt.call(e)}function O(e,t){return Se(e)}function de(e,t){{var n=Se(e);return n instanceof Comment&&n.data===""?We(n):n}}function H(e,t=1,n=!1){let r=e;for(;t--;)r=We(r);return r}function Xn(e){e.textContent=""}function ie(e){var t=Y|te,n=E!==null&&E.f&Y?E:null;return b===null||n!==null&&n.f&X?t|=X:b.f|=Ft,{ctx:T,deps:null,effects:null,equals:Lt,f:t,fn:e,reactions:null,rv:0,v:null,wv:0,parent:n??b}}function Zn(e){const t=ie(e);return t.equals=Mt,t}function Gt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;nnew Promise(r=>{n.outro?De(t,()=>{le(t),r(void 0)}):(le(t),r(void 0))})}function zt(e){return Ge(Pt,e,!1)}function _e(e,t=[],n=ie){const r=t.map(n);return wt(()=>e(...r.map(h)))}function wt(e,t=0){return Ge(dt|pt|t,e,!0)}function xe(e,t=!0){return Ge(dt|ee,e,!0,t)}function Jt(e){var t=e.teardown;if(t!==null){const n=Et,r=E;Ot(!0),he(null);try{t.call(null)}finally{Ot(n),he(r)}}}function Qt(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){var r=n.next;le(n,t),n=r}}function tr(e){for(var t=e.first;t!==null;){var n=t.next;t.f&ee||le(t),t=n}}function le(e,t=!0){var n=!1;if((t||e.f&qn)&&e.nodes_start!==null){for(var r=e.nodes_start,l=e.nodes_end;r!==null;){var a=r===l?null:We(r);r.remove(),r=a}n=!0}Qt(e,t&&!n),Be(e,0),$(e,He);var u=e.transitions;if(u!==null)for(const f of u)f.stop();Jt(e);var _=e.parent;_!==null&&_.first!==null&&$t(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes_start=e.nodes_end=null}function $t(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function De(e,t){var n=[];yt(e,n,!0),en(n,()=>{le(e),t&&t()})}function en(e,t){var n=e.length;if(n>0){var r=()=>--n||t();for(var l of e)l.out(r)}else t()}function yt(e,t,n){if(!(e.f&J)){if(e.f^=J,e.transitions!==null)for(const u of e.transitions)(u.is_global||n)&&t.push(u);for(var r=e.first;r!==null;){var l=r.next,a=(r.f&ht)!==0||(r.f&ee)!==0;yt(r,t,a?n:!1),r=l}}}function Pe(e){tn(e,!0)}function tn(e,t){if(e.f&J){e.f^=J,e.f&F||(e.f^=F),Ae(e)&&($(e,te),Ze(e));for(var n=e.first;n!==null;){var r=n.next,l=(n.f&ht)!==0||(n.f&ee)!==0;tn(n,l?t:!1),n=r}if(e.transitions!==null)for(const a of e.transitions)(a.is_global||t)&&a.in()}}let it=!1,ut=[];function nr(){it=!1;const e=ut.slice();ut=[],Nn(e)}function rr(e){it||(it=!0,queueMicrotask(nr)),ut.push(e)}let Re=!1,Fe=!1,Le=null,pe=!1,Et=!1;function Rt(e){pe=e}function Ot(e){Et=e}let ot=[],Ee=0;let E=null,Q=!1;function he(e){E=e}let b=null;function ge(e){b=e}let G=null;function lr(e){G=e}let P=null,M=0,z=null;function ar(e){z=e}let nn=1,Me=0,se=!1;function rn(){return++nn}function Ae(e){var i;var t=e.f;if(t&te)return!0;if(t&me){var n=e.deps,r=(t&X)!==0;if(n!==null){var l,a,u=(t&Ie)!==0,_=r&&b!==null&&!se,f=n.length;if(u||_){for(l=0;le.wv)return!0}(!r||b!==null&&!se)&&$(e,F)}return!1}function sr(e,t){for(var n=t;n!==null;){if(n.f&Oe)try{n.fn(e);return}catch{n.f^=Oe}n=n.parent}throw Re=!1,e}function ir(e){return(e.f&He)===0&&(e.parent===null||(e.parent.f&Oe)===0)}function Xe(e,t,n,r){if(Re){if(n===null&&(Re=!1),ir(t))throw e;return}n!==null&&(Re=!0);{sr(e,t);return}}function ln(e,t,n=0){var r=e.reactions;if(r!==null)for(var l=0;l0)for(v.length=M+P.length,s=0;s1e3){Ee=0;try{Dn()}catch(e){if(Le!==null)Xe(e,Le,null);else throw e}}Ee++}function fr(e){var t=e.length;if(t!==0){or();var n=pe;pe=!0;try{for(var r=0;r1001)return;const e=ot;ot=[],fr(e),Fe||(Ee=0,Le=null)}function Ze(e){Fe||(Fe=!0,queueMicrotask(cr)),Le=e;for(var t=e;t.parent!==null;){t=t.parent;var n=t.f;if(n&(ke|ee)){if(!(n&F))return;t.f^=F}}ot.push(t)}function sn(e,t){var n=e.first,r=[];e:for(;n!==null;){var l=n.f,a=(l&ee)!==0,u=a&&(l&F)!==0,_=n.next;if(!u&&!(l&J))if(l&dt){if(a)n.f^=F;else{var f=E;try{E=n,Ae(n)&&bt(n)}catch(s){Xe(s,n,null,n.ctx)}finally{E=f}}var i=n.first;if(i!==null){n=i;continue}}else l&Pt&&r.push(n);if(_===null){let s=n.parent;for(;s!==null;){if(e===s)break e;var o=s.next;if(o!==null){n=o;continue e}s=s.parent}}n=_}for(var v=0;v{throw g});throw s}}finally{e.__root=t,delete e.currentTarget,he(o),ge(v)}}}function hr(e){var t=document.createElement("template");return t.innerHTML=e,t.content}function Ue(e,t){var n=b;n.nodes_start===null&&(n.nodes_start=e,n.nodes_end=t)}function L(e,t){var n=(t&Hn)!==0,r=(t&Yn)!==0,l,a=!e.startsWith("");return()=>{l===void 0&&(l=hr(a?e:""+e),n||(l=Se(l)));var u=r?document.importNode(l,!0):l.cloneNode(!0);if(n){var _=Se(u),f=u.lastChild;Ue(_,f)}else Ue(u,u);return u}}function at(e=""){{var t=Ke(e+"");return Ue(t,t),t}}function It(){var e=document.createDocumentFragment(),t=document.createComment(""),n=Ke();return e.append(t,n),Ue(t,n),e}function q(e,t){e!==null&&e.before(t)}function be(e,t){var n=t==null?"":typeof t=="object"?t+"":t;n!==(e.__t??(e.__t=e.nodeValue))&&(e.__t=n,e.nodeValue=n+"")}function gr(e,t){return mr(e,t)}const ce=new Map;function mr(e,{target:t,anchor:n,props:r={},events:l,context:a,intro:u=!0}){Gn();var _=new Set,f=v=>{for(var s=0;s{var v=n??t.appendChild(Ke());return xe(()=>{if(a){Vt({});var s=T;s.c=a}l&&(r.$$events=l),i=e(v,r)||{},a&&jt()}),()=>{var d;for(var s of _){t.removeEventListener(s,qe);var c=ce.get(s);--c===0?(document.removeEventListener(s,qe),ce.delete(s)):ce.set(s,c)}ft.delete(f),v!==n&&((d=v.parentNode)==null||d.removeChild(v))}});return wr.set(i,o),i}let wr=new WeakMap;function W(e,t,n=!1){var r=e,l=null,a=null,u=D,_=n?ht:0,f=!1;const i=(v,s=!0)=>{f=!0,o(s,v)},o=(v,s)=>{u!==(u=v)&&(u?(l?Pe(l):s&&(l=xe(()=>s(r))),a&&De(a,()=>{a=null})):(a?Pe(a):s&&(a=xe(()=>s(r))),l&&De(l,()=>{l=null})))};wt(()=>{f=!1,t(i),f||o(null,null)},_)}function Ve(e,t){return t}function yr(e,t,n,r){for(var l=[],a=t.length,u=0;u0&&l.length===0&&n!==null;if(_){var f=n.parentNode;Xn(f),f.append(n),r.clear(),re(e,t[0].prev,t[a-1].next)}en(l,()=>{for(var i=0;i{var c=n();return ct(c)?c:c==null?[]:_t(c)});wt(()=>{var c=h(s),d=c.length;v&&d===0||(v=d===0,Er(c,_,u,l,t,r,n),a!==null&&(d===0?o?Pe(o):o=xe(()=>a(u)):o!==null&&De(o,()=>{o=null})),h(s))})}function Er(e,t,n,r,l,a,u){var ue,oe,ae,Ne;var _=(l&Vn)!==0,f=(l&(gt|mt))!==0,i=e.length,o=t.items,v=t.first,s=v,c,d=null,A,x=[],N=[],R,g,p,m;if(_)for(m=0;m0){var we=l&Bt&&i===0?n:null;if(_){for(m=0;m{var Ce;if(A!==void 0)for(p of A)(Ce=p.a)==null||Ce.apply()}),b.first=t.first&&t.first.e,b.last=d&&d.e}function br(e,t,n,r){r>&&st(e.v,t),r&mt?st(e.i,n):e.i=n}function xr(e,t,n,r,l,a,u,_,f,i){var o=(f>)!==0,v=(f&jn)===0,s=o?v?Kn(l):B(l):l,c=f&mt?B(u):u,d={i:c,v:s,k:a,a:null,e:null,prev:n,next:r};try{return d.e=xe(()=>_(e,s,c,i),Yt),d.e.prev=n&&n.e,d.e.next=r&&r.e,n===null?t.first=d:(n.next=d,n.e.next=d.e),r!==null&&(r.prev=d,r.e.prev=d.e),d}finally{}}function St(e,t,n){for(var r=e.next?e.next.e.nodes_start:n,l=t?t.e.nodes_start:n,a=e.e.nodes_start;a!==r;){var u=We(a);l.before(a),a=u}}function re(e,t,n){t===null?e.first=n:(t.next=n,t.e.next=n&&n.e),n!==null&&(n.prev=t,n.e.prev=t&&t.e)}function vn(e){var t,n,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var l=e.length;for(t=0;t{const t=un(e);if(typeof t=="function")return t})}function Cr(e){T===null&&Ut(),cn(()=>()=>un(e))}function qr(e){var t=e.l;return t.u??(t.u={a:[],b:[],m:[]})}const Rr="5";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(Rr);Un();function Or(e,t){w(t,!h(t))}var Ir=L(' '),Sr=L('   '),Dr=L(''),Pr=L(''),Fr=L(' ',1),Lr=L('
'),Mr=L('
');function vt(e,t){const n="__array__";let r=Object.entries(t.node),l=ie(()=>Array.isArray(t.node)),a=ie(()=>typeof t.node=="object"&&t.node!==null),u=t.key==n,_=j(!1);var f=Mr(),i=O(f);i.__click=[Or,_];var o=O(i);{var v=g=>{var p=Ir(),m=O(p);_e(()=>be(m,h(_)?"▼":"▶")),q(g,p)},s=g=>{var p=Sr();q(g,p)};W(o,g=>{h(a)?g(v):g(s,!1)})}var c=H(o,2),d=O(c);{var A=g=>{var p=Dr();_e(()=>Dt(p,t.node)),q(g,p)},x=g=>{var p=Fr(),m=de(p),I=O(m),k=H(m,2);{var y=S=>{var U=Pr();_e(()=>Dt(U,t.node)),q(S,U)};W(k,S=>{h(a)||S(y)})}_e(()=>be(I,t.key)),q(g,p)};W(d,g=>{u?g(A):g(x,!1)})}var N=H(i,2);{var R=g=>{var p=Lr(),m=O(p);{var I=y=>{var S=It(),U=de(S);je(U,17,()=>t.node,Ve,(V,K,we,ue)=>{vt(V,{key:n,get node(){return h(K)}})}),q(y,S)},k=y=>{var S=It(),U=de(S);je(U,17,()=>r,Ve,(V,K,we,ue)=>{let oe=()=>h(K)[0],ae=()=>h(K)[1];vt(V,{get key(){return oe()},get node(){return ae()}})}),q(y,S)};W(m,y=>{h(l)?y(I):y(k,!1)})}q(g,p)};W(N,g=>{h(a)&&h(_)&&g(R)})}q(e,f)}fn(["click"]);async function Br(e,t,n,r){(await fetch(t,{method:"POST"})).ok&&(n(),r())}async function Ur(e,t,n,r){(await fetch(`${t}/stop`,{method:"POST"})).ok&&(n(),r())}async function Vr(e,t,n,r){(await fetch(`${t}/step`,{method:"POST"})).ok&&(n(),r())}var jr=L(''),Hr=L(" ◉",1),Yr=L('
'),Kr=L('
',1),Wr=L('
',1),Gr=L('
Waiting for request...
'),Xr=L('
Inspect

');function Zr(e,t){Vt(t,!0);const n="";let r=j(C([])),l=ie(()=>h(r).length>0),a=j(""),u=j(""),_=j(""),f=j(0),i=j(0),o=ie(()=>h(_)!=""),v=j(C([])),s=j(0),c=j(0),d=j(!1);cn(async()=>{w(s,C(setInterval(N,1e3)))}),Cr(async()=>{clearInterval(h(s))}),Zt(()=>{document.title=h(a)?`Caddy Inspect - ${h(u).toUpperCase()} ${h(a)}`:"Caddy Inspect"});function A(I){return I.split("_").map(k=>k.charAt(0).toUpperCase()+k.slice(1).toLowerCase()).join(" ")}function x(){w(r,C([])),w(a,""),w(u,""),w(c,0),w(_,""),w(f,0),w(i,0),w(v,C([]))}async function N(){const I=await fetch(`${n}/request`);if(!I.ok)return;const k=await I.json();if(!k.has_request){x();return}if(h(c)==k.id)return;const{caddyfile:y,...S}=k.request;w(c,C(k.id)),w(a,C(k.request.url)),w(u,C(k.request.method)),w(d,C(k.has_response)),y&&(w(_,C(y.file)),w(f,C(y.line)),w(i,C(y.source_line_start)),w(v,C(y.source))),w(r,C(Object.entries(S))),window.focus()}var R=Xr(),g=H(O(R),4);{var p=I=>{var k=Wr(),y=de(k),S=O(y),U=O(S);U.__click=[Br,n,x,N];var V=H(S,2);{var K=Z=>{var ne=jr(),fe=O(ne);fe.__click=[Vr,n,x,N],q(Z,ne)};W(V,Z=>{h(d)||Z(K)})}var we=H(V,2),ue=O(we);ue.__click=[Ur,n,x,N];var oe=H(y,2),ae=O(oe);{var Ne=Z=>{var ne=Kr(),fe=de(ne),ze=O(fe),Je=O(ze),_n=H(fe,2);je(_n,21,()=>h(v),Ve,(Qe,$e,et)=>{var tt=Yr(),xt=O(tt),dn=O(xt);{var pn=nt=>{var Tt=Hr(),wn=de(Tt);{var yn=ve=>{var rt=at("↑");q(ve,rt)},En=ve=>{var rt=at("↓");q(ve,rt)};W(wn,ve=>{h(d)?ve(yn):ve(En,!1)})}q(nt,Tt)};W(dn,nt=>{et+h(i)==h(f)&&nt(pn)})}var kt=H(xt,2),hn=O(kt),gn=H(kt,2),mn=O(gn);_e(()=>{Ar(tt,Tr({"code-line":!0,highlight:et+h(i)==h(f)}),"svelte-4p6snr"),be(hn,et+h(i)),be(mn,h($e))}),q(Qe,tt)},Qe=>{var $e=at(" ");q(Qe,$e)}),_e(()=>be(Je,`${h(_)??""}:${h(f)??""}`)),q(Z,ne)};W(ae,Z=>{h(o)&&Z(Ne)})}var Ce=H(ae,2);je(Ce,17,()=>h(r),Ve,(Z,ne)=>{let fe=()=>h(ne)[0],ze=()=>h(ne)[1];const Je=ie(()=>A(fe()));vt(Z,{get key(){return h(Je)},get node(){return ze()}})}),q(I,k)},m=I=>{var k=Gr();q(I,k)};W(g,I=>{h(l)?I(p):I(m,!1)})}q(e,R),jt()}fn(["click"]);function zr(e){Zr(e,{})}gr(zr,{target:document.getElementById("app")}); 2 | -------------------------------------------------------------------------------- /static/assets/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /static/assets/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Caddy Inspect 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /store.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | var configMap = storeMap{m: map[string]snippetDetails{}} 9 | 10 | func configKey(file string, line int) string { return fmt.Sprintf("%s:%d", file, line) } 11 | 12 | type storeMap struct { 13 | m map[string]snippetDetails 14 | sync.RWMutex 15 | } 16 | 17 | func (s *storeMap) set(key string, val snippetDetails) { 18 | s.Lock() 19 | defer s.Unlock() 20 | 21 | s.m[key] = val 22 | } 23 | 24 | func (s *storeMap) get(key string) snippetDetails { 25 | s.RLock() 26 | defer s.RUnlock() 27 | 28 | return s.m[key] 29 | } 30 | -------------------------------------------------------------------------------- /xcaddy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | xcaddy build --with github.com/abiosoft/caddy-inspect=. 4 | --------------------------------------------------------------------------------