feat(surf): print the serve URL and admin status on startup

- Add surf.Router.AdminPrefix (additive): the admin mount path and true
  when BuildRouter mounted the cabana admin, false otherwise
- serve binds with net.Listen before printing, so a busy port fails
  without a misleading URL, then prints the listen address with a
  browser URL and an admin status line: the admin URL, a warning naming
  summer admin:create when backend_users is empty or missing, or
  admin: off with the steps to add an admin controller
- The administrator count is bounded by a 2 s timeout and never fails boot
- route:list ends with the same admin status line
- surf and cabana READMEs and the console and routing docs describe the output
This commit is contained in:
Jakub Zych
2026-10-08 16:36:21 +02:00
parent 4b5ed7707e
commit da21670870
10 changed files with 294 additions and 10 deletions

View File

@@ -22,7 +22,7 @@ surf turns the routes that plugins declare through `pact.HasRoutes` into one `ht
- Path-scoped CORS configured with the same keys as Laravel's `config/cors.php` (`surf.CORSConfig`), including preflight handling. Every `OPTIONS` request on a CORS path is answered with 204 before routing, with the headers Laravel's `HandleCors` sends: `Cache-Control: no-cache, private` always and, on a preflight (an `Origin` and an `Access-Control-Request-Method`), the requested method (upper-cased) and headers echoed in `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers` when `*` allows any, `Vary` on the request headers and PHP's default `Content-Type: text/html; charset=UTF-8`.
- Optional `surf.LocaleResolver`: a compiled plugin publishes the interface through backpack; surf looks it up without importing the plugin. When present, it may strip an enabled locale prefix before routing and writes a validated code with `towel.WithLocale`. When absent, hosts keep the raw `Accept-Language` header and `surf.LocaleFromPrincipal` overlay.
- `surf.LocaleFromPrincipal` switches the request locale to the signed-in user's preferred locale.
- A read-only route table (`surf.Router.Routes`) and the `serve` and `route:list` commands.
- A read-only route table (`surf.Router.Routes`), the admin mount status (`surf.Router.AdminPrefix`) and the `serve` and `route:list` commands.
## Usage
@@ -108,6 +108,7 @@ The generated application `main` wires surf in with `surf.ServeCommand` and `sur
| `surf.Router.RegisterMiddleware` | Stores a named middleware; duplicates fail. |
| `surf.Router.RegisterMiddlewareFactory` | Stores a parameterized middleware used as `name:param`. |
| `surf.Router.Routes` | Returns a copy of the registered routes as `surf.RouteInfo` values. |
| `surf.Router.AdminPrefix` | The admin mount path and true when `surf.BuildRouter` mounted the cabana admin; false when no plugin registers an admin controller. |
| `surf.RouteInfo` | Method, pattern, owning plugin, middleware and raw flag of one route. |
| `surf.Use` | Builds a middleware name list for a group. |
| `surf.Constraint` | A compiled path-parameter restriction built by `surf.Regex` or `surf.Enum`. |
@@ -162,8 +163,8 @@ The `serve` command also opens the database through [lagoon](../lagoon/README.md
| Command | Flags | Description |
|---------|-------|-------------|
| `serve` | `--addr` (default `:8080`) | Opens the database and uploads bucket, assembles the router, starts the in-process job worker (see `queue.work_in_serve`) and serves HTTP until SIGINT or SIGTERM, then shuts the server and the worker down gracefully within 10 seconds. |
| `route:list` | none | Builds the router the same way `serve` does, without opening the database or listening, and prints a table of method, pattern, plugin, middleware and raw flag for every route. |
| `serve` | `--addr` (default `:8080`) | Opens the database and uploads bucket, assembles the router, binds the address, starts the in-process job worker (see `queue.work_in_serve`) and serves HTTP until SIGINT or SIGTERM, then shuts the server and the worker down gracefully within 10 seconds. After binding it prints the listen address with a browser URL (an unspecified host shows as 127.0.0.1) and an admin status line: the admin URL, with a warning naming `summer admin:create` when no administrator exists yet, or `admin: off` with the steps to add an admin controller. |
| `route:list` | none | Builds the router the same way `serve` does, without opening the database or listening, and prints a table of method, pattern, plugin, middleware and raw flag for every route, followed by the admin status line (`admin: mounted at <prefix>` or `admin: off`). |
## Dependencies