From c9867cda5ec1d12609abd6e572788cbc7aa922db Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 8 Jun 2026 10:21:44 +0100 Subject: [PATCH] feat(core): print clickable dev-server URLs and dev-bypass shortcut --- .changeset/dev-server-clickable-urls.md | 5 +++ packages/core/src/astro/integration/index.ts | 47 +++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 .changeset/dev-server-clickable-urls.md diff --git a/.changeset/dev-server-clickable-urls.md b/.changeset/dev-server-clickable-urls.md new file mode 100644 index 000000000..4e2faeebc --- /dev/null +++ b/.changeset/dev-server-clickable-urls.md @@ -0,0 +1,5 @@ +--- +"emdash": minor +--- + +The Astro dev server now prints absolute, clickable URLs for the admin UI and (when enabled) the MCP server, along with a dev-bypass shortcut link that signs you in as a dev admin without going through passkey setup or auth. The startup banner also shows the installed EmDash version. The dev-bypass link is dev-only and the underlying endpoint returns 403 in production. diff --git a/packages/core/src/astro/integration/index.ts b/packages/core/src/astro/integration/index.ts index c1aee2607..3bebbce34 100644 --- a/packages/core/src/astro/integration/index.ts +++ b/packages/core/src/astro/integration/index.ts @@ -16,6 +16,7 @@ import type { AstroIntegration, AstroIntegrationLogger } from "astro"; import { validateAllowedOrigins, validateOriginShape } from "../../auth/allowed-origins.js"; import type { ResolvedPlugin } from "../../plugins/types.js"; +import { VERSION } from "../../version.js"; import { local } from "../storage/adapters.js"; import { notoSans } from "./font-provider.js"; import { @@ -68,15 +69,24 @@ const cyan = (s: string) => `\x1b[36m${s}\x1b[39m`; function printBanner(_logger: AstroIntegrationLogger): void { const banner = ` - ${bold(cyan("— E M D A S H —"))} + ${bold(cyan("— E M D A S H —"))} ${dim(`v${VERSION}`)} `; console.log(banner); } -/** Print route injection summary */ -function printRoutesSummary(_logger: AstroIntegrationLogger): void { - console.log(`\n ${dim("›")} Admin UI ${cyan("/_emdash/admin")}`); - console.log(` ${dim("›")} API ${cyan("/_emdash/api/*")}`); +/** + * Print dev-server route info with absolute (clickable) URLs, including the + * dev-bypass shortcut that skips passkey auth. Dev only -- the dev-bypass + * endpoint returns 403 in production. + */ +function printDevServerInfo(baseUrl: string, mcpEnabled: boolean): void { + const devBypassUrl = `${baseUrl}/_emdash/api/setup/dev-bypass?redirect=/_emdash/admin`; + console.log(`\n ${dim("›")} Admin UI ${cyan(`${baseUrl}/_emdash/admin`)}`); + if (mcpEnabled) { + console.log(` ${dim("›")} MCP server ${cyan(`${baseUrl}/_emdash/api/mcp`)}`); + } + console.log(` ${dim("›")} Dev bypass ${cyan(devBypassUrl)}`); + console.log(` ${dim("Skips passkey setup/auth and signs you in as a dev admin")}`); console.log(""); } @@ -211,6 +221,10 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration { // Check if auth is an AuthDescriptor (has entrypoint) indicating external auth const useExternalAuth = !!(resolvedConfig.auth && "entrypoint" in resolvedConfig.auth); + // Captured in astro:config:setup so the astro:server:setup hook can tell + // whether we're running `astro dev` (where the dev-bypass shortcut applies). + let astroCommand: "dev" | "build" | "preview" | "sync" | undefined; + return { name: "emdash", hooks: { @@ -222,6 +236,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration { config: astroConfig, command, }) => { + astroCommand = command; printBanner(logger); // Capture the host's Astro version so the runtime can expose it // to the admin and the registry install gate for `env:astro` @@ -258,7 +273,9 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration { const securityConfig: Record = { checkOrigin: false, ...(resolvedConfig.siteUrl - ? { allowedDomains: [{ hostname: new URL(resolvedConfig.siteUrl).hostname }] } + ? { + allowedDomains: [{ hostname: new URL(resolvedConfig.siteUrl).hostname }], + } : {}), }; @@ -375,9 +392,25 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration { order: "pre", }); - printRoutesSummary(logger); + // Route info is printed with absolute, clickable URLs once the + // dev server is listening (see astro:server:setup), since the + // port isn't known yet here. Nothing useful to print for build. }, "astro:server:setup": ({ server, logger }) => { + // Print route info with absolute, clickable URLs once the server + // is listening. Only in `astro dev` -- the dev-bypass shortcut is + // dev-only and the port is unknown until now. + if (astroCommand === "dev") { + server.httpServer?.once("listening", () => { + const address = server.httpServer?.address(); + if (!address || typeof address === "string") return; + printDevServerInfo( + `http://${address.address === "::1" ? "localhost" : address.address}:${address.port}`, + resolvedConfig.mcp !== false, + ); + }); + } + // Generate types once the server is listening. // The endpoint returns the types content; we write the file here // (in Node) because workerd has no real filesystem access.