Skip to content

Commit b6a5fac

Browse files
feat(core): print clickable dev-server URLs and dev-bypass shortcut (#1382)
* feat(core): print clickable dev-server URLs and dev-bypass shortcut * Update packages/core/src/astro/integration/index.ts Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com> * fix(core): repair botched merge in dev-server-setup hook The main merge left a duplicated printDevServerInfo block and a stray closing brace in the astro:server:setup hook, producing a syntax error. Remove the orphaned leftover, keeping the single listening handler. --------- Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
1 parent 37e848b commit b6a5fac

2 files changed

Lines changed: 48 additions & 7 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"emdash": minor
3+
---
4+
5+
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.

packages/core/src/astro/integration/index.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { AstroIntegration, AstroIntegrationLogger } from "astro";
1717
import { validateAllowedOrigins, validateOriginShape } from "../../auth/allowed-origins.js";
1818
import { INTERNAL_MEDIA_PREFIX } from "../../media/normalize.js";
1919
import type { ResolvedPlugin } from "../../plugins/types.js";
20+
import { VERSION } from "../../version.js";
2021
import { local } from "../storage/adapters.js";
2122
import { notoSans } from "./font-provider.js";
2223
import {
@@ -155,15 +156,24 @@ const cyan = (s: string) => `\x1b[36m${s}\x1b[39m`;
155156
function printBanner(_logger: AstroIntegrationLogger): void {
156157
const banner = `
157158
158-
${bold(cyan("— E M D A S H —"))}
159+
${bold(cyan("— E M D A S H —"))} ${dim(`v${VERSION}`)}
159160
`;
160161
console.log(banner);
161162
}
162163

163-
/** Print route injection summary */
164-
function printRoutesSummary(_logger: AstroIntegrationLogger): void {
165-
console.log(`\n ${dim("›")} Admin UI ${cyan("/_emdash/admin")}`);
166-
console.log(` ${dim("›")} API ${cyan("/_emdash/api/*")}`);
164+
/**
165+
* Print dev-server route info with absolute (clickable) URLs, including the
166+
* dev-bypass shortcut that skips passkey auth. Dev only -- the dev-bypass
167+
* endpoint returns 403 in production.
168+
*/
169+
function printDevServerInfo(baseUrl: string, mcpEnabled: boolean): void {
170+
const devBypassUrl = `${baseUrl}/_emdash/api/setup/dev-bypass?redirect=/_emdash/admin`;
171+
console.log(`\n ${dim("›")} Admin UI ${cyan(`${baseUrl}/_emdash/admin`)}`);
172+
if (mcpEnabled) {
173+
console.log(` ${dim("›")} MCP server ${cyan(`${baseUrl}/_emdash/api/mcp`)}`);
174+
}
175+
console.log(` ${dim("›")} Dev bypass ${cyan(devBypassUrl)}`);
176+
console.log(` ${dim("Skips passkey setup/auth and signs you in as a dev admin")}`);
167177
console.log("");
168178
}
169179

@@ -298,6 +308,10 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
298308
// Check if auth is an AuthDescriptor (has entrypoint) indicating external auth
299309
const useExternalAuth = !!(resolvedConfig.auth && "entrypoint" in resolvedConfig.auth);
300310

311+
// Captured in astro:config:setup so the astro:server:setup hook can tell
312+
// whether we're running `astro dev` (where the dev-bypass shortcut applies).
313+
let astroCommand: "dev" | "build" | "preview" | "sync" | undefined;
314+
301315
return {
302316
name: "emdash",
303317
hooks: {
@@ -309,6 +323,7 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
309323
config: astroConfig,
310324
command,
311325
}) => {
326+
astroCommand = command;
312327
printBanner(logger);
313328
// Capture the host's Astro version so the runtime can expose it
314329
// to the admin and the registry install gate for `env:astro`
@@ -345,7 +360,9 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
345360
const securityConfig: Record<string, unknown> = {
346361
checkOrigin: false,
347362
...(resolvedConfig.siteUrl
348-
? { allowedDomains: [{ hostname: new URL(resolvedConfig.siteUrl).hostname }] }
363+
? {
364+
allowedDomains: [{ hostname: new URL(resolvedConfig.siteUrl).hostname }],
365+
}
349366
: {}),
350367
};
351368

@@ -473,9 +490,28 @@ export function emdash(config: EmDashConfig = {}): AstroIntegration {
473490
order: "pre",
474491
});
475492

476-
printRoutesSummary(logger);
493+
// Route info is printed with absolute, clickable URLs once the
494+
// dev server is listening (see astro:server:setup), since the
495+
// port isn't known yet here. Nothing useful to print for build.
477496
},
478497
"astro:server:setup": ({ server, logger }) => {
498+
// Print route info with absolute, clickable URLs once the server
499+
// is listening. Only in `astro dev` -- the dev-bypass shortcut is
500+
// dev-only and the port is unknown until now.
501+
if (astroCommand === "dev") {
502+
server.httpServer?.once("listening", () => {
503+
const address = server.httpServer?.address();
504+
if (!address || typeof address === "string") return;
505+
let host = address.address;
506+
if (host === "::1" || host === "::" || host === "0.0.0.0") {
507+
host = "localhost";
508+
} else if (address.family === "IPv6") {
509+
host = `[${host}]`;
510+
}
511+
printDevServerInfo(`http://${host}:${address.port}`, resolvedConfig.mcp !== false);
512+
});
513+
}
514+
479515
// Generate types once the server is listening.
480516
// The endpoint returns the types content; we write the file here
481517
// (in Node) because workerd has no real filesystem access.

0 commit comments

Comments
 (0)