@guren/server 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  logDevServerBanner,
3
3
  parseImportMap,
4
4
  startViteDevServer
5
- } from "./chunk-VXXZIXAP.js";
5
+ } from "./chunk-QH4NUKSV.js";
6
6
  import {
7
7
  PendingSchedule,
8
8
  Schedule,
@@ -1706,644 +1706,729 @@ var ErrorServiceProvider = class extends ServiceProvider {
1706
1706
  }
1707
1707
  };
1708
1708
 
1709
- // src/http/Application.ts
1710
- var MANAGED_VITE_ENV_FLAG = "GUREN_MANAGED_VITE_DEV_SERVER";
1711
- var DEFAULT_DEV_ENTRY_PATH = "/resources/js/dev-entry.ts";
1712
- function clearManagedViteEnv() {
1713
- if (typeof process === "undefined") {
1714
- return;
1709
+ // src/mvc/inertia/InertiaEngine.ts
1710
+ import { isAbsolute, resolve as resolvePath } from "path";
1711
+ import { pathToFileURL } from "url";
1712
+ ensureErrorStackTracePolyfill();
1713
+ var DEFAULT_TITLE = "Guren";
1714
+ var DEV_FALLBACK_IMPORT_MAP = {
1715
+ react: "https://esm.sh/react@19.0.0?dev",
1716
+ "react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime?dev",
1717
+ "react/jsx-dev-runtime": "https://esm.sh/react@19.0.0/jsx-dev-runtime?dev",
1718
+ "react-dom/client": "https://esm.sh/react-dom@19.0.0/client?dev",
1719
+ "@guren/inertia-client": "/vendor/inertia-client.tsx",
1720
+ "@inertiajs/react": "https://esm.sh/@inertiajs/react@2.2.15?dev&external=react,react-dom/client"
1721
+ };
1722
+ async function inertia(component, props, options = {}) {
1723
+ const resolvedVersion = options.version ?? process.env.GUREN_INERTIA_VERSION ?? void 0;
1724
+ const page = {
1725
+ component,
1726
+ props,
1727
+ url: options.url ?? "",
1728
+ version: resolvedVersion
1729
+ };
1730
+ const request = options.request;
1731
+ const isInertiaVisit = Boolean(request?.headers.get("X-Inertia"));
1732
+ const prefersJson = request ? acceptsJson(request) : false;
1733
+ const versionHeader = resolvedVersion ? { "X-Inertia-Version": resolvedVersion } : {};
1734
+ if (request && resolvedVersion && isInertiaVisit && request.method === "GET") {
1735
+ const clientVersion = request.headers.get("X-Inertia-Version");
1736
+ if (clientVersion !== resolvedVersion) {
1737
+ return new Response(null, {
1738
+ status: 409,
1739
+ headers: {
1740
+ "X-Inertia-Location": options.url ?? request.url,
1741
+ Vary: "Accept"
1742
+ }
1743
+ });
1744
+ }
1715
1745
  }
1716
- if (process.env[MANAGED_VITE_ENV_FLAG] === "1") {
1717
- delete process.env.VITE_DEV_SERVER_URL;
1746
+ if (isInertiaVisit || prefersJson) {
1747
+ return new Response(serializePage(page), {
1748
+ status: options.status ?? 200,
1749
+ headers: {
1750
+ "Content-Type": "application/json; charset=utf-8",
1751
+ "X-Inertia": "true",
1752
+ Vary: "Accept",
1753
+ ...versionHeader,
1754
+ ...options.headers
1755
+ }
1756
+ });
1718
1757
  }
1719
- delete process.env[MANAGED_VITE_ENV_FLAG];
1758
+ const html = await renderDocument(page, options);
1759
+ return new Response(html, {
1760
+ status: options.status ?? 200,
1761
+ headers: {
1762
+ "Content-Type": "text/html; charset=utf-8",
1763
+ "X-Inertia": "true",
1764
+ Vary: "Accept",
1765
+ ...versionHeader,
1766
+ ...options.headers
1767
+ }
1768
+ });
1720
1769
  }
1721
- function normalizeDevEntryUrl(baseUrl) {
1722
- return `${baseUrl.replace(/\/$/u, "")}${DEFAULT_DEV_ENTRY_PATH}`;
1770
+ async function renderDocument(page, options) {
1771
+ const defaultEntry = process.env.GUREN_INERTIA_ENTRY ?? "/resources/js/app.tsx";
1772
+ const entry = options.entry ?? defaultEntry;
1773
+ const title = escapeHtml(options.title ?? DEFAULT_TITLE);
1774
+ const styles = options.styles ?? parseStylesEnv(process.env.GUREN_INERTIA_STYLES);
1775
+ const envImportMap = parseImportMap(process.env.GUREN_INERTIA_IMPORT_MAP, {
1776
+ context: "GUREN_INERTIA_IMPORT_MAP"
1777
+ });
1778
+ const isProduction = process.env.NODE_ENV === "production";
1779
+ const importMapEntries = {
1780
+ ...isProduction ? {} : DEV_FALLBACK_IMPORT_MAP,
1781
+ ...envImportMap,
1782
+ ...options.importMap ?? {}
1783
+ };
1784
+ const importMap = JSON.stringify({ imports: importMapEntries }, null, 2);
1785
+ const serializedPage = serializePage(page);
1786
+ const stylesheetLinks = renderStyles(styles);
1787
+ const docsHeadBootstrap = renderDocsHeadBootstrap(page.component);
1788
+ const ssrResult = await tryRenderSsr(page, options);
1789
+ const headElements = (ssrResult?.head ?? []).map(normalizeHeadElement);
1790
+ const hasCustomTitle = headElements.some(
1791
+ (element) => /<title\b[^>]*>/iu.test(element)
1792
+ );
1793
+ const headSegments = [
1794
+ '<meta charset="utf-8" />',
1795
+ '<meta name="viewport" content="width=device-width, initial-scale=1" />',
1796
+ hasCustomTitle ? "" : `<title>${title}</title>`,
1797
+ docsHeadBootstrap,
1798
+ stylesheetLinks,
1799
+ ...headElements,
1800
+ Object.keys(importMapEntries).length > 0 ? `<script type="importmap">${importMap}</script>` : "",
1801
+ `<script>window.__INERTIA_PAGE__ = ${serializedPage};</script>`
1802
+ ].filter((segment) => segment && segment.length > 0);
1803
+ const appMarkup = ssrResult?.body ?? `<script data-page="app" type="application/json">${serializedPage}</script><div id="app"></div>`;
1804
+ const bodyClass = resolveBodyClass(page.component);
1805
+ const bodyAttributes = bodyClass ? ` class="${escapeAttribute(bodyClass)}"` : "";
1806
+ return `<!DOCTYPE html>
1807
+ <html lang="en">
1808
+ <head>
1809
+ ${headSegments.join("\n ")}
1810
+ </head>
1811
+ <body${bodyAttributes}>
1812
+ ${appMarkup}
1813
+ <script type="module" src="${entry}"></script>
1814
+ </body>
1815
+ </html>`;
1723
1816
  }
1724
- function syncManagedInertiaDevEntry(devServerUrl) {
1725
- if (typeof process === "undefined") {
1726
- return;
1727
- }
1728
- const nextEntry = normalizeDevEntryUrl(devServerUrl);
1729
- const currentEntry = process.env.GUREN_INERTIA_ENTRY;
1730
- if (!currentEntry || currentEntry.endsWith(DEFAULT_DEV_ENTRY_PATH)) {
1731
- process.env.GUREN_INERTIA_ENTRY = nextEntry;
1817
+ async function tryRenderSsr(page, options) {
1818
+ const ssrOptions = options.ssr;
1819
+ if (ssrOptions?.enabled === false) {
1820
+ return void 0;
1732
1821
  }
1733
- }
1734
- function getGlobalState() {
1735
- return globalThis;
1736
- }
1737
- async function stopActiveBunServer() {
1738
- const state = getGlobalState();
1739
- const previous = state.__gurenActiveServer;
1740
- if (!previous?.stop) {
1741
- state.__gurenActiveServer = void 0;
1742
- return;
1822
+ const manifest = (ssrOptions?.manifest ?? process.env.GUREN_INERTIA_SSR_MANIFEST)?.trim() || void 0;
1823
+ const renderer = ssrOptions?.render ?? await loadSsrRenderer(
1824
+ ssrOptions?.entry ?? process.env.GUREN_INERTIA_SSR_ENTRY
1825
+ );
1826
+ if (!renderer) {
1827
+ return void 0;
1743
1828
  }
1744
1829
  try {
1745
- await Promise.resolve(previous.stop());
1830
+ const result = await renderer({
1831
+ page,
1832
+ request: options.request,
1833
+ manifest
1834
+ });
1835
+ if (!result || typeof result.body !== "string") {
1836
+ return void 0;
1837
+ }
1838
+ return {
1839
+ body: result.body,
1840
+ head: Array.isArray(result.head) ? result.head : []
1841
+ };
1746
1842
  } catch (error) {
1747
- console.warn("Failed to stop previous Bun server:", error);
1748
- } finally {
1749
- state.__gurenActiveServer = void 0;
1843
+ console.error(
1844
+ "Inertia SSR renderer failed; falling back to client rendering.",
1845
+ error
1846
+ );
1847
+ return void 0;
1750
1848
  }
1751
1849
  }
1752
- function setActiveBunServer(server) {
1753
- getGlobalState().__gurenActiveServer = server;
1754
- }
1755
- async function stopActiveViteDevServer() {
1756
- const state = getGlobalState();
1757
- const previous = state.__gurenActiveViteDevServer;
1758
- if (!previous) {
1759
- state.__gurenActiveViteDevServer = void 0;
1760
- clearManagedViteEnv();
1761
- return;
1850
+ async function loadSsrRenderer(entry) {
1851
+ const specifier = entry?.trim();
1852
+ if (!specifier) {
1853
+ return void 0;
1762
1854
  }
1855
+ const normalized = normalizeSsrSpecifier(specifier);
1763
1856
  try {
1764
- await previous.close();
1857
+ const module = await import(normalized);
1858
+ const renderCandidate = extractSsrRenderer(module);
1859
+ if (!renderCandidate) {
1860
+ console.warn(
1861
+ `Inertia SSR entry "${specifier}" does not export a renderer. Expected a default export or a named "render" function.`
1862
+ );
1863
+ return void 0;
1864
+ }
1865
+ return renderCandidate;
1765
1866
  } catch (error) {
1766
- console.warn("Failed to stop previous Vite dev server:", error);
1767
- } finally {
1768
- state.__gurenActiveViteDevServer = void 0;
1769
- clearManagedViteEnv();
1867
+ console.error(`Failed to import Inertia SSR entry "${specifier}".`, error);
1868
+ return void 0;
1770
1869
  }
1771
1870
  }
1772
- function setActiveViteDevServer(server) {
1773
- getGlobalState().__gurenActiveViteDevServer = server;
1774
- }
1775
- var Application = class {
1776
- constructor(options = {}) {
1777
- this.options = options;
1778
- this.hono = new Hono();
1779
- this.container = new Container();
1780
- this.router = new Router();
1781
- this.authManager = new AuthManager();
1782
- this.providerManager = new ProviderManager(this.container);
1783
- this.container.instance("app", this);
1784
- this.container.instance("hono", this.hono);
1785
- this.container.instance("auth", this.authManager);
1786
- this.container.instance("router", this.router);
1787
- if (!this.authManager.guardNames().length) {
1788
- this.authManager.registerGuard("web", ({ ctx, session, manager }) => {
1789
- let provider;
1790
- try {
1791
- provider = manager.getProvider("users");
1792
- } catch {
1793
- provider = { retrieveById: async () => null, retrieveByCredentials: async () => null, validateCredentials: async () => false };
1794
- }
1795
- return new SessionGuard({ provider, session });
1796
- });
1797
- this.authManager.setDefaultGuard("web");
1798
- }
1799
- if (this.options.auth) {
1800
- this.providerManager.register(AuthServiceProvider);
1801
- } else {
1802
- this.hono.use("*", attachAuthContext((ctx) => this.authManager.createAuthContext(ctx)));
1803
- }
1804
- this.providerManager.register(AuthorizationServiceProvider);
1805
- const userProviders = Array.isArray(this.options.providers) ? this.options.providers : [];
1806
- const hasUserErrorProvider = userProviders.some(
1807
- (provider) => provider === ErrorServiceProvider || provider.prototype instanceof ErrorServiceProvider
1808
- );
1809
- if (!hasUserErrorProvider) {
1810
- this.providerManager.register(ErrorServiceProvider);
1811
- }
1812
- if (userProviders.length > 0) {
1813
- this.providerManager.registerMany(userProviders);
1814
- }
1871
+ function extractSsrRenderer(module) {
1872
+ if (!module || typeof module !== "object") {
1873
+ return void 0;
1815
1874
  }
1816
- hono;
1817
- container;
1818
- router;
1819
- providerManager;
1820
- authManager;
1821
- viteDevServer;
1822
- bunServer;
1823
- viteTeardownRegistered = false;
1824
- bunTeardownRegistered = false;
1825
- autoSessionAttached = false;
1826
- routesRegistered = false;
1827
- get auth() {
1828
- return this.authManager;
1875
+ const candidate = typeof module.render === "function" ? module.render : typeof module.default === "function" ? module.default : void 0;
1876
+ if (!candidate) {
1877
+ return void 0;
1829
1878
  }
1830
- get authOptions() {
1831
- return this.options.auth;
1879
+ return (context) => Promise.resolve(candidate(context));
1880
+ }
1881
+ function normalizeSsrSpecifier(specifier) {
1882
+ if (specifier.startsWith("file://") || isUrlLike(specifier)) {
1883
+ return specifier;
1832
1884
  }
1833
- markAutoSessionAttached() {
1834
- this.autoSessionAttached = true;
1885
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1886
+ const absolute = resolvePath(process.cwd(), specifier);
1887
+ return pathToFileURL(absolute).href;
1835
1888
  }
1836
- hasAutoSessionAttached() {
1837
- return this.autoSessionAttached;
1889
+ if (isAbsolute(specifier)) {
1890
+ return pathToFileURL(specifier).href;
1838
1891
  }
1839
- /**
1840
- * Mounts the application router onto the Hono instance.
1841
- */
1842
- async mountRoutes() {
1843
- if (this.options.routes && !this.routesRegistered) {
1844
- await this.options.routes(this.router);
1845
- this.routesRegistered = true;
1892
+ return specifier;
1893
+ }
1894
+ function isUrlLike(specifier) {
1895
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(specifier);
1896
+ }
1897
+ function renderStyles(styles) {
1898
+ if (!styles.length) {
1899
+ return "";
1900
+ }
1901
+ return styles.map((href) => `<link rel="stylesheet" href="${escapeAttribute(href)}" />`).join("\n ");
1902
+ }
1903
+ function normalizeHeadElement(element) {
1904
+ const markup = typeof element === "string" ? element : String(element ?? "");
1905
+ const pattern = /href="\/(?!public\/)([^"?]+\.(?:js|css))(\?[^"']*)?"/g;
1906
+ return markup.replace(
1907
+ pattern,
1908
+ (_match, file2, query = "") => `href="/public/assets/${file2}${query}"`
1909
+ );
1910
+ }
1911
+ function resolveBodyClass(componentName) {
1912
+ return componentName.startsWith("Docs/") ? "docs-theme" : "";
1913
+ }
1914
+ function renderDocsHeadBootstrap(componentName) {
1915
+ if (!componentName.startsWith("Docs/")) {
1916
+ return "";
1917
+ }
1918
+ const criticalStyles = [
1919
+ "html,body{background:#ffffff;color:#1f2937;}",
1920
+ "body.docs-theme{background:#ffffff;color:#1f2937;}",
1921
+ "html.dark,html.dark body,html.dark body.docs-theme{background:#1a1a2e;color:#e0def4;}",
1922
+ ".size-4{width:1rem;height:1rem;}",
1923
+ ".size-5{width:1.25rem;height:1.25rem;}",
1924
+ ".size-6{width:1.5rem;height:1.5rem;}",
1925
+ ".size-8{width:2rem;height:2rem;}",
1926
+ ".size-9{width:2.25rem;height:2.25rem;}"
1927
+ ].join("");
1928
+ const prepaintScript = [
1929
+ "(function(){",
1930
+ "try{",
1931
+ "var mode=localStorage.getItem('guren-color-mode')||'system';",
1932
+ "var dark=mode==='dark'||(mode!=='light'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);",
1933
+ "var root=document.documentElement;",
1934
+ "root.classList.toggle('dark',dark);",
1935
+ "root.style.colorScheme=dark?'dark':'light';",
1936
+ "}catch(_){",
1937
+ "}",
1938
+ "})();"
1939
+ ].join("");
1940
+ return `<style id="guren-docs-critical">${criticalStyles}</style><script>${prepaintScript}</script>`;
1941
+ }
1942
+ function serializePage(page) {
1943
+ return JSON.stringify(page).replace(/[<\u2028\u2029]/gu, (char) => {
1944
+ switch (char) {
1945
+ case "<":
1946
+ return "\\u003c";
1947
+ case "\u2028":
1948
+ return "\\u2028";
1949
+ case "\u2029":
1950
+ return "\\u2029";
1951
+ default:
1952
+ return char;
1846
1953
  }
1847
- this.router.mount(this.hono, { container: this.container });
1954
+ });
1955
+ }
1956
+ function acceptsJson(request) {
1957
+ const accept = request.headers.get("accept")?.toLowerCase() ?? "";
1958
+ if (!accept || accept === "*/*") {
1959
+ return false;
1848
1960
  }
1849
- /**
1850
- * Allows registering global middlewares directly on the underlying Hono app.
1851
- */
1852
- use(path, ...middleware) {
1853
- this.hono.use(path, ...middleware);
1961
+ if (accept.includes("text/html")) {
1962
+ return false;
1854
1963
  }
1855
- /**
1856
- * Executes provider registration, boot callback, mounts routes, and boots providers.
1857
- */
1858
- async boot() {
1859
- this.mountSecurityDefaults();
1860
- await this.providerManager.registerAll();
1861
- await this.options.boot?.(this.hono);
1862
- await this.mountRoutes();
1863
- await this.mountMcpEndpoint();
1864
- await this.providerManager.bootAll();
1964
+ return accept.includes("application/json") || accept.includes("json");
1965
+ }
1966
+ function escapeHtml(value) {
1967
+ return value.replace(/[&<>"']/gu, (char) => {
1968
+ switch (char) {
1969
+ case "&":
1970
+ return "&amp;";
1971
+ case "<":
1972
+ return "&lt;";
1973
+ case ">":
1974
+ return "&gt;";
1975
+ case '"':
1976
+ return "&quot;";
1977
+ case "'":
1978
+ return "&#39;";
1979
+ default:
1980
+ return char;
1981
+ }
1982
+ });
1983
+ }
1984
+ function escapeAttribute(value) {
1985
+ return value.replace(/[&"]/gu, (char) => {
1986
+ if (char === "&") {
1987
+ return "&amp;";
1988
+ }
1989
+ return "&quot;";
1990
+ });
1991
+ }
1992
+ function parseStylesEnv(value) {
1993
+ if (!value) {
1994
+ return [];
1865
1995
  }
1866
- /**
1867
- * Registers default security middleware (headers + host authorization).
1868
- */
1869
- mountSecurityDefaults() {
1870
- const { securityHeaders } = this.options;
1871
- if (securityHeaders !== false) {
1872
- this.hono.use("*", createSecurityHeaders(securityHeaders ?? {}));
1996
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
1997
+ }
1998
+
1999
+ // src/mvc/ViewEngine.ts
2000
+ var ViewEngine = class {
2001
+ static engines = /* @__PURE__ */ new Map();
2002
+ static register(name, renderer) {
2003
+ this.engines.set(name, renderer);
2004
+ }
2005
+ static has(name) {
2006
+ return this.engines.has(name);
2007
+ }
2008
+ static render(name, template, props) {
2009
+ const engine = this.engines.get(name);
2010
+ if (!engine) {
2011
+ throw new Error(`View engine "${name}" has not been registered.`);
1873
2012
  }
1874
- this.mountHostAuthorization();
2013
+ return engine(template, props);
1875
2014
  }
1876
- mountHostAuthorization() {
1877
- const { hostAuthorization } = this.options;
1878
- if (hostAuthorization === false || !hostAuthorization) return;
1879
- this.hono.use("*", createHostAuthorizationMiddleware(hostAuthorization));
2015
+ };
2016
+
2017
+ // src/mvc/inertia/shared.ts
2018
+ var resolver = null;
2019
+ function setInertiaSharedProps(resolverFn) {
2020
+ resolver = resolverFn;
2021
+ }
2022
+ function getInertiaSharedPropsResolver() {
2023
+ return resolver;
2024
+ }
2025
+ function shareInertiaProps(resolverFn) {
2026
+ const previous = resolver;
2027
+ resolver = async (ctx) => {
2028
+ const prev = previous ? await previous(ctx) : {};
2029
+ const next = await resolverFn(ctx);
2030
+ return { ...prev, ...next };
2031
+ };
2032
+ }
2033
+ async function resolveSharedInertiaProps(ctx) {
2034
+ if (!resolver) return {};
2035
+ const shared = await resolver(ctx);
2036
+ if (shared && typeof shared === "object") {
2037
+ return shared;
2038
+ }
2039
+ return {};
2040
+ }
2041
+
2042
+ // src/providers/InertiaServiceProvider.ts
2043
+ var InertiaServiceProvider = class extends ServiceProvider {
2044
+ register() {
2045
+ if (!ViewEngine.has("inertia")) {
2046
+ ViewEngine.register("inertia", inertia);
2047
+ }
2048
+ }
2049
+ boot() {
2050
+ this.registerValidationRenderer();
2051
+ this.registerSharedErrors();
1880
2052
  }
1881
2053
  /**
1882
- * Mounts the MCP endpoint at /_guren/mcp when explicitly enabled.
1883
- * Requires GUREN_MCP=1 environment variable. Never active in production.
1884
- * This allows AI coding agents to introspect the project.
2054
+ * Register a custom renderer for ValidationException on Inertia requests.
2055
+ * Non-Inertia requests fall through to the default JSON 422 response.
1885
2056
  */
1886
- async mountMcpEndpoint() {
1887
- if (typeof process === "undefined" || process.env?.NODE_ENV === "production" || process.env?.GUREN_MCP !== "1") {
1888
- return;
1889
- }
2057
+ registerValidationRenderer() {
2058
+ let handler;
1890
2059
  try {
1891
- const { McpServiceProvider } = await import("./McpServiceProvider-JW6PDVMD.js");
1892
- const provider = new McpServiceProvider(this.container);
1893
- provider.register();
1894
- await provider.boot();
2060
+ handler = this.container.make("exception.handler");
1895
2061
  } catch {
2062
+ return;
1896
2063
  }
2064
+ handler.render(ValidationException, (error, ctx) => {
2065
+ const isInertia = ctx.req.header("X-Inertia");
2066
+ if (!isInertia) {
2067
+ return ctx.json(
2068
+ {
2069
+ message: error.message,
2070
+ errors: error.errors
2071
+ },
2072
+ 422
2073
+ );
2074
+ }
2075
+ const session = getSessionFromContext(ctx);
2076
+ if (session) {
2077
+ const flattened = {};
2078
+ for (const [key, messages] of Object.entries(error.errors ?? {})) {
2079
+ flattened[key] = messages[0] ?? "";
2080
+ }
2081
+ session.flash("errors", flattened);
2082
+ }
2083
+ const referer = ctx.req.header("Referer") ?? "/";
2084
+ return new Response(null, {
2085
+ status: 303,
2086
+ headers: { Location: referer }
2087
+ });
2088
+ });
1897
2089
  }
1898
2090
  /**
1899
- * Fetch handler to integrate with Bun.serve or any standard Fetch runtime.
2091
+ * Wrap existing shared props resolver to auto-inject flashed `errors`.
1900
2092
  */
1901
- async fetch(request, env, executionCtx) {
1902
- return this.hono.fetch(request, env, executionCtx);
2093
+ registerSharedErrors() {
2094
+ const previous = getInertiaSharedPropsResolver();
2095
+ setInertiaSharedProps(async (ctx) => {
2096
+ const prev = previous ? await previous(ctx) : {};
2097
+ const session = getSessionFromContext(ctx);
2098
+ const errors = session?.getFlash("errors");
2099
+ return {
2100
+ ...prev,
2101
+ ...errors && Object.keys(errors).length > 0 ? { errors } : {}
2102
+ };
2103
+ });
1903
2104
  }
1904
- /**
1905
- * Convenience helper to start a Bun server when available.
1906
- */
1907
- async listen(options = {}) {
1908
- if (!Bun) {
1909
- throw new Error("Bun runtime is required to call Application.listen");
1910
- }
1911
- await stopActiveBunServer();
1912
- await stopActiveViteDevServer();
1913
- const { port = 3e3, hostname = "0.0.0.0", assetsUrl, vite } = options;
1914
- const externalAssetsUrl = typeof process !== "undefined" && process.env?.[MANAGED_VITE_ENV_FLAG] !== "1" ? process.env?.VITE_DEV_SERVER_URL : void 0;
1915
- let resolvedAssetsUrl = assetsUrl ?? externalAssetsUrl;
1916
- const shouldStartVite = vite !== false && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !resolvedAssetsUrl && process.env?.GUREN_DEV_VITE !== "0";
1917
- if (shouldStartVite) {
1918
- const viteOptions = typeof vite === "object" ? vite : void 0;
1919
- try {
1920
- await this.closeViteDevServer();
1921
- const { server: server2, localUrl } = await startViteDevServer({
1922
- root: viteOptions?.root ?? process.cwd(),
1923
- config: viteOptions?.config,
1924
- host: viteOptions?.host ?? true,
1925
- port: viteOptions?.port
1926
- });
1927
- this.viteDevServer = server2;
1928
- setActiveViteDevServer(server2);
1929
- resolvedAssetsUrl = localUrl;
1930
- if (typeof process !== "undefined") {
1931
- process.env.VITE_DEV_SERVER_URL = resolvedAssetsUrl;
1932
- process.env[MANAGED_VITE_ENV_FLAG] = "1";
2105
+ };
2106
+
2107
+ // src/http/Application.ts
2108
+ var MANAGED_VITE_ENV_FLAG = "GUREN_MANAGED_VITE_DEV_SERVER";
2109
+ var DEFAULT_DEV_ENTRY_PATH = "/resources/js/dev-entry.ts";
2110
+ function clearManagedViteEnv() {
2111
+ if (typeof process === "undefined") {
2112
+ return;
2113
+ }
2114
+ if (process.env[MANAGED_VITE_ENV_FLAG] === "1") {
2115
+ delete process.env.VITE_DEV_SERVER_URL;
2116
+ }
2117
+ delete process.env[MANAGED_VITE_ENV_FLAG];
2118
+ }
2119
+ function normalizeDevEntryUrl(baseUrl) {
2120
+ return `${baseUrl.replace(/\/$/u, "")}${DEFAULT_DEV_ENTRY_PATH}`;
2121
+ }
2122
+ function syncManagedInertiaDevEntry(devServerUrl) {
2123
+ if (typeof process === "undefined") {
2124
+ return;
2125
+ }
2126
+ const nextEntry = normalizeDevEntryUrl(devServerUrl);
2127
+ const currentEntry = process.env.GUREN_INERTIA_ENTRY;
2128
+ if (!currentEntry || currentEntry.endsWith(DEFAULT_DEV_ENTRY_PATH)) {
2129
+ process.env.GUREN_INERTIA_ENTRY = nextEntry;
2130
+ }
2131
+ }
2132
+ function getGlobalState() {
2133
+ return globalThis;
2134
+ }
2135
+ async function stopActiveBunServer() {
2136
+ const state = getGlobalState();
2137
+ const previous = state.__gurenActiveServer;
2138
+ if (!previous?.stop) {
2139
+ state.__gurenActiveServer = void 0;
2140
+ return;
2141
+ }
2142
+ try {
2143
+ await Promise.resolve(previous.stop());
2144
+ } catch (error) {
2145
+ console.warn("Failed to stop previous Bun server:", error);
2146
+ } finally {
2147
+ state.__gurenActiveServer = void 0;
2148
+ }
2149
+ }
2150
+ function setActiveBunServer(server) {
2151
+ getGlobalState().__gurenActiveServer = server;
2152
+ }
2153
+ async function stopActiveViteDevServer() {
2154
+ const state = getGlobalState();
2155
+ const previous = state.__gurenActiveViteDevServer;
2156
+ if (!previous) {
2157
+ state.__gurenActiveViteDevServer = void 0;
2158
+ clearManagedViteEnv();
2159
+ return;
2160
+ }
2161
+ try {
2162
+ await previous.close();
2163
+ } catch (error) {
2164
+ console.warn("Failed to stop previous Vite dev server:", error);
2165
+ } finally {
2166
+ state.__gurenActiveViteDevServer = void 0;
2167
+ clearManagedViteEnv();
2168
+ }
2169
+ }
2170
+ function setActiveViteDevServer(server) {
2171
+ getGlobalState().__gurenActiveViteDevServer = server;
2172
+ }
2173
+ var Application = class {
2174
+ constructor(options = {}) {
2175
+ this.options = options;
2176
+ this.hono = new Hono();
2177
+ this.container = new Container();
2178
+ this.router = new Router();
2179
+ this.authManager = new AuthManager();
2180
+ this.providerManager = new ProviderManager(this.container);
2181
+ this.container.instance("app", this);
2182
+ this.container.instance("hono", this.hono);
2183
+ this.container.instance("auth", this.authManager);
2184
+ this.container.instance("router", this.router);
2185
+ if (!this.authManager.guardNames().length) {
2186
+ this.authManager.registerGuard("web", ({ ctx, session, manager }) => {
2187
+ let provider;
2188
+ try {
2189
+ provider = manager.getProvider("users");
2190
+ } catch {
2191
+ provider = { retrieveById: async () => null, retrieveByCredentials: async () => null, validateCredentials: async () => false };
1933
2192
  }
1934
- syncManagedInertiaDevEntry(resolvedAssetsUrl);
1935
- this.registerViteTeardown();
1936
- } catch (error) {
1937
- console.error("Failed to start Vite dev server:", error);
1938
- process.exit(1);
1939
- }
1940
- }
1941
- const server = Bun.serve({
1942
- port,
1943
- hostname,
1944
- fetch: (request) => this.fetch(request)
1945
- });
1946
- this.bunServer = server;
1947
- setActiveBunServer(server);
1948
- this.registerBunTeardown();
1949
- const shouldLogBanner = typeof process === "undefined" || process.env?.NODE_ENV !== "production" && process.env?.GUREN_DEV_BANNER !== "0";
1950
- if (shouldLogBanner) {
1951
- this.logDevServerBanner({
1952
- hostname,
1953
- port,
1954
- assetsUrl: resolvedAssetsUrl ?? "http://localhost:5173"
2193
+ return new SessionGuard({ provider, session });
1955
2194
  });
2195
+ this.authManager.setDefaultGuard("web");
2196
+ }
2197
+ if (this.options.auth) {
2198
+ this.providerManager.register(AuthServiceProvider);
2199
+ } else {
2200
+ this.hono.use("*", attachAuthContext((ctx) => this.authManager.createAuthContext(ctx)));
2201
+ }
2202
+ this.providerManager.register(AuthorizationServiceProvider);
2203
+ const userProviders = Array.isArray(this.options.providers) ? this.options.providers : [];
2204
+ const hasUserErrorProvider = userProviders.some(
2205
+ (provider) => provider === ErrorServiceProvider || provider.prototype instanceof ErrorServiceProvider
2206
+ );
2207
+ if (!hasUserErrorProvider) {
2208
+ this.providerManager.register(ErrorServiceProvider);
2209
+ }
2210
+ if (userProviders.length > 0) {
2211
+ this.providerManager.registerMany(userProviders);
2212
+ }
2213
+ const hasUserInertiaProvider = userProviders.some(
2214
+ (provider) => provider === InertiaServiceProvider || provider.prototype instanceof InertiaServiceProvider
2215
+ );
2216
+ if (!hasUserInertiaProvider) {
2217
+ this.providerManager.register(InertiaServiceProvider);
1956
2218
  }
1957
2219
  }
2220
+ hono;
2221
+ container;
2222
+ router;
2223
+ providerManager;
2224
+ authManager;
2225
+ viteDevServer;
2226
+ bunServer;
2227
+ viteTeardownRegistered = false;
2228
+ bunTeardownRegistered = false;
2229
+ autoSessionAttached = false;
2230
+ routesRegistered = false;
2231
+ get auth() {
2232
+ return this.authManager;
2233
+ }
2234
+ get authOptions() {
2235
+ return this.options.auth;
2236
+ }
2237
+ markAutoSessionAttached() {
2238
+ this.autoSessionAttached = true;
2239
+ }
2240
+ hasAutoSessionAttached() {
2241
+ return this.autoSessionAttached;
2242
+ }
1958
2243
  /**
1959
- * Register a service provider.
2244
+ * Mounts the application router onto the Hono instance.
1960
2245
  */
1961
- register(provider) {
1962
- this.providerManager.register(provider);
1963
- return this;
2246
+ async mountRoutes() {
2247
+ if (this.options.routes && !this.routesRegistered) {
2248
+ await this.options.routes(this.router);
2249
+ this.routesRegistered = true;
2250
+ }
2251
+ this.router.mount(this.hono, { container: this.container });
1964
2252
  }
1965
2253
  /**
1966
- * Register multiple service providers.
2254
+ * Allows registering global middlewares directly on the underlying Hono app.
1967
2255
  */
1968
- registerMany(providers) {
1969
- this.providerManager.registerMany(providers);
1970
- return this;
2256
+ use(path, ...middleware) {
2257
+ this.hono.use(path, ...middleware);
1971
2258
  }
1972
2259
  /**
1973
- * Logs the rich development server banner to the console.
2260
+ * Executes provider registration, boot callback, mounts routes, and boots providers.
1974
2261
  */
1975
- logDevServerBanner(options) {
1976
- logDevServerBanner(options);
2262
+ async boot() {
2263
+ this.mountSecurityDefaults();
2264
+ await this.providerManager.registerAll();
2265
+ await this.options.boot?.(this.hono);
2266
+ await this.mountRoutes();
2267
+ await this.mountMcpEndpoint();
2268
+ await this.providerManager.bootAll();
1977
2269
  }
1978
- async closeViteDevServer() {
1979
- if (!this.viteDevServer) {
2270
+ /**
2271
+ * Registers default security middleware (headers + host authorization).
2272
+ */
2273
+ mountSecurityDefaults() {
2274
+ const { securityHeaders } = this.options;
2275
+ if (securityHeaders !== false) {
2276
+ this.hono.use("*", createSecurityHeaders(securityHeaders ?? {}));
2277
+ }
2278
+ this.mountHostAuthorization();
2279
+ }
2280
+ mountHostAuthorization() {
2281
+ const { hostAuthorization } = this.options;
2282
+ if (hostAuthorization === false || !hostAuthorization) return;
2283
+ this.hono.use("*", createHostAuthorizationMiddleware(hostAuthorization));
2284
+ }
2285
+ /**
2286
+ * Mounts the MCP endpoint at /_guren/mcp when explicitly enabled.
2287
+ * Requires GUREN_MCP=1 environment variable. Never active in production.
2288
+ * This allows AI coding agents to introspect the project.
2289
+ */
2290
+ async mountMcpEndpoint() {
2291
+ if (typeof process === "undefined" || process.env?.NODE_ENV === "production" || process.env?.GUREN_MCP !== "1") {
1980
2292
  return;
1981
2293
  }
1982
2294
  try {
1983
- await this.viteDevServer.close();
1984
- } catch (error) {
1985
- console.error("Error while shutting down Vite dev server:", error);
1986
- } finally {
1987
- if (getGlobalState().__gurenActiveViteDevServer === this.viteDevServer) {
1988
- setActiveViteDevServer();
1989
- }
1990
- this.viteDevServer = void 0;
1991
- this.viteTeardownRegistered = false;
1992
- clearManagedViteEnv();
2295
+ const { McpServiceProvider } = await import("./McpServiceProvider-JW6PDVMD.js");
2296
+ const provider = new McpServiceProvider(this.container);
2297
+ provider.register();
2298
+ await provider.boot();
2299
+ } catch {
1993
2300
  }
1994
2301
  }
1995
- registerViteTeardown() {
1996
- if (this.viteTeardownRegistered || !this.viteDevServer || typeof process === "undefined") {
1997
- return;
1998
- }
1999
- this.viteTeardownRegistered = true;
2000
- const exitHandler = () => {
2001
- this.closeViteDevServer().then(() => process.exit(0)).catch(() => process.exit(1));
2002
- };
2003
- process.once("SIGINT", exitHandler);
2004
- process.once("SIGTERM", exitHandler);
2005
- process.on("exit", () => {
2006
- if (this.viteDevServer) {
2007
- void this.viteDevServer.close();
2008
- }
2009
- });
2010
- }
2011
- registerBunTeardown() {
2012
- if (this.bunTeardownRegistered || typeof process === "undefined") {
2013
- return;
2014
- }
2015
- this.bunTeardownRegistered = true;
2016
- const exitHandler = () => {
2017
- stopActiveBunServer().then(() => process.exit(0)).catch(() => process.exit(1));
2018
- };
2019
- process.once("SIGINT", exitHandler);
2020
- process.once("SIGTERM", exitHandler);
2021
- process.on("exit", () => {
2022
- void stopActiveBunServer();
2023
- });
2024
- }
2025
- };
2026
- function createApp(options = {}) {
2027
- return new Application(options);
2028
- }
2029
-
2030
- // src/mvc/inertia/InertiaEngine.ts
2031
- import { isAbsolute, resolve as resolvePath } from "path";
2032
- import { pathToFileURL } from "url";
2033
- ensureErrorStackTracePolyfill();
2034
- var DEFAULT_TITLE = "Guren";
2035
- var DEFAULT_IMPORT_MAP = {
2036
- react: "https://esm.sh/react@19.0.0?dev",
2037
- "react/jsx-runtime": "https://esm.sh/react@19.0.0/jsx-runtime?dev",
2038
- "react/jsx-dev-runtime": "https://esm.sh/react@19.0.0/jsx-dev-runtime?dev",
2039
- "react-dom/client": "https://esm.sh/react-dom@19.0.0/client?dev",
2040
- "@guren/inertia-client": "/vendor/inertia-client.tsx",
2041
- "@inertiajs/react": "https://esm.sh/@inertiajs/react@2.2.15?dev&external=react,react-dom/client"
2042
- };
2043
- async function inertia(component, props, options = {}) {
2044
- const resolvedVersion = options.version ?? process.env.GUREN_INERTIA_VERSION ?? void 0;
2045
- const page = {
2046
- component,
2047
- props,
2048
- url: options.url ?? "",
2049
- version: resolvedVersion
2050
- };
2051
- const request = options.request;
2052
- const isInertiaVisit = Boolean(request?.headers.get("X-Inertia"));
2053
- const prefersJson = request ? acceptsJson(request) : false;
2054
- const versionHeader = resolvedVersion ? { "X-Inertia-Version": resolvedVersion } : {};
2055
- if (request && resolvedVersion && isInertiaVisit && request.method === "GET") {
2056
- const clientVersion = request.headers.get("X-Inertia-Version");
2057
- if (clientVersion !== resolvedVersion) {
2058
- return new Response(null, {
2059
- status: 409,
2060
- headers: {
2061
- "X-Inertia-Location": options.url ?? request.url,
2062
- Vary: "Accept"
2063
- }
2064
- });
2065
- }
2066
- }
2067
- if (isInertiaVisit || prefersJson) {
2068
- return new Response(serializePage(page), {
2069
- status: options.status ?? 200,
2070
- headers: {
2071
- "Content-Type": "application/json; charset=utf-8",
2072
- "X-Inertia": "true",
2073
- Vary: "Accept",
2074
- ...versionHeader,
2075
- ...options.headers
2076
- }
2077
- });
2078
- }
2079
- const html = await renderDocument(page, options);
2080
- return new Response(html, {
2081
- status: options.status ?? 200,
2082
- headers: {
2083
- "Content-Type": "text/html; charset=utf-8",
2084
- "X-Inertia": "true",
2085
- Vary: "Accept",
2086
- ...versionHeader,
2087
- ...options.headers
2088
- }
2089
- });
2090
- }
2091
- async function renderDocument(page, options) {
2092
- const defaultEntry = process.env.GUREN_INERTIA_ENTRY ?? "/resources/js/app.tsx";
2093
- const entry = options.entry ?? defaultEntry;
2094
- const title = escapeHtml(options.title ?? DEFAULT_TITLE);
2095
- const styles = options.styles ?? parseStylesEnv(process.env.GUREN_INERTIA_STYLES);
2096
- const envImportMap = parseImportMap(process.env.GUREN_INERTIA_IMPORT_MAP, {
2097
- context: "GUREN_INERTIA_IMPORT_MAP"
2098
- });
2099
- const importMap = JSON.stringify(
2100
- {
2101
- imports: {
2102
- ...DEFAULT_IMPORT_MAP,
2103
- ...envImportMap,
2104
- ...options.importMap ?? {}
2105
- }
2106
- },
2107
- null,
2108
- 2
2109
- );
2110
- const serializedPage = serializePage(page);
2111
- const stylesheetLinks = renderStyles(styles);
2112
- const docsHeadBootstrap = renderDocsHeadBootstrap(page.component);
2113
- const ssrResult = await tryRenderSsr(page, options);
2114
- const headElements = (ssrResult?.head ?? []).map(normalizeHeadElement);
2115
- const hasCustomTitle = headElements.some(
2116
- (element) => /<title\b[^>]*>/iu.test(element)
2117
- );
2118
- const headSegments = [
2119
- '<meta charset="utf-8" />',
2120
- '<meta name="viewport" content="width=device-width, initial-scale=1" />',
2121
- hasCustomTitle ? "" : `<title>${title}</title>`,
2122
- docsHeadBootstrap,
2123
- stylesheetLinks,
2124
- ...headElements,
2125
- `<script type="importmap">${importMap}</script>`,
2126
- `<script>window.__INERTIA_PAGE__ = ${serializedPage};</script>`
2127
- ].filter((segment) => segment && segment.length > 0);
2128
- const appMarkup = ssrResult?.body ?? `<script data-page="app" type="application/json">${serializedPage}</script><div id="app"></div>`;
2129
- const bodyClass = resolveBodyClass(page.component);
2130
- const bodyAttributes = bodyClass ? ` class="${escapeAttribute(bodyClass)}"` : "";
2131
- return `<!DOCTYPE html>
2132
- <html lang="en">
2133
- <head>
2134
- ${headSegments.join("\n ")}
2135
- </head>
2136
- <body${bodyAttributes}>
2137
- ${appMarkup}
2138
- <script type="module" src="${entry}"></script>
2139
- </body>
2140
- </html>`;
2141
- }
2142
- async function tryRenderSsr(page, options) {
2143
- const ssrOptions = options.ssr;
2144
- if (ssrOptions?.enabled === false) {
2145
- return void 0;
2146
- }
2147
- const manifest = (ssrOptions?.manifest ?? process.env.GUREN_INERTIA_SSR_MANIFEST)?.trim() || void 0;
2148
- const renderer = ssrOptions?.render ?? await loadSsrRenderer(
2149
- ssrOptions?.entry ?? process.env.GUREN_INERTIA_SSR_ENTRY
2150
- );
2151
- if (!renderer) {
2152
- return void 0;
2153
- }
2154
- try {
2155
- const result = await renderer({
2156
- page,
2157
- request: options.request,
2158
- manifest
2159
- });
2160
- if (!result || typeof result.body !== "string") {
2161
- return void 0;
2162
- }
2163
- return {
2164
- body: result.body,
2165
- head: Array.isArray(result.head) ? result.head : []
2166
- };
2167
- } catch (error) {
2168
- console.error(
2169
- "Inertia SSR renderer failed; falling back to client rendering.",
2170
- error
2171
- );
2172
- return void 0;
2173
- }
2174
- }
2175
- async function loadSsrRenderer(entry) {
2176
- const specifier = entry?.trim();
2177
- if (!specifier) {
2178
- return void 0;
2179
- }
2180
- const normalized = normalizeSsrSpecifier(specifier);
2181
- try {
2182
- const module = await import(normalized);
2183
- const renderCandidate = extractSsrRenderer(module);
2184
- if (!renderCandidate) {
2185
- console.warn(
2186
- `Inertia SSR entry "${specifier}" does not export a renderer. Expected a default export or a named "render" function.`
2187
- );
2188
- return void 0;
2189
- }
2190
- return renderCandidate;
2191
- } catch (error) {
2192
- console.error(`Failed to import Inertia SSR entry "${specifier}".`, error);
2193
- return void 0;
2194
- }
2195
- }
2196
- function extractSsrRenderer(module) {
2197
- if (!module || typeof module !== "object") {
2198
- return void 0;
2199
- }
2200
- const candidate = typeof module.render === "function" ? module.render : typeof module.default === "function" ? module.default : void 0;
2201
- if (!candidate) {
2202
- return void 0;
2203
- }
2204
- return (context) => Promise.resolve(candidate(context));
2205
- }
2206
- function normalizeSsrSpecifier(specifier) {
2207
- if (specifier.startsWith("file://") || isUrlLike(specifier)) {
2208
- return specifier;
2209
- }
2210
- if (specifier.startsWith("./") || specifier.startsWith("../")) {
2211
- const absolute = resolvePath(process.cwd(), specifier);
2212
- return pathToFileURL(absolute).href;
2213
- }
2214
- if (isAbsolute(specifier)) {
2215
- return pathToFileURL(specifier).href;
2216
- }
2217
- return specifier;
2218
- }
2219
- function isUrlLike(specifier) {
2220
- return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(specifier);
2221
- }
2222
- function renderStyles(styles) {
2223
- if (!styles.length) {
2224
- return "";
2225
- }
2226
- return styles.map((href) => `<link rel="stylesheet" href="${escapeAttribute(href)}" />`).join("\n ");
2227
- }
2228
- function normalizeHeadElement(element) {
2229
- const markup = typeof element === "string" ? element : String(element ?? "");
2230
- const pattern = /href="\/(?!public\/)([^"?]+\.(?:js|css))(\?[^"']*)?"/g;
2231
- return markup.replace(
2232
- pattern,
2233
- (_match, file2, query = "") => `href="/public/assets/${file2}${query}"`
2234
- );
2235
- }
2236
- function resolveBodyClass(componentName) {
2237
- return componentName.startsWith("Docs/") ? "docs-theme" : "";
2238
- }
2239
- function renderDocsHeadBootstrap(componentName) {
2240
- if (!componentName.startsWith("Docs/")) {
2241
- return "";
2302
+ /**
2303
+ * Fetch handler to integrate with Bun.serve or any standard Fetch runtime.
2304
+ */
2305
+ async fetch(request, env, executionCtx) {
2306
+ return this.hono.fetch(request, env, executionCtx);
2242
2307
  }
2243
- const criticalStyles = [
2244
- "html,body{background:#ffffff;color:#1f2937;}",
2245
- "body.docs-theme{background:#ffffff;color:#1f2937;}",
2246
- "html.dark,html.dark body,html.dark body.docs-theme{background:#1a1a2e;color:#e0def4;}",
2247
- ".size-4{width:1rem;height:1rem;}",
2248
- ".size-5{width:1.25rem;height:1.25rem;}",
2249
- ".size-6{width:1.5rem;height:1.5rem;}",
2250
- ".size-8{width:2rem;height:2rem;}",
2251
- ".size-9{width:2.25rem;height:2.25rem;}"
2252
- ].join("");
2253
- const prepaintScript = [
2254
- "(function(){",
2255
- "try{",
2256
- "var mode=localStorage.getItem('guren-color-mode')||'system';",
2257
- "var dark=mode==='dark'||(mode!=='light'&&window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches);",
2258
- "var root=document.documentElement;",
2259
- "root.classList.toggle('dark',dark);",
2260
- "root.style.colorScheme=dark?'dark':'light';",
2261
- "}catch(_){",
2262
- "}",
2263
- "})();"
2264
- ].join("");
2265
- return `<style id="guren-docs-critical">${criticalStyles}</style><script>${prepaintScript}</script>`;
2266
- }
2267
- function serializePage(page) {
2268
- return JSON.stringify(page).replace(/[<\u2028\u2029]/gu, (char) => {
2269
- switch (char) {
2270
- case "<":
2271
- return "\\u003c";
2272
- case "\u2028":
2273
- return "\\u2028";
2274
- case "\u2029":
2275
- return "\\u2029";
2276
- default:
2277
- return char;
2308
+ /**
2309
+ * Convenience helper to start a Bun server when available.
2310
+ */
2311
+ async listen(options = {}) {
2312
+ if (!Bun) {
2313
+ throw new Error("Bun runtime is required to call Application.listen");
2314
+ }
2315
+ await stopActiveBunServer();
2316
+ await stopActiveViteDevServer();
2317
+ const { port = 3e3, hostname = "0.0.0.0", assetsUrl, vite } = options;
2318
+ const externalAssetsUrl = typeof process !== "undefined" && process.env?.[MANAGED_VITE_ENV_FLAG] !== "1" ? process.env?.VITE_DEV_SERVER_URL : void 0;
2319
+ let resolvedAssetsUrl = assetsUrl ?? externalAssetsUrl;
2320
+ const shouldStartVite = vite !== false && typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && !resolvedAssetsUrl && process.env?.GUREN_DEV_VITE !== "0";
2321
+ if (shouldStartVite) {
2322
+ const viteOptions = typeof vite === "object" ? vite : void 0;
2323
+ try {
2324
+ await this.closeViteDevServer();
2325
+ const { server: server2, localUrl } = await startViteDevServer({
2326
+ root: viteOptions?.root ?? process.cwd(),
2327
+ config: viteOptions?.config,
2328
+ host: viteOptions?.host ?? true,
2329
+ port: viteOptions?.port
2330
+ });
2331
+ this.viteDevServer = server2;
2332
+ setActiveViteDevServer(server2);
2333
+ resolvedAssetsUrl = localUrl;
2334
+ if (typeof process !== "undefined") {
2335
+ process.env.VITE_DEV_SERVER_URL = resolvedAssetsUrl;
2336
+ process.env[MANAGED_VITE_ENV_FLAG] = "1";
2337
+ }
2338
+ syncManagedInertiaDevEntry(resolvedAssetsUrl);
2339
+ this.registerViteTeardown();
2340
+ } catch (error) {
2341
+ console.error("Failed to start Vite dev server:", error);
2342
+ process.exit(1);
2343
+ }
2344
+ }
2345
+ const server = Bun.serve({
2346
+ port,
2347
+ hostname,
2348
+ fetch: (request) => this.fetch(request)
2349
+ });
2350
+ this.bunServer = server;
2351
+ setActiveBunServer(server);
2352
+ this.registerBunTeardown();
2353
+ const shouldLogBanner = typeof process === "undefined" || process.env?.NODE_ENV !== "production" && process.env?.GUREN_DEV_BANNER !== "0";
2354
+ if (shouldLogBanner) {
2355
+ this.logDevServerBanner({
2356
+ hostname,
2357
+ port,
2358
+ assetsUrl: resolvedAssetsUrl ?? "http://localhost:5173"
2359
+ });
2278
2360
  }
2279
- });
2280
- }
2281
- function acceptsJson(request) {
2282
- const accept = request.headers.get("accept")?.toLowerCase() ?? "";
2283
- if (!accept || accept === "*/*") {
2284
- return false;
2285
2361
  }
2286
- if (accept.includes("text/html")) {
2287
- return false;
2362
+ /**
2363
+ * Register a service provider.
2364
+ */
2365
+ register(provider) {
2366
+ this.providerManager.register(provider);
2367
+ return this;
2288
2368
  }
2289
- return accept.includes("application/json") || accept.includes("json");
2290
- }
2291
- function escapeHtml(value) {
2292
- return value.replace(/[&<>"']/gu, (char) => {
2293
- switch (char) {
2294
- case "&":
2295
- return "&amp;";
2296
- case "<":
2297
- return "&lt;";
2298
- case ">":
2299
- return "&gt;";
2300
- case '"':
2301
- return "&quot;";
2302
- case "'":
2303
- return "&#39;";
2304
- default:
2305
- return char;
2369
+ /**
2370
+ * Register multiple service providers.
2371
+ */
2372
+ registerMany(providers) {
2373
+ this.providerManager.registerMany(providers);
2374
+ return this;
2375
+ }
2376
+ /**
2377
+ * Logs the rich development server banner to the console.
2378
+ */
2379
+ logDevServerBanner(options) {
2380
+ logDevServerBanner(options);
2381
+ }
2382
+ async closeViteDevServer() {
2383
+ if (!this.viteDevServer) {
2384
+ return;
2306
2385
  }
2307
- });
2308
- }
2309
- function escapeAttribute(value) {
2310
- return value.replace(/[&"]/gu, (char) => {
2311
- if (char === "&") {
2312
- return "&amp;";
2386
+ try {
2387
+ await this.viteDevServer.close();
2388
+ } catch (error) {
2389
+ console.error("Error while shutting down Vite dev server:", error);
2390
+ } finally {
2391
+ if (getGlobalState().__gurenActiveViteDevServer === this.viteDevServer) {
2392
+ setActiveViteDevServer();
2393
+ }
2394
+ this.viteDevServer = void 0;
2395
+ this.viteTeardownRegistered = false;
2396
+ clearManagedViteEnv();
2313
2397
  }
2314
- return "&quot;";
2315
- });
2316
- }
2317
- function parseStylesEnv(value) {
2318
- if (!value) {
2319
- return [];
2320
2398
  }
2321
- return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
2322
- }
2323
-
2324
- // src/mvc/inertia/shared.ts
2325
- var resolver = null;
2326
- function setInertiaSharedProps(resolverFn) {
2327
- resolver = resolverFn;
2328
- }
2329
- function getInertiaSharedPropsResolver() {
2330
- return resolver;
2331
- }
2332
- function shareInertiaProps(resolverFn) {
2333
- const previous = resolver;
2334
- resolver = async (ctx) => {
2335
- const prev = previous ? await previous(ctx) : {};
2336
- const next = await resolverFn(ctx);
2337
- return { ...prev, ...next };
2338
- };
2339
- }
2340
- async function resolveSharedInertiaProps(ctx) {
2341
- if (!resolver) return {};
2342
- const shared = await resolver(ctx);
2343
- if (shared && typeof shared === "object") {
2344
- return shared;
2399
+ registerViteTeardown() {
2400
+ if (this.viteTeardownRegistered || !this.viteDevServer || typeof process === "undefined") {
2401
+ return;
2402
+ }
2403
+ this.viteTeardownRegistered = true;
2404
+ const exitHandler = () => {
2405
+ this.closeViteDevServer().then(() => process.exit(0)).catch(() => process.exit(1));
2406
+ };
2407
+ process.once("SIGINT", exitHandler);
2408
+ process.once("SIGTERM", exitHandler);
2409
+ process.on("exit", () => {
2410
+ if (this.viteDevServer) {
2411
+ void this.viteDevServer.close();
2412
+ }
2413
+ });
2345
2414
  }
2346
- return {};
2415
+ registerBunTeardown() {
2416
+ if (this.bunTeardownRegistered || typeof process === "undefined") {
2417
+ return;
2418
+ }
2419
+ this.bunTeardownRegistered = true;
2420
+ const exitHandler = () => {
2421
+ stopActiveBunServer().then(() => process.exit(0)).catch(() => process.exit(1));
2422
+ };
2423
+ process.once("SIGINT", exitHandler);
2424
+ process.once("SIGTERM", exitHandler);
2425
+ process.on("exit", () => {
2426
+ void stopActiveBunServer();
2427
+ });
2428
+ }
2429
+ };
2430
+ function createApp(options = {}) {
2431
+ return new Application(options);
2347
2432
  }
2348
2433
 
2349
2434
  // src/mvc/Controller.ts
@@ -2755,89 +2840,6 @@ var Controller = class {
2755
2840
  }
2756
2841
  };
2757
2842
 
2758
- // src/mvc/ViewEngine.ts
2759
- var ViewEngine = class {
2760
- static engines = /* @__PURE__ */ new Map();
2761
- static register(name, renderer) {
2762
- this.engines.set(name, renderer);
2763
- }
2764
- static has(name) {
2765
- return this.engines.has(name);
2766
- }
2767
- static render(name, template, props) {
2768
- const engine = this.engines.get(name);
2769
- if (!engine) {
2770
- throw new Error(`View engine "${name}" has not been registered.`);
2771
- }
2772
- return engine(template, props);
2773
- }
2774
- };
2775
-
2776
- // src/providers/InertiaServiceProvider.ts
2777
- var InertiaServiceProvider = class extends ServiceProvider {
2778
- register() {
2779
- if (!ViewEngine.has("inertia")) {
2780
- ViewEngine.register("inertia", inertia);
2781
- }
2782
- }
2783
- boot() {
2784
- this.registerValidationRenderer();
2785
- this.registerSharedErrors();
2786
- }
2787
- /**
2788
- * Register a custom renderer for ValidationException on Inertia requests.
2789
- * Non-Inertia requests fall through to the default JSON 422 response.
2790
- */
2791
- registerValidationRenderer() {
2792
- let handler;
2793
- try {
2794
- handler = this.container.make("exception.handler");
2795
- } catch {
2796
- return;
2797
- }
2798
- handler.render(ValidationException, (error, ctx) => {
2799
- const isInertia = ctx.req.header("X-Inertia");
2800
- if (!isInertia) {
2801
- return ctx.json(
2802
- {
2803
- message: error.message,
2804
- errors: error.errors
2805
- },
2806
- 422
2807
- );
2808
- }
2809
- const session = getSessionFromContext(ctx);
2810
- if (session) {
2811
- const flattened = {};
2812
- for (const [key, messages] of Object.entries(error.errors ?? {})) {
2813
- flattened[key] = messages[0] ?? "";
2814
- }
2815
- session.flash("errors", flattened);
2816
- }
2817
- const referer = ctx.req.header("Referer") ?? "/";
2818
- return new Response(null, {
2819
- status: 303,
2820
- headers: { Location: referer }
2821
- });
2822
- });
2823
- }
2824
- /**
2825
- * Wrap existing shared props resolver to auto-inject flashed `errors`.
2826
- */
2827
- registerSharedErrors() {
2828
- const previous = getInertiaSharedPropsResolver();
2829
- setInertiaSharedProps(async (ctx) => {
2830
- const prev = previous ? await previous(ctx) : {};
2831
- const session = getSessionFromContext(ctx);
2832
- const errors = session?.getFlash("errors");
2833
- return {
2834
- ...prev,
2835
- ...errors && Object.keys(errors).length > 0 ? { errors } : {}
2836
- };
2837
- });
2838
- }
2839
- };
2840
-
2841
2843
  // src/providers/OAuthServiceProvider.ts
2842
2844
  var OAuthServiceProvider = class extends ServiceProvider {
2843
2845
  register() {